From 8859de53933976ea530abca839aacb6aa421987e Mon Sep 17 00:00:00 2001 From: nym21 Date: Sat, 21 Mar 2026 19:41:41 +0100 Subject: [PATCH] global: snapshot part 17 --- .../src/distribution/cohorts/addr/groups.rs | 7 +- .../src/distribution/cohorts/addr/vecs.rs | 3 +- .../src/distribution/cohorts/traits.rs | 5 +- .../src/distribution/cohorts/utxo/groups.rs | 24 +- .../src/distribution/metrics/cohort/all.rs | 9 +- .../src/distribution/metrics/cohort/basic.rs | 5 +- .../src/distribution/metrics/cohort/core.rs | 7 +- .../distribution/metrics/cohort/extended.rs | 5 +- .../metrics/cohort/extended_adjusted.rs | 4 +- .../distribution/metrics/cohort/minimal.rs | 8 +- .../src/distribution/metrics/cohort/type.rs | 8 +- .../src/distribution/metrics/mod.rs | 2 + .../src/distribution/metrics/outputs/base.rs | 42 ++- .../src/distribution/state/cohort/addr.rs | 1 + .../src/distribution/state/cohort/base.rs | 6 + .../src/distribution/state/cohort/utxo.rs | 1 + crates/brk_computer/src/distribution/vecs.rs | 4 +- .../src/internal/transform/mod.rs | 2 +- .../src/internal/transform/ratio.rs | 15 +- crates/brk_types/src/stored_f32.rs | 5 +- crates/brk_types/src/stored_i16.rs | 8 +- crates/brk_types/src/stored_i8.rs | 8 +- crates/brk_types/src/stored_u16.rs | 4 +- crates/brk_types/src/stored_u32.rs | 20 +- website/scripts/options/cointime.js | 14 +- .../scripts/options/distribution/activity.js | 27 +- .../scripts/options/distribution/holdings.js | 34 +- website/scripts/options/distribution/index.js | 39 ++- .../options/distribution/profitability.js | 6 +- .../scripts/options/distribution/valuation.js | 4 +- website/scripts/options/market.js | 114 +++++-- website/scripts/options/mining.js | 108 +++--- website/scripts/options/network.js | 62 ++-- website/scripts/options/series.js | 310 +++++++++--------- website/scripts/options/shared.js | 6 +- 35 files changed, 567 insertions(+), 360 deletions(-) diff --git a/crates/brk_computer/src/distribution/cohorts/addr/groups.rs b/crates/brk_computer/src/distribution/cohorts/addr/groups.rs index d4ff5f3bc..967036e1e 100644 --- a/crates/brk_computer/src/distribution/cohorts/addr/groups.rs +++ b/crates/brk_computer/src/distribution/cohorts/addr/groups.rs @@ -5,10 +5,10 @@ use brk_cohort::{ }; use brk_error::Result; use brk_traversable::Traversable; -use brk_types::{Height, Indexes, Version}; +use brk_types::{Height, Indexes, StoredU64, Version}; use derive_more::{Deref, DerefMut}; use rayon::prelude::*; -use vecdb::{AnyStoredVec, Database, Exit, Rw, StorageMode}; +use vecdb::{AnyStoredVec, Database, Exit, ReadableVec, Rw, StorageMode}; use crate::{distribution::DynCohortVecs, indexes, internal::CachedWindowStarts, prices}; @@ -106,11 +106,12 @@ impl AddrCohorts { &mut self, prices: &prices::Vecs, starting_indexes: &Indexes, + all_utxo_count: &impl ReadableVec, exit: &Exit, ) -> Result<()> { self.0 .par_iter_mut() - .try_for_each(|v| v.compute_rest_part2(prices, starting_indexes, exit)) + .try_for_each(|v| v.compute_rest_part2(prices, starting_indexes, all_utxo_count, exit)) } /// Returns a parallel iterator over all vecs for parallel writing. diff --git a/crates/brk_computer/src/distribution/cohorts/addr/vecs.rs b/crates/brk_computer/src/distribution/cohorts/addr/vecs.rs index f5443907a..a62fe7e20 100644 --- a/crates/brk_computer/src/distribution/cohorts/addr/vecs.rs +++ b/crates/brk_computer/src/distribution/cohorts/addr/vecs.rs @@ -232,9 +232,10 @@ impl CohortVecs for AddrCohortVecs { &mut self, prices: &prices::Vecs, starting_indexes: &Indexes, + all_utxo_count: &impl ReadableVec, exit: &Exit, ) -> Result<()> { self.metrics - .compute_rest_part2(prices, starting_indexes, exit) + .compute_rest_part2(prices, starting_indexes, all_utxo_count, exit) } } diff --git a/crates/brk_computer/src/distribution/cohorts/traits.rs b/crates/brk_computer/src/distribution/cohorts/traits.rs index 98acf81dc..7306177dd 100644 --- a/crates/brk_computer/src/distribution/cohorts/traits.rs +++ b/crates/brk_computer/src/distribution/cohorts/traits.rs @@ -1,6 +1,6 @@ use brk_error::Result; -use brk_types::{Cents, Height, Indexes, Version}; -use vecdb::Exit; +use brk_types::{Cents, Height, Indexes, StoredU64, Version}; +use vecdb::{Exit, ReadableVec}; use crate::prices; @@ -62,6 +62,7 @@ pub trait CohortVecs: DynCohortVecs { &mut self, prices: &prices::Vecs, starting_indexes: &Indexes, + all_utxo_count: &impl ReadableVec, exit: &Exit, ) -> Result<()>; } diff --git a/crates/brk_computer/src/distribution/cohorts/utxo/groups.rs b/crates/brk_computer/src/distribution/cohorts/utxo/groups.rs index a01c6447c..aeb4bb7c9 100644 --- a/crates/brk_computer/src/distribution/cohorts/utxo/groups.rs +++ b/crates/brk_computer/src/distribution/cohorts/utxo/groups.rs @@ -565,8 +565,9 @@ impl UTXOCohorts { exit, )?; - // Clone all_supply_sats for non-all cohorts. + // Clone all_supply_sats and all_utxo_count for non-all cohorts. let all_supply_sats = self.all.metrics.supply.total.sats.height.read_only_clone(); + let all_utxo_count = self.all.metrics.outputs.unspent_count.height.read_only_clone(); // Destructure to allow parallel mutable access to independent fields. let Self { @@ -589,6 +590,7 @@ impl UTXOCohorts { let vc = &under_1h_value_created; let vd = &under_1h_value_destroyed; let ss = &all_supply_sats; + let au = &all_utxo_count; let tasks: Vec Result<()> + Send + '_>> = vec![ Box::new(|| { @@ -600,6 +602,7 @@ impl UTXOCohorts { vc, vd, ss, + au, exit, ) }), @@ -610,58 +613,59 @@ impl UTXOCohorts { starting_indexes, height_to_market_cap, ss, + au, exit, ) }), Box::new(|| { age_range.par_iter_mut().try_for_each(|v| { v.metrics - .compute_rest_part2(prices, starting_indexes, ss, exit) + .compute_rest_part2(prices, starting_indexes, ss, au, exit) }) }), Box::new(|| { under_age.par_iter_mut().try_for_each(|v| { v.metrics - .compute_rest_part2(prices, starting_indexes, ss, exit) + .compute_rest_part2(prices, starting_indexes, ss, au, exit) }) }), Box::new(|| { over_age.par_iter_mut().try_for_each(|v| { v.metrics - .compute_rest_part2(prices, starting_indexes, ss, exit) + .compute_rest_part2(prices, starting_indexes, ss, au, exit) }) }), Box::new(|| { over_amount .par_iter_mut() - .try_for_each(|v| v.metrics.compute_rest_part2(prices, starting_indexes, exit)) + .try_for_each(|v| v.metrics.compute_rest_part2(prices, starting_indexes, au, exit)) }), Box::new(|| { epoch.par_iter_mut().try_for_each(|v| { v.metrics - .compute_rest_part2(prices, starting_indexes, ss, exit) + .compute_rest_part2(prices, starting_indexes, ss, au, exit) }) }), Box::new(|| { class.par_iter_mut().try_for_each(|v| { v.metrics - .compute_rest_part2(prices, starting_indexes, ss, exit) + .compute_rest_part2(prices, starting_indexes, ss, au, exit) }) }), Box::new(|| { amount_range .par_iter_mut() - .try_for_each(|v| v.metrics.compute_rest_part2(prices, starting_indexes, exit)) + .try_for_each(|v| v.metrics.compute_rest_part2(prices, starting_indexes, au, exit)) }), Box::new(|| { under_amount .par_iter_mut() - .try_for_each(|v| v.metrics.compute_rest_part2(prices, starting_indexes, exit)) + .try_for_each(|v| v.metrics.compute_rest_part2(prices, starting_indexes, au, exit)) }), Box::new(|| { type_ .par_iter_mut() - .try_for_each(|v| v.metrics.compute_rest_part2(prices, starting_indexes, exit)) + .try_for_each(|v| v.metrics.compute_rest_part2(prices, starting_indexes, au, exit)) }), ]; diff --git a/crates/brk_computer/src/distribution/metrics/cohort/all.rs b/crates/brk_computer/src/distribution/metrics/cohort/all.rs index 715458d8a..1f4b784ef 100644 --- a/crates/brk_computer/src/distribution/metrics/cohort/all.rs +++ b/crates/brk_computer/src/distribution/metrics/cohort/all.rs @@ -5,7 +5,7 @@ use brk_types::{ Cents, Dollars, Height, Indexes, Version, }; use vecdb::AnyStoredVec; -use vecdb::{Exit, ReadableVec, Rw, StorageMode}; +use vecdb::{Exit, ReadOnlyClone, ReadableVec, Rw, StorageMode}; use crate::{ blocks, @@ -135,6 +135,13 @@ impl AllCohortMetrics { exit, )?; + let all_utxo_count = self.outputs.unspent_count.height.read_only_clone(); + self.outputs.compute_part2( + starting_indexes.height, + &all_utxo_count, + exit, + )?; + self.cost_basis.compute_prices( starting_indexes, &prices.spot.cents.height, diff --git a/crates/brk_computer/src/distribution/metrics/cohort/basic.rs b/crates/brk_computer/src/distribution/metrics/cohort/basic.rs index 10397313d..4bc378247 100644 --- a/crates/brk_computer/src/distribution/metrics/cohort/basic.rs +++ b/crates/brk_computer/src/distribution/metrics/cohort/basic.rs @@ -1,7 +1,7 @@ use brk_cohort::Filter; use brk_error::Result; use brk_traversable::Traversable; -use brk_types::{Height, Indexes, Sats}; +use brk_types::{Height, Indexes, Sats, StoredU64}; use vecdb::{AnyStoredVec, Exit, ReadableVec, Rw, StorageMode}; use crate::{ @@ -69,6 +69,7 @@ impl BasicCohortMetrics { prices: &prices::Vecs, starting_indexes: &Indexes, all_supply_sats: &impl ReadableVec, + all_utxo_count: &impl ReadableVec, exit: &Exit, ) -> Result<()> { self.realized.compute_rest_part2( @@ -93,6 +94,8 @@ impl BasicCohortMetrics { exit, )?; + self.outputs.compute_part2(starting_indexes.height, all_utxo_count, exit)?; + Ok(()) } diff --git a/crates/brk_computer/src/distribution/metrics/cohort/core.rs b/crates/brk_computer/src/distribution/metrics/cohort/core.rs index 08586fb10..805304413 100644 --- a/crates/brk_computer/src/distribution/metrics/cohort/core.rs +++ b/crates/brk_computer/src/distribution/metrics/cohort/core.rs @@ -1,7 +1,7 @@ use brk_cohort::Filter; use brk_error::Result; use brk_traversable::Traversable; -use brk_types::{Height, Indexes, Sats, Version}; +use brk_types::{Height, Indexes, Sats, StoredU64, Version}; use vecdb::{AnyStoredVec, Exit, ReadableVec, Rw, StorageMode}; use crate::{ @@ -108,6 +108,8 @@ impl CoreCohortMetrics { self.supply .compute(prices, starting_indexes.height, exit)?; + self.outputs.compute_rest(starting_indexes.height, exit)?; + self.activity .compute_rest_part1(prices, starting_indexes, exit)?; @@ -124,6 +126,7 @@ impl CoreCohortMetrics { prices: &prices::Vecs, starting_indexes: &Indexes, all_supply_sats: &impl ReadableVec, + all_utxo_count: &impl ReadableVec, exit: &Exit, ) -> Result<()> { self.realized.compute_rest_part2( @@ -148,6 +151,8 @@ impl CoreCohortMetrics { exit, )?; + self.outputs.compute_part2(starting_indexes.height, all_utxo_count, exit)?; + Ok(()) } } diff --git a/crates/brk_computer/src/distribution/metrics/cohort/extended.rs b/crates/brk_computer/src/distribution/metrics/cohort/extended.rs index c3770d8aa..c8f8c39a6 100644 --- a/crates/brk_computer/src/distribution/metrics/cohort/extended.rs +++ b/crates/brk_computer/src/distribution/metrics/cohort/extended.rs @@ -2,7 +2,7 @@ use brk_cohort::Filter; use brk_error::Result; use brk_traversable::Traversable; use brk_types::{ - Dollars, Height, Indexes, Sats, Version, + Dollars, Height, Indexes, Sats, StoredU64, Version, }; use vecdb::AnyStoredVec; use vecdb::{Exit, ReadableVec, Rw, StorageMode}; @@ -94,6 +94,7 @@ impl ExtendedCohortMetrics { starting_indexes: &Indexes, height_to_market_cap: &impl ReadableVec, all_supply_sats: &impl ReadableVec, + all_utxo_count: &impl ReadableVec, exit: &Exit, ) -> Result<()> { self.realized.compute_rest_part2( @@ -141,6 +142,8 @@ impl ExtendedCohortMetrics { exit, )?; + self.outputs.compute_part2(starting_indexes.height, all_utxo_count, exit)?; + Ok(()) } diff --git a/crates/brk_computer/src/distribution/metrics/cohort/extended_adjusted.rs b/crates/brk_computer/src/distribution/metrics/cohort/extended_adjusted.rs index e15602109..e6d693a28 100644 --- a/crates/brk_computer/src/distribution/metrics/cohort/extended_adjusted.rs +++ b/crates/brk_computer/src/distribution/metrics/cohort/extended_adjusted.rs @@ -1,6 +1,6 @@ use brk_error::Result; use brk_traversable::Traversable; -use brk_types::{Cents, Dollars, Height, Indexes, Sats, Version}; +use brk_types::{Cents, Dollars, Height, Indexes, Sats, StoredU64, Version}; use derive_more::{Deref, DerefMut}; use vecdb::{AnyStoredVec, Exit, ReadableVec, Rw, StorageMode}; @@ -67,6 +67,7 @@ impl ExtendedAdjustedCohortMetrics { under_1h_value_created: &impl ReadableVec, under_1h_value_destroyed: &impl ReadableVec, all_supply_sats: &impl ReadableVec, + all_utxo_count: &impl ReadableVec, exit: &Exit, ) -> Result<()> { self.inner.compute_rest_part2( @@ -75,6 +76,7 @@ impl ExtendedAdjustedCohortMetrics { starting_indexes, height_to_market_cap, all_supply_sats, + all_utxo_count, exit, )?; diff --git a/crates/brk_computer/src/distribution/metrics/cohort/minimal.rs b/crates/brk_computer/src/distribution/metrics/cohort/minimal.rs index 47c2b1563..4b45be071 100644 --- a/crates/brk_computer/src/distribution/metrics/cohort/minimal.rs +++ b/crates/brk_computer/src/distribution/metrics/cohort/minimal.rs @@ -1,8 +1,8 @@ use brk_cohort::Filter; use brk_error::Result; use brk_traversable::Traversable; -use brk_types::Indexes; -use vecdb::{AnyStoredVec, Exit, Rw, StorageMode}; +use brk_types::{Height, Indexes, StoredU64}; +use vecdb::{AnyStoredVec, Exit, ReadableVec, Rw, StorageMode}; use crate::{ distribution::metrics::{ @@ -101,6 +101,7 @@ impl MinimalCohortMetrics { exit: &Exit, ) -> Result<()> { self.supply.compute(prices, starting_indexes.height, exit)?; + self.outputs.compute_rest(starting_indexes.height, exit)?; self.activity .compute_rest_part1(prices, starting_indexes, exit)?; self.realized @@ -112,6 +113,7 @@ impl MinimalCohortMetrics { &mut self, prices: &prices::Vecs, starting_indexes: &Indexes, + all_utxo_count: &impl ReadableVec, exit: &Exit, ) -> Result<()> { self.realized.compute_rest_part2( @@ -128,6 +130,8 @@ impl MinimalCohortMetrics { exit, )?; + self.outputs.compute_part2(starting_indexes.height, all_utxo_count, exit)?; + Ok(()) } } diff --git a/crates/brk_computer/src/distribution/metrics/cohort/type.rs b/crates/brk_computer/src/distribution/metrics/cohort/type.rs index 18e8a9567..ca214e6f0 100644 --- a/crates/brk_computer/src/distribution/metrics/cohort/type.rs +++ b/crates/brk_computer/src/distribution/metrics/cohort/type.rs @@ -1,8 +1,8 @@ use brk_cohort::Filter; use brk_error::Result; use brk_traversable::Traversable; -use brk_types::Indexes; -use vecdb::{AnyStoredVec, Exit, Rw, StorageMode}; +use brk_types::{Height, Indexes, StoredU64}; +use vecdb::{AnyStoredVec, Exit, ReadableVec, Rw, StorageMode}; use crate::{ distribution::metrics::{ @@ -63,6 +63,7 @@ impl TypeCohortMetrics { exit: &Exit, ) -> Result<()> { self.supply.compute(prices, starting_indexes.height, exit)?; + self.outputs.compute_rest(starting_indexes.height, exit)?; self.activity .compute_rest_part1(prices, starting_indexes, exit)?; self.realized @@ -74,6 +75,7 @@ impl TypeCohortMetrics { &mut self, prices: &prices::Vecs, starting_indexes: &Indexes, + all_utxo_count: &impl ReadableVec, exit: &Exit, ) -> Result<()> { self.realized.compute_rest_part2( @@ -90,6 +92,8 @@ impl TypeCohortMetrics { exit, )?; + self.outputs.compute_part2(starting_indexes.height, all_utxo_count, exit)?; + Ok(()) } } diff --git a/crates/brk_computer/src/distribution/metrics/mod.rs b/crates/brk_computer/src/distribution/metrics/mod.rs index b3a91199f..d6bacc655 100644 --- a/crates/brk_computer/src/distribution/metrics/mod.rs +++ b/crates/brk_computer/src/distribution/metrics/mod.rs @@ -269,6 +269,8 @@ pub trait CohortMetricsBase: ) -> Result<()> { self.supply_mut() .compute(prices, starting_indexes.height, exit)?; + self.outputs_mut() + .compute_rest(starting_indexes.height, exit)?; self.activity_mut() .compute_rest_part1(prices, starting_indexes, exit)?; diff --git a/crates/brk_computer/src/distribution/metrics/outputs/base.rs b/crates/brk_computer/src/distribution/metrics/outputs/base.rs index 803a3ae35..28c34de3f 100644 --- a/crates/brk_computer/src/distribution/metrics/outputs/base.rs +++ b/crates/brk_computer/src/distribution/metrics/outputs/base.rs @@ -1,38 +1,44 @@ use brk_error::Result; use brk_traversable::Traversable; -use brk_types::{BasisPointsSigned32, Indexes, StoredI64, StoredU64, Version}; -use vecdb::{AnyStoredVec, AnyVec, Exit, Rw, StorageMode, WritableVec}; +use brk_types::{BasisPointsSigned32, Height, Indexes, StoredF32, StoredI64, StoredU32, StoredU64, Version}; +use vecdb::{AnyStoredVec, AnyVec, Exit, ReadableVec, Rw, StorageMode, WritableVec}; use crate::{ distribution::{ metrics::ImportConfig, state::{CohortState, CostBasisOps, RealizedOps}, }, - internal::PerBlockWithDeltas, + internal::{PerBlock, PerBlockCumulativeRolling, PerBlockWithDeltas, RatioU32U64F32}, }; /// Base output metrics: utxo_count + delta. #[derive(Traversable)] pub struct OutputsBase { pub unspent_count: PerBlockWithDeltas, + pub spent_count: PerBlockCumulativeRolling, + pub spending_rate: PerBlock, } impl OutputsBase { pub(crate) fn forced_import(cfg: &ImportConfig) -> Result { + let v1 = Version::ONE; Ok(Self { unspent_count: PerBlockWithDeltas::forced_import( cfg.db, &cfg.name("utxo_count"), cfg.version, - Version::ONE, + v1, cfg.indexes, cfg.cached_starts, )?, + spent_count: cfg.import("spent_utxo_count", v1)?, + spending_rate: cfg.import("spending_rate", v1)?, }) } pub(crate) fn min_len(&self) -> usize { self.unspent_count.height.len() + .min(self.spent_count.block.len()) } #[inline(always)] @@ -40,10 +46,35 @@ impl OutputsBase { self.unspent_count .height .push(StoredU64::from(state.supply.utxo_count)); + self.spent_count + .block + .push(StoredU32::from(state.spent_utxo_count)); } pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> { - vec![&mut self.unspent_count.height as &mut dyn AnyStoredVec] + vec![ + &mut self.unspent_count.height as &mut dyn AnyStoredVec, + &mut self.spent_count.block, + ] + } + + pub(crate) fn compute_rest(&mut self, max_from: Height, exit: &Exit) -> Result<()> { + self.spent_count.compute_rest(max_from, exit) + } + + pub(crate) fn compute_part2( + &mut self, + max_from: Height, + all_utxo_count: &impl ReadableVec, + exit: &Exit, + ) -> Result<()> { + self.spending_rate + .compute_binary::( + max_from, + &self.spent_count.block, + all_utxo_count, + exit, + ) } pub(crate) fn compute_from_stateful( @@ -60,6 +91,7 @@ impl OutputsBase { .collect::>(), exit, )?; + sum_others!(self, starting_indexes, others, exit; spent_count.block); Ok(()) } } diff --git a/crates/brk_computer/src/distribution/state/cohort/addr.rs b/crates/brk_computer/src/distribution/state/cohort/addr.rs index b604111e2..75923a581 100644 --- a/crates/brk_computer/src/distribution/state/cohort/addr.rs +++ b/crates/brk_computer/src/distribution/state/cohort/addr.rs @@ -28,6 +28,7 @@ impl AddrCohortState { self.addr_count = 0; self.inner.supply = SupplyState::default(); self.inner.sent = Sats::ZERO; + self.inner.spent_utxo_count = 0; self.inner.satdays_destroyed = Sats::ZERO; self.inner.realized = R::default(); } diff --git a/crates/brk_computer/src/distribution/state/cohort/base.rs b/crates/brk_computer/src/distribution/state/cohort/base.rs index 8f1f004e4..d5e1d6a88 100644 --- a/crates/brk_computer/src/distribution/state/cohort/base.rs +++ b/crates/brk_computer/src/distribution/state/cohort/base.rs @@ -53,6 +53,7 @@ pub struct CohortState { pub supply: SupplyState, pub realized: R, pub sent: Sats, + pub spent_utxo_count: u64, pub satdays_destroyed: Sats, cost_basis: C, } @@ -63,6 +64,7 @@ impl CohortState { supply: SupplyState::default(), realized: R::default(), sent: Sats::ZERO, + spent_utxo_count: 0, satdays_destroyed: Sats::ZERO, cost_basis: C::create(path, name), } @@ -97,6 +99,7 @@ impl CohortState { pub(crate) fn reset_single_iteration_values(&mut self) { self.sent = Sats::ZERO; + self.spent_utxo_count = 0; if R::TRACK_ACTIVITY { self.satdays_destroyed = Sats::ZERO; } @@ -197,6 +200,7 @@ impl CohortState { ) { self.supply -= supply; self.sent += pre.sats; + self.spent_utxo_count += supply.utxo_count; if R::TRACK_ACTIVITY { self.satdays_destroyed += pre.age.satdays_destroyed(pre.sats); } @@ -220,6 +224,7 @@ impl CohortState { self.send_utxo_precomputed(supply, &pre); } else if supply.utxo_count > 0 { self.supply -= supply; + self.spent_utxo_count += supply.utxo_count; } } @@ -239,6 +244,7 @@ impl CohortState { } self.supply -= supply; + self.spent_utxo_count += supply.utxo_count; if supply.value > Sats::ZERO { self.sent += supply.value; diff --git a/crates/brk_computer/src/distribution/state/cohort/utxo.rs b/crates/brk_computer/src/distribution/state/cohort/utxo.rs index 297fab4ae..bfa002d04 100644 --- a/crates/brk_computer/src/distribution/state/cohort/utxo.rs +++ b/crates/brk_computer/src/distribution/state/cohort/utxo.rs @@ -23,6 +23,7 @@ impl UTXOCohortState { pub(crate) fn reset(&mut self) { self.0.supply = SupplyState::default(); self.0.sent = Sats::ZERO; + self.0.spent_utxo_count = 0; self.0.satdays_destroyed = Sats::ZERO; self.0.realized = R::default(); } diff --git a/crates/brk_computer/src/distribution/vecs.rs b/crates/brk_computer/src/distribution/vecs.rs index ad0353b0c..55c978b46 100644 --- a/crates/brk_computer/src/distribution/vecs.rs +++ b/crates/brk_computer/src/distribution/vecs.rs @@ -467,8 +467,10 @@ impl Vecs { &height_to_market_cap, exit, )?; + + let all_utxo_count = self.utxo_cohorts.all.metrics.outputs.unspent_count.height.read_only_clone(); self.addr_cohorts - .compute_rest_part2(prices, starting_indexes, exit)?; + .compute_rest_part2(prices, starting_indexes, &all_utxo_count, exit)?; let _lock = exit.lock(); self.db.compact()?; diff --git a/crates/brk_computer/src/internal/transform/mod.rs b/crates/brk_computer/src/internal/transform/mod.rs index 327b37be7..2ef08bfed 100644 --- a/crates/brk_computer/src/internal/transform/mod.rs +++ b/crates/brk_computer/src/internal/transform/mod.rs @@ -26,7 +26,7 @@ pub use derived::{ pub use ratio::{ RatioCentsBp32, RatioCentsSignedCentsBps32, RatioCentsSignedDollarsBps32, RatioDiffCentsBps32, RatioDiffDollarsBps32, RatioDiffF32Bps32, RatioDollarsBp16, RatioDollarsBp32, - RatioDollarsBps32, RatioSatsBp16, RatioU64Bp16, + RatioDollarsBps32, RatioSatsBp16, RatioU32U64F32, RatioU64Bp16, }; pub use specialized::{ BlockCountTarget1m, BlockCountTarget1w, BlockCountTarget1y, BlockCountTarget24h, diff --git a/crates/brk_computer/src/internal/transform/ratio.rs b/crates/brk_computer/src/internal/transform/ratio.rs index e2cb66190..34b8a6a41 100644 --- a/crates/brk_computer/src/internal/transform/ratio.rs +++ b/crates/brk_computer/src/internal/transform/ratio.rs @@ -1,6 +1,6 @@ use brk_types::{ BasisPoints16, BasisPoints32, BasisPointsSigned32, Cents, CentsSigned, Dollars, Sats, StoredF32, - StoredU64, + StoredU32, StoredU64, }; use vecdb::BinaryTransform; @@ -112,6 +112,19 @@ impl BinaryTransform for RatioDollarsBp32 { } } +pub struct RatioU32U64F32; + +impl BinaryTransform for RatioU32U64F32 { + #[inline(always)] + fn apply(numerator: StoredU32, denominator: StoredU64) -> StoredF32 { + if *denominator > 0 { + StoredF32::from(*numerator as f64 / *denominator as f64) + } else { + StoredF32::default() + } + } +} + pub struct RatioDiffF32Bps32; impl BinaryTransform for RatioDiffF32Bps32 { diff --git a/crates/brk_types/src/stored_f32.rs b/crates/brk_types/src/stored_f32.rs index 16edcf54e..af763ba2e 100644 --- a/crates/brk_types/src/stored_f32.rs +++ b/crates/brk_types/src/stored_f32.rs @@ -1,4 +1,3 @@ -use core::panic; use std::{ cmp::Ordering, f32, @@ -33,9 +32,7 @@ impl From for StoredF32 { impl From for StoredF32 { #[inline] fn from(value: f64) -> Self { - if value > f32::MAX as f64 { - panic!("f64 is too big") - } + debug_assert!(value <= f32::MAX as f64); Self(value as f32) } } diff --git a/crates/brk_types/src/stored_i16.rs b/crates/brk_types/src/stored_i16.rs index 24a5593a1..0563e1410 100644 --- a/crates/brk_types/src/stored_i16.rs +++ b/crates/brk_types/src/stored_i16.rs @@ -40,9 +40,7 @@ impl From for StoredI16 { impl From for StoredI16 { #[inline] fn from(value: usize) -> Self { - if value > i16::MAX as usize { - panic!("usize too big (value = {value})") - } + debug_assert!(value <= i16::MAX as usize); Self(value as i16) } } @@ -76,9 +74,7 @@ impl AddAssign for StoredI16 { impl From for StoredI16 { #[inline] fn from(value: f64) -> Self { - if value < 0.0 || value > i16::MAX as f64 { - panic!() - } + debug_assert!(value >= 0.0 && value <= i16::MAX as f64); Self(value as i16) } } diff --git a/crates/brk_types/src/stored_i8.rs b/crates/brk_types/src/stored_i8.rs index 87dbd1f9d..1dc05a0fb 100644 --- a/crates/brk_types/src/stored_i8.rs +++ b/crates/brk_types/src/stored_i8.rs @@ -40,9 +40,7 @@ impl From for StoredI8 { impl From for StoredI8 { #[inline] fn from(value: usize) -> Self { - if value > i8::MAX as usize { - panic!("usize too big (value = {value})") - } + debug_assert!(value <= i8::MAX as usize); Self(value as i8) } } @@ -76,9 +74,7 @@ impl AddAssign for StoredI8 { impl From for StoredI8 { #[inline] fn from(value: f64) -> Self { - if value < i8::MIN as f64 || value > i8::MAX as f64 { - panic!() - } + debug_assert!(value >= i8::MIN as f64 && value <= i8::MAX as f64); Self(value as i8) } } diff --git a/crates/brk_types/src/stored_u16.rs b/crates/brk_types/src/stored_u16.rs index 1d22be88e..826f0f043 100644 --- a/crates/brk_types/src/stored_u16.rs +++ b/crates/brk_types/src/stored_u16.rs @@ -47,9 +47,7 @@ impl From for StoredU16 { impl From for StoredU16 { #[inline] fn from(value: usize) -> Self { - if value > u16::MAX as usize { - panic!("usize too big (value = {value})") - } + debug_assert!(value <= u16::MAX as usize); Self(value as u16) } } diff --git a/crates/brk_types/src/stored_u32.rs b/crates/brk_types/src/stored_u32.rs index 7ee2de194..ac15044b8 100644 --- a/crates/brk_types/src/stored_u32.rs +++ b/crates/brk_types/src/stored_u32.rs @@ -63,12 +63,18 @@ impl From for f32 { } } +impl From for StoredU32 { + #[inline] + fn from(value: u64) -> Self { + debug_assert!(value <= u32::MAX as u64); + Self(value as u32) + } +} + impl From for StoredU32 { #[inline] fn from(value: usize) -> Self { - if value > u32::MAX as usize { - panic!("usize too big (value = {value})") - } + debug_assert!(value <= u32::MAX as usize); Self(value as u32) } } @@ -81,9 +87,7 @@ impl CheckedSub for StoredU32 { impl CheckedSub for StoredU32 { fn checked_sub(self, rhs: usize) -> Option { - if rhs > u32::MAX as usize { - panic!() - } + debug_assert!(rhs <= u32::MAX as usize); self.0.checked_sub(rhs as u32).map(Self) } } @@ -125,9 +129,7 @@ impl Mul for StoredU32 { type Output = Self; fn mul(self, rhs: usize) -> Self::Output { let res = self.0 as usize * rhs; - if res > u32::MAX as usize { - panic!() - } + debug_assert!(res <= u32::MAX as usize); Self::from(res) } } diff --git a/website/scripts/options/cointime.js b/website/scripts/options/cointime.js index 6d0855305..9e1355474 100644 --- a/website/scripts/options/cointime.js +++ b/website/scripts/options/cointime.js @@ -285,16 +285,16 @@ export function createCointimeSection() { sum: pattern.sum, cumulative: pattern.cumulative, })), - title: "Coinblocks", + metric: "Coinblocks", unit: Unit.coinblocks, }), - ...coinblocks.map(({ pattern, name, title, color }) => ({ + ...coinblocks.map(({ pattern, name, title: metric, color }) => ({ name, tree: sumsAndAveragesCumulative({ sum: pattern.sum, average: pattern.average, cumulative: pattern.cumulative, - title, + metric, unit: Unit.coinblocks, color, }), @@ -322,16 +322,16 @@ export function createCointimeSection() { cumulative: vocdd.pattern.cumulative, }, ], - title: "Cointime Value", + metric: "Cointime Value", unit: Unit.usd, }), - ...cointimeValues.map(({ pattern, name, title, color }) => ({ + ...cointimeValues.map(({ pattern, name, title: metric, color }) => ({ name, tree: sumsAndAveragesCumulative({ sum: pattern.sum, average: pattern.average, cumulative: pattern.cumulative, - title, + metric, unit: Unit.usd, color, }), @@ -342,7 +342,7 @@ export function createCointimeSection() { sum: vocdd.pattern.sum, average: vocdd.pattern.average, cumulative: vocdd.pattern.cumulative, - title: vocdd.title, + metric: vocdd.title, unit: Unit.usd, color: vocdd.color, }), diff --git a/website/scripts/options/distribution/activity.js b/website/scripts/options/distribution/activity.js index d1df5ea48..7bdf15041 100644 --- a/website/scripts/options/distribution/activity.js +++ b/website/scripts/options/distribution/activity.js @@ -41,7 +41,8 @@ function volumeTree(tv, color, title) { return [ ...satsBtcUsdFullTree({ pattern: tv, - title: title("Transfer Volume"), + title, + metric: "Transfer Volume", color, }), { @@ -83,7 +84,8 @@ function volumeTree(tv, color, title) { name: "In Profit", tree: satsBtcUsdFullTree({ pattern: tv.inProfit, - title: title("Transfer Volume In Profit"), + title, + metric: "Transfer Volume In Profit", color: colors.profit, }), }, @@ -91,7 +93,8 @@ function volumeTree(tv, color, title) { name: "In Loss", tree: satsBtcUsdFullTree({ pattern: tv.inLoss, - title: title("Transfer Volume In Loss"), + title, + metric: "Transfer Volume In Loss", color: colors.loss, }), }, @@ -122,7 +125,7 @@ function volumeFolderWithAdjusted(activity, adjustedTransferVolume, color, title name: "Volume", tree: [ ...volumeTree(activity.transferVolume, color, title), - { name: "Adjusted", tree: chartsFromCount({ pattern: adjustedTransferVolume, title: title("Adjusted Transfer Volume"), unit: Unit.usd }) }, + { name: "Adjusted", tree: chartsFromCount({ pattern: adjustedTransferVolume, title, metric: "Adjusted Transfer Volume", unit: Unit.usd }) }, ], }; } @@ -173,7 +176,7 @@ function singleRollingSoprTree(ratio, title, prefix = "") { * @returns {PartialOptionsTree} */ function valueDestroyedTree(valueDestroyed, title) { - return chartsFromCount({ pattern: valueDestroyed, title: title("Value Destroyed"), unit: Unit.usd }); + return chartsFromCount({ pattern: valueDestroyed, title, metric: "Value Destroyed", unit: Unit.usd }); } /** @@ -196,7 +199,7 @@ function valueDestroyedFolderWithAdjusted(valueDestroyed, adjusted, title) { name: "Value Destroyed", tree: [ ...valueDestroyedTree(valueDestroyed, title), - { name: "Adjusted", tree: chartsFromCount({ pattern: adjusted, title: title("Adjusted Value Destroyed"), unit: Unit.usd }) }, + { name: "Adjusted", tree: chartsFromCount({ pattern: adjusted, title, metric: "Adjusted Value Destroyed", unit: Unit.usd }) }, ], }; } @@ -258,7 +261,8 @@ function singleFullActivityTree(cohort, title, volumeItem, soprFolder, valueDest name: "Coindays Destroyed", tree: chartsFromCount({ pattern: tree.activity.coindaysDestroyed, - title: title("Coindays Destroyed"), + title, + metric: "Coindays Destroyed", unit: Unit.coindays, color, }), @@ -267,7 +271,8 @@ function singleFullActivityTree(cohort, title, volumeItem, soprFolder, valueDest name: "Dormancy", tree: averagesArray({ windows: tree.activity.dormancy, - title: title("Dormancy"), + title, + metric: "Dormancy", unit: Unit.days, }), }, @@ -341,7 +346,8 @@ export function createActivitySectionWithActivity({ cohort, title }) { name: "Coindays Destroyed", tree: chartsFromCount({ pattern: tree.activity.coindaysDestroyed, - title: title("Coindays Destroyed"), + title, + metric: "Coindays Destroyed", unit: Unit.coindays, color, }), @@ -360,7 +366,8 @@ export function createActivitySectionMinimal({ cohort, title }) { name: "Activity", tree: satsBtcUsdFullTree({ pattern: cohort.tree.activity.transferVolume, - title: title("Transfer Volume"), + title, + metric: "Transfer Volume", }), }; } diff --git a/website/scripts/options/distribution/holdings.js b/website/scripts/options/distribution/holdings.js index 859a82b01..f980d092b 100644 --- a/website/scripts/options/distribution/holdings.js +++ b/website/scripts/options/distribution/holdings.js @@ -52,12 +52,12 @@ function groupedUtxoCountFolder(list, all, title) { tree: [ { name: "Count", - title: title("UTXOs"), + title: title("UTXO Count"), bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ series: tree.outputs.unspentCount.base, name, color, unit: Unit.count }), ), }, - ...groupedDeltaItems(list, all, (c) => c.tree.outputs.unspentCount.delta, Unit.count, title, "UTXOs"), + ...groupedDeltaItems(list, all, (c) => c.tree.outputs.unspentCount.delta, Unit.count, title, "UTXO Count"), ], }; } @@ -74,7 +74,8 @@ function singleDeltaItems(delta, unit, title, name) { { ...sumsTreeBaseline({ windows: delta.absolute, - title: title(`${name} Change`), + title, + metric: `${name} Change`, unit, }), name: "Change", @@ -82,7 +83,8 @@ function singleDeltaItems(delta, unit, title, name) { { ...rollingPercentRatioTree({ windows: delta.rate, - title: title(`${name} Growth Rate`), + title, + metric: `${name} Growth Rate`, }), name: "Growth Rate", }, @@ -233,7 +235,7 @@ function countFolder(pattern, name, chartTitle, color, title) { }), ], }, - ...singleDeltaItems(pattern.delta, Unit.count, title, "Change"), + ...singleDeltaItems(pattern.delta, Unit.count, title, chartTitle), ], }; } @@ -257,7 +259,7 @@ export function createHoldingsSection({ cohort, title }) { title: title("Supply"), bottom: simpleSupplySeries(supply), }, - ...singleDeltaItems(supply.delta, Unit.sats, title, "Change"), + ...singleDeltaItems(supply.delta, Unit.sats, title, "Supply"), ], }, countFolder(cohort.tree.outputs.unspentCount, "UTXOs", "UTXO Count", cohort.color, title), @@ -281,7 +283,7 @@ export function createHoldingsSectionAll({ cohort, title }) { }, profitabilityChart(supply, title), ownSupplyChart(supply, title), - ...singleDeltaItems(supply.delta, Unit.sats, title, "Change"), + ...singleDeltaItems(supply.delta, Unit.sats, title, "Supply"), ], }, countFolder(cohort.tree.outputs.unspentCount, "UTXOs", "UTXO Count", cohort.color, title), @@ -307,7 +309,7 @@ export function createHoldingsSectionWithRelative({ cohort, title }) { profitabilityChart(supply, title), circulatingChart(supply, title), ownSupplyChart(supply, title), - ...singleDeltaItems(supply.delta, Unit.sats, title, "Change"), + ...singleDeltaItems(supply.delta, Unit.sats, title, "Supply"), ], }, countFolder(cohort.tree.outputs.unspentCount, "UTXOs", "UTXO Count", cohort.color, title), @@ -331,7 +333,7 @@ export function createHoldingsSectionWithOwnSupply({ cohort, title }) { }, profitabilityChart(supply, title), circulatingChart(supply, title), - ...singleDeltaItems(supply.delta, Unit.sats, title, "Change"), + ...singleDeltaItems(supply.delta, Unit.sats, title, "Supply"), ], }, countFolder(cohort.tree.outputs.unspentCount, "UTXOs", "UTXO Count", cohort.color, title), @@ -354,7 +356,7 @@ export function createHoldingsSectionWithProfitLoss({ cohort, title }) { bottom: simpleSupplySeries(supply), }, profitabilityChart(supply, title), - ...singleDeltaItems(supply.delta, Unit.sats, title, "Change"), + ...singleDeltaItems(supply.delta, Unit.sats, title, "Supply"), ], }, countFolder(cohort.tree.outputs.unspentCount, "UTXOs", "UTXO Count", cohort.color, title), @@ -377,7 +379,7 @@ export function createHoldingsSectionAddress({ cohort, title }) { bottom: simpleSupplySeries(supply), }, profitabilityChart(supply, title), - ...singleDeltaItems(supply.delta, Unit.sats, title, "Change"), + ...singleDeltaItems(supply.delta, Unit.sats, title, "Supply"), ], }, countFolder(cohort.tree.outputs.unspentCount, "UTXOs", "UTXO Count", cohort.color, title), @@ -400,7 +402,7 @@ export function createHoldingsSectionAddressAmount({ cohort, title }) { title: title("Supply"), bottom: simpleSupplySeries(supply), }, - ...singleDeltaItems(supply.delta, Unit.sats, title, "Change"), + ...singleDeltaItems(supply.delta, Unit.sats, title, "Supply"), ], }, countFolder(cohort.tree.outputs.unspentCount, "UTXOs", "UTXO Count", cohort.color, title), @@ -461,12 +463,12 @@ export function createGroupedHoldingsSectionAddress({ list, all, title }) { tree: [ { name: "Count", - title: title("Addresses"), + title: title("Address Count"), bottom: mapCohortsWithAll(list, all, ({ name, color, addressCount }) => line({ series: addressCount.base, name, color, unit: Unit.count }), ), }, - ...groupedDeltaItems(list, all, (c) => c.addressCount.delta, Unit.count, title, "Addresses"), + ...groupedDeltaItems(list, all, (c) => c.addressCount.delta, Unit.count, title, "Address Count"), ], }, ]; @@ -492,12 +494,12 @@ export function createGroupedHoldingsSectionAddressAmount({ list, all, title }) tree: [ { name: "Count", - title: title("Addresses"), + title: title("Address Count"), bottom: mapCohortsWithAll(list, all, ({ name, color, addressCount }) => line({ series: addressCount.base, name, color, unit: Unit.count }), ), }, - ...groupedDeltaItems(list, all, (c) => c.addressCount.delta, Unit.count, title, "Addresses"), + ...groupedDeltaItems(list, all, (c) => c.addressCount.delta, Unit.count, title, "Address Count"), ], }, ]; diff --git a/website/scripts/options/distribution/index.js b/website/scripts/options/distribution/index.js index 61c10c8f5..c677ecdac 100644 --- a/website/scripts/options/distribution/index.js +++ b/website/scripts/options/distribution/index.js @@ -216,7 +216,8 @@ export function createCohortFolderAgeRangeWithMatured(cohort) { name: "Matured", tree: satsBtcUsdFullTree({ pattern: cohort.matured, - title: title("Matured Supply"), + title, + metric: "Matured Supply", }), }); return folder; @@ -498,6 +499,7 @@ export function createGroupedAddressCohortFolder({ * @returns {PartialOptionsGroup} */ function singleBucketFolder({ name, color, pattern }) { + const title = formatCohortTitle(name); return { name, tree: [ @@ -506,7 +508,7 @@ function singleBucketFolder({ name, color, pattern }) { tree: [ { name: "Value", - title: `${name}: Supply`, + title: title("Supply"), bottom: [ ...satsBtcUsd({ pattern: pattern.supply.all, name: "Total" }), ...satsBtcUsd({ @@ -522,7 +524,8 @@ function singleBucketFolder({ name, color, pattern }) { { ...sumsTreeBaseline({ windows: pattern.supply.all.delta.absolute, - title: `${name}: Supply Change`, + title, + metric: "Supply Change", unit: Unit.sats, }), name: "Change", @@ -530,7 +533,8 @@ function singleBucketFolder({ name, color, pattern }) { { ...rollingPercentRatioTree({ windows: pattern.supply.all.delta.rate, - title: `${name}: Supply Rate`, + title, + metric: "Supply Growth Rate", }), name: "Growth Rate", }, @@ -540,7 +544,7 @@ function singleBucketFolder({ name, color, pattern }) { }, { name: "Realized Cap", - title: `${name}: Realized Cap`, + title: title("Realized Cap"), bottom: [ line({ series: pattern.realizedCap.all, @@ -557,7 +561,7 @@ function singleBucketFolder({ name, color, pattern }) { }, { name: "NUPL", - title: `${name}: NUPL`, + title: title("NUPL"), bottom: [ line({ series: pattern.nupl.ratio, name, color, unit: Unit.ratio }), ], @@ -568,24 +572,25 @@ function singleBucketFolder({ name, color, pattern }) { /** * @param {{ name: string, color: Color, pattern: RealizedSupplyPattern }[]} list - * @param {string} titlePrefix + * @param {string} groupTitle * @returns {PartialOptionsTree} */ -function groupedBucketCharts(list, titlePrefix) { +function groupedBucketCharts(list, groupTitle) { + const title = formatCohortTitle(groupTitle); return [ { name: "Supply", tree: [ { name: "All", - title: `${titlePrefix}: Supply`, + title: title("Supply"), bottom: list.flatMap(({ name, color, pattern }) => satsBtcUsd({ pattern: pattern.supply.all, name, color }), ), }, { name: "STH", - title: `${titlePrefix}: STH Supply`, + title: title("STH Supply"), bottom: list.flatMap(({ name, color, pattern }) => satsBtcUsd({ pattern: pattern.supply.sth, name, color }), ), @@ -598,7 +603,7 @@ function groupedBucketCharts(list, titlePrefix) { tree: [ { name: "Compare", - title: `${titlePrefix}: Supply Change`, + title: title("Supply Change"), bottom: ROLLING_WINDOWS.flatMap((w) => list.map(({ name, color, pattern }) => baseline({ @@ -612,7 +617,7 @@ function groupedBucketCharts(list, titlePrefix) { }, ...ROLLING_WINDOWS.map((w) => ({ name: w.name, - title: `${titlePrefix}: Supply Change (${w.title})`, + title: title(`Supply Change (${w.title})`), bottom: list.map(({ name, color, pattern }) => baseline({ series: pattern.supply.all.delta.absolute[w.key], @@ -629,7 +634,7 @@ function groupedBucketCharts(list, titlePrefix) { tree: [ { name: "Compare", - title: `${titlePrefix}: Supply Rate`, + title: title("Supply Growth Rate"), bottom: ROLLING_WINDOWS.flatMap((w) => list.flatMap(({ name, color, pattern }) => percentRatio({ @@ -642,7 +647,7 @@ function groupedBucketCharts(list, titlePrefix) { }, ...ROLLING_WINDOWS.map((w) => ({ name: w.name, - title: `${titlePrefix}: Supply Rate (${w.title})`, + title: title(`Supply Growth Rate (${w.title})`), bottom: list.flatMap(({ name, color, pattern }) => percentRatio({ pattern: pattern.supply.all.delta.rate[w.key], @@ -662,7 +667,7 @@ function groupedBucketCharts(list, titlePrefix) { tree: [ { name: "All", - title: `${titlePrefix}: Realized Cap`, + title: title("Realized Cap"), bottom: list.map(({ name, color, pattern }) => line({ series: pattern.realizedCap.all, @@ -674,7 +679,7 @@ function groupedBucketCharts(list, titlePrefix) { }, { name: "STH", - title: `${titlePrefix}: STH Realized Cap`, + title: title("STH Realized Cap"), bottom: list.map(({ name, color, pattern }) => line({ series: pattern.realizedCap.sth, @@ -688,7 +693,7 @@ function groupedBucketCharts(list, titlePrefix) { }, { name: "NUPL", - title: `${titlePrefix}: NUPL`, + title: title("NUPL"), bottom: list.map(({ name, color, pattern }) => line({ series: pattern.nupl.ratio, name, color, unit: Unit.ratio }), ), diff --git a/website/scripts/options/distribution/profitability.js b/website/scripts/options/distribution/profitability.js index ba4afde7d..006692646 100644 --- a/website/scripts/options/distribution/profitability.js +++ b/website/scripts/options/distribution/profitability.js @@ -389,7 +389,8 @@ function realizedNetFolder({ netPnl, title, extraChange = [] }) { { ...sumsTreeBaseline({ windows: mapWindows(netPnl.delta.absolute, (c) => c.usd), - title: title("Net Realized P&L Change"), + title, + metric: "Net Realized P&L Change", unit: Unit.usd, }), name: "Change", @@ -399,7 +400,8 @@ function realizedNetFolder({ netPnl, title, extraChange = [] }) { tree: [ ...rollingPercentRatioTree({ windows: netPnl.delta.rate, - title: title("Net Realized P&L Growth Rate"), + title, + metric: "Net Realized P&L Growth Rate", }).tree, ...extraChange, ], diff --git a/website/scripts/options/distribution/valuation.js b/website/scripts/options/distribution/valuation.js index df5085a51..ac73f98d8 100644 --- a/website/scripts/options/distribution/valuation.js +++ b/website/scripts/options/distribution/valuation.js @@ -19,8 +19,8 @@ import { ratioBottomSeries, mapCohortsWithAll, flatMapCohortsWithAll } from "../ */ function singleDeltaItems(tree, title) { return [ - { ...sumsTreeBaseline({ windows: mapWindows(tree.realized.cap.delta.absolute, (c) => c.usd), title: title("Realized Cap Change"), unit: Unit.usd }), name: "Change" }, - { ...rollingPercentRatioTree({ windows: tree.realized.cap.delta.rate, title: title("Realized Cap Growth Rate") }), name: "Growth Rate" }, + { ...sumsTreeBaseline({ windows: mapWindows(tree.realized.cap.delta.absolute, (c) => c.usd), title, metric: "Realized Cap Change", unit: Unit.usd }), name: "Change" }, + { ...rollingPercentRatioTree({ windows: tree.realized.cap.delta.rate, title, metric: "Realized Cap Growth Rate" }), name: "Growth Rate" }, ]; } diff --git a/website/scripts/options/market.js b/website/scripts/options/market.js index 59eafd2f4..cd297c0de 100644 --- a/website/scripts/options/market.js +++ b/website/scripts/options/market.js @@ -46,8 +46,20 @@ import { periodIdToName } from "./utils.js"; */ function indexRatio({ pattern, name, color, defaultActive }) { return [ - line({ series: pattern.percent, name, color, defaultActive, unit: Unit.index }), - line({ series: pattern.ratio, name, color, defaultActive, unit: Unit.ratio }), + line({ + series: pattern.percent, + name, + color, + defaultActive, + unit: Unit.index, + }), + line({ + series: pattern.ratio, + name, + color, + defaultActive, + unit: Unit.ratio, + }), ]; } @@ -530,7 +542,7 @@ export function createMarketSection() { }, ...deltaTree({ delta: supply.marketCap.delta, - title: "Market Cap", + metric: "Market Cap", unit: Unit.usd, extract: (v) => v.usd, }), @@ -553,7 +565,7 @@ export function createMarketSection() { }, ...deltaTree({ delta: cohorts.utxo.all.realized.cap.delta, - title: "Realized Cap", + metric: "Realized Cap", unit: Unit.usd, extract: (v) => v.usd, }), @@ -650,7 +662,11 @@ export function createMarketSection() { title: "RSI Comparison", bottom: [ ...ROLLING_WINDOWS_TO_1M.flatMap((w) => - indexRatio({ pattern: technical.rsi[w.key].rsi, name: w.name, color: w.color }), + indexRatio({ + pattern: technical.rsi[w.key].rsi, + name: w.name, + color: w.color, + }), ), priceLine({ unit: Unit.index, number: 70 }), priceLine({ unit: Unit.index, number: 30 }), @@ -662,9 +678,17 @@ export function createMarketSection() { name: w.name, title: `RSI (${w.title})`, bottom: [ - ...indexRatio({ pattern: rsi.rsi, name: "RSI", color: colors.indicator.main }), + ...indexRatio({ + pattern: rsi.rsi, + name: "RSI", + color: colors.indicator.main, + }), priceLine({ unit: Unit.index, number: 70 }), - priceLine({ unit: Unit.index, number: 50, defaultActive: false }), + priceLine({ + unit: Unit.index, + number: 50, + defaultActive: false, + }), priceLine({ unit: Unit.index, number: 30 }), ], }; @@ -672,17 +696,28 @@ export function createMarketSection() { { name: "Stochastic", tree: ROLLING_WINDOWS_TO_1M.map((w) => { - const rsi = technical.rsi[w.key]; - return { - name: w.name, - title: `Stochastic RSI (${w.title})`, - bottom: [ - ...indexRatio({ pattern: rsi.stochRsiK, name: "K", color: colors.indicator.fast }), - ...indexRatio({ pattern: rsi.stochRsiD, name: "D", color: colors.indicator.slow }), - ...priceLines({ unit: Unit.index, numbers: [80, 20] }), - ], - }; - }), + const rsi = technical.rsi[w.key]; + return { + name: w.name, + title: `Stochastic RSI (${w.title})`, + bottom: [ + ...indexRatio({ + pattern: rsi.stochRsiK, + name: "K", + color: colors.indicator.fast, + }), + ...indexRatio({ + pattern: rsi.stochRsiD, + name: "D", + color: colors.indicator.slow, + }), + ...priceLines({ + unit: Unit.index, + numbers: [80, 20], + }), + ], + }; + }), }, ], }, @@ -693,16 +728,35 @@ export function createMarketSection() { name: "Compare", title: "MACD Comparison", bottom: ROLLING_WINDOWS_TO_1M.map((w) => - line({ series: technical.macd[w.key].line, name: w.name, color: w.color, unit: Unit.usd }), + line({ + series: technical.macd[w.key].line, + name: w.name, + color: w.color, + unit: Unit.usd, + }), ), }, ...ROLLING_WINDOWS_TO_1M.map((w) => ({ name: w.name, title: `MACD (${w.title})`, bottom: [ - line({ series: technical.macd[w.key].line, name: "MACD", color: colors.indicator.fast, unit: Unit.usd }), - line({ series: technical.macd[w.key].signal, name: "Signal", color: colors.indicator.slow, unit: Unit.usd }), - histogram({ series: technical.macd[w.key].histogram, name: "Histogram", unit: Unit.usd }), + line({ + series: technical.macd[w.key].line, + name: "MACD", + color: colors.indicator.fast, + unit: Unit.usd, + }), + line({ + series: technical.macd[w.key].signal, + name: "Signal", + color: colors.indicator.slow, + unit: Unit.usd, + }), + histogram({ + series: technical.macd[w.key].histogram, + name: "Histogram", + unit: Unit.usd, + }), ], })), ], @@ -721,13 +775,25 @@ export function createMarketSection() { name: "Compare", title: "Volatility Index", bottom: ROLLING_WINDOWS.map((w) => - line({ series: volatility[w.key], name: w.name, color: w.color, unit: Unit.percentage }), + line({ + series: volatility[w.key], + name: w.name, + color: w.color, + unit: Unit.percentage, + }), ), }, ...ROLLING_WINDOWS.map((w) => ({ name: w.name, title: `Volatility Index (${w.title})`, - bottom: [line({ series: volatility[w.key], name: w.name, color: w.color, unit: Unit.percentage })], + bottom: [ + line({ + series: volatility[w.key], + name: w.name, + color: w.color, + unit: Unit.percentage, + }), + ], })), ], }, diff --git a/website/scripts/options/mining.js b/website/scripts/options/mining.js index 5de4c54ce..245ed2655 100644 --- a/website/scripts/options/mining.js +++ b/website/scripts/options/mining.js @@ -19,6 +19,7 @@ import { satsBtcUsdFullTree, revenueBtcSatsUsd, revenueRollingBtcSatsUsd, + formatCohortTitle, } from "./shared.js"; import { brk } from "../client.js"; @@ -76,13 +77,17 @@ export function createMiningSection() { includes(ANTPOOL_AND_FRIENDS_IDS, p.id), ); - /** @param {string} title @param {{ _24h: any, _1w: any, _1m: any, _1y: any, percent: any, ratio: any }} dominance */ - const dominanceTree = (title, dominance) => ({ + /** + * @param {(metric: string) => string} title + * @param {string} metric + * @param {{ _24h: any, _1w: any, _1m: any, _1y: any, percent: any, ratio: any }} dominance + */ + const dominanceTree = (title, metric, dominance) => ({ name: "Dominance", tree: [ { name: "Compare", - title, + title: title(metric), bottom: [ ...ROLLING_WINDOWS.flatMap((w) => percentRatio({ pattern: dominance[w.key], name: w.name, color: w.color, defaultActive: w.key !== "_24h" }), @@ -92,12 +97,12 @@ export function createMiningSection() { }, ...ROLLING_WINDOWS.map((w) => ({ name: w.name, - title: `${title} ${w.title}`, + title: title(`${w.name} ${metric}`), bottom: percentRatio({ pattern: dominance[w.key], name: w.name, color: w.color }), })), { name: "All Time", - title: `${title} All Time`, + title: title(`${metric} All Time`), bottom: percentRatio({ pattern: dominance, name: "All Time", color: colors.time.all }), }, ], @@ -107,50 +112,59 @@ export function createMiningSection() { * @param {typeof majorPoolData} poolList */ const createPoolTree = (poolList) => - poolList.map(({ name, pool }) => ({ - name, - tree: [ - dominanceTree(`Dominance: ${name}`, pool.dominance), - { - name: "Blocks Mined", - tree: chartsFromCount({ - pattern: pool.blocksMined, - title: `Blocks Mined: ${name}`, - unit: Unit.count, - }), - }, - { - name: "Rewards", - tree: satsBtcUsdFullTree({ - pattern: pool.rewards, - title: `Rewards: ${name}`, - }), - }, - ], - })); + poolList.map(({ name, pool }) => { + const title = formatCohortTitle(name); + return { + name, + tree: [ + dominanceTree(title, "Dominance", pool.dominance), + { + name: "Blocks Mined", + tree: chartsFromCount({ + pattern: pool.blocksMined, + title, + metric: "Blocks Mined", + unit: Unit.count, + }), + }, + { + name: "Rewards", + tree: satsBtcUsdFullTree({ + pattern: pool.rewards, + title, + metric: "Rewards", + }), + }, + ], + }; + }); /** * @param {typeof minorPoolData} poolList */ const createMinorPoolTree = (poolList) => - poolList.map(({ name, pool }) => ({ - name, - tree: [ - { - name: "Dominance", - title: `Dominance: ${name}`, - bottom: percentRatio({ pattern: pool.dominance, name: "All Time", color: colors.time.all }), - }, - { - name: "Blocks Mined", - tree: chartsFromCount({ - pattern: pool.blocksMined, - title: `Blocks Mined: ${name}`, - unit: Unit.count, - }), - }, - ], - })); + poolList.map(({ name, pool }) => { + const title = formatCohortTitle(name); + return { + name, + tree: [ + { + name: "Dominance", + title: title("Dominance"), + bottom: percentRatio({ pattern: pool.dominance, name: "All Time", color: colors.time.all }), + }, + { + name: "Blocks Mined", + tree: chartsFromCount({ + pattern: pool.blocksMined, + title, + metric: "Blocks Mined", + unit: Unit.count, + }), + }, + ], + }; + }); /** * @param {string} groupTitle @@ -306,14 +320,14 @@ export function createMiningSection() { name: "Coinbase", tree: satsBtcUsdFullTree({ pattern: mining.rewards.coinbase, - title: "Coinbase Rewards", + metric: "Coinbase Rewards", }), }, { name: "Subsidy", tree: satsBtcUsdFullTree({ pattern: mining.rewards.subsidy, - title: "Block Subsidy", + metric: "Block Subsidy", }), }, { @@ -321,7 +335,7 @@ export function createMiningSection() { tree: [ ...satsBtcUsdFullTree({ pattern: mining.rewards.fees, - title: "Transaction Fee Revenue", + metric: "Transaction Fee Revenue", }), { name: "Distributions", diff --git a/website/scripts/options/network.js b/website/scripts/options/network.js index b0274e955..116ca92da 100644 --- a/website/scripts/options/network.js +++ b/website/scripts/options/network.js @@ -53,7 +53,12 @@ export function createNetworkSection() { // Non-addressable script types const nonAddressableTypes = /** @type {const} */ ([ { key: "p2ms", name: "P2MS", color: st.p2ms, defaultActive: false }, - { key: "opReturn", name: "OP_RETURN", color: st.opReturn, defaultActive: true }, + { + key: "opReturn", + name: "OP_RETURN", + color: st.opReturn, + defaultActive: true, + }, { key: "emptyOutput", name: "Empty", @@ -71,7 +76,6 @@ export function createNetworkSection() { // All script types = addressable + non-addressable const scriptTypes = [...addressTypes, ...nonAddressableTypes]; - // Transacting types (transaction participation) const activityTypes = /** @type {const} */ ([ { key: "sending", name: "Sending" }, @@ -138,14 +142,16 @@ export function createNetworkSection() { }, ...simpleDeltaTree({ delta: addrs.delta[key], - title: `${titlePrefix}Address Count`, + title: (s) => `${titlePrefix}${s}`, + metric: "Address Count", unit: Unit.count, }), { name: "New", tree: chartsFromCount({ pattern: addrs.new[key], - title: `${titlePrefix}New Addresses`, + title: (s) => `${titlePrefix}${s}`, + metric: "New Addresses", unit: Unit.count, }), }, @@ -171,7 +177,8 @@ export function createNetworkSection() { name: t.name, tree: averagesArray({ windows: addrs.activity[key][t.key], - title: `${titlePrefix}${t.name} Addresses`, + title: (s) => `${titlePrefix}${s}`, + metric: `${t.name} Addresses`, unit: Unit.count, }), })), @@ -187,7 +194,10 @@ export function createNetworkSection() { { name: "Script Hash", types: [byKey.p2sh, byKey.p2ms] }, { name: "SegWit", types: [byKey.p2wsh, byKey.p2wpkh] }, { name: "Taproot", types: [byKey.p2a, byKey.p2tr] }, - { name: "Other", types: [byKey.opReturn, byKey.emptyOutput, byKey.unknownOutput] }, + { + name: "Other", + types: [byKey.opReturn, byKey.emptyOutput, byKey.unknownOutput], + }, ]; /** @@ -206,7 +216,9 @@ export function createNetworkSection() { title: `${groupName} Output Count ${w.title} Sum`, bottom: types.map((t) => line({ - series: /** @type {CountPattern} */ (scripts.count[t.key]).sum[w.key], + series: /** @type {CountPattern} */ ( + scripts.count[t.key] + ).sum[w.key], name: t.name, color: t.color, unit: Unit.count, @@ -231,7 +243,7 @@ export function createNetworkSection() { name: t.name, tree: chartsFromCount({ pattern: /** @type {CountPattern} */ (scripts.count[t.key]), - title: `${t.name} Output Count`, + metric: `${t.name} Output Count`, unit: Unit.count, }), })), @@ -298,7 +310,7 @@ export function createNetworkSection() { name: "Count", tree: chartsFromFullPerBlock({ pattern: transactions.count.total, - title: "Transaction Count", + metric: "Transaction Count", unit: Unit.count, }), }, @@ -306,14 +318,14 @@ export function createNetworkSection() { name: "Volume", tree: satsBtcUsdFullTree({ pattern: transactions.volume.transferVolume, - title: "Transaction Volume", + metric: "Transaction Volume", }), }, { name: "Fee Rate", tree: chartsFromBlockAnd6b({ pattern: transactions.fees.feeRate, - title: "Transaction Fee Rate", + metric: "Transaction Fee Rate", unit: Unit.feeRate, }), }, @@ -321,7 +333,7 @@ export function createNetworkSection() { name: "Fee", tree: chartsFromBlockAnd6b({ pattern: transactions.fees.fee, - title: "Transaction Fee", + metric: "Transaction Fee", unit: Unit.sats, }), }, @@ -329,7 +341,7 @@ export function createNetworkSection() { name: "Weight", tree: chartsFromBlockAnd6b({ pattern: transactions.size.weight, - title: "Transaction Weight", + metric: "Transaction Weight", unit: Unit.wu, }), }, @@ -337,7 +349,7 @@ export function createNetworkSection() { name: "vSize", tree: chartsFromBlockAnd6b({ pattern: transactions.size.vsize, - title: "Transaction vSize", + metric: "Transaction vSize", unit: Unit.vb, }), }, @@ -345,7 +357,7 @@ export function createNetworkSection() { name: "Versions", tree: chartsFromCountEntries({ entries: entries(transactions.versions), - title: "Transaction Versions", + metric: "Transaction Versions", unit: Unit.count, }), }, @@ -423,7 +435,7 @@ export function createNetworkSection() { name: "Interval", tree: averagesArray({ windows: blocks.interval, - title: "Block Interval", + metric: "Block Interval", unit: Unit.secs, }), }, @@ -431,7 +443,7 @@ export function createNetworkSection() { name: "Size", tree: chartsFromFullPerBlock({ pattern: blocks.size, - title: "Block Size", + metric: "Block Size", unit: Unit.bytes, }), }, @@ -439,7 +451,7 @@ export function createNetworkSection() { name: "Weight", tree: chartsFromFullPerBlock({ pattern: blocks.weight, - title: "Block Weight", + metric: "Block Weight", unit: Unit.wu, }), }, @@ -447,7 +459,7 @@ export function createNetworkSection() { name: "vBytes", tree: chartsFromFullPerBlock({ pattern: blocks.vbytes, - title: "Block vBytes", + metric: "Block vBytes", unit: Unit.vb, }), }, @@ -471,7 +483,7 @@ export function createNetworkSection() { }, ...simpleDeltaTree({ delta: cohorts.utxo.all.outputs.unspentCount.delta, - title: "UTXO Count", + metric: "UTXO Count", unit: Unit.count, }), { @@ -493,7 +505,7 @@ export function createNetworkSection() { cumulative: inputs.count.cumulative, }, ], - title: "UTXO Flow", + metric: "UTXO Flow", unit: Unit.count, }), }, @@ -503,7 +515,7 @@ export function createNetworkSection() { name: "Inputs", tree: chartsFromAggregatedPerBlock({ pattern: inputs.count, - title: "Input Count", + metric: "Input Count", unit: Unit.count, }), }, @@ -511,7 +523,7 @@ export function createNetworkSection() { name: "Outputs", tree: chartsFromAggregatedPerBlock({ pattern: outputs.count.total, - title: "Output Count", + metric: "Output Count", unit: Unit.count, }), }, @@ -588,7 +600,9 @@ export function createNetworkSection() { title: `Output Count by Script Type ${w.title} Sum`, bottom: scriptTypes.map((t) => line({ - series: /** @type {CountPattern} */ (scripts.count[t.key]).sum[w.key], + series: /** @type {CountPattern} */ ( + scripts.count[t.key] + ).sum[w.key], name: t.name, color: t.color, unit: Unit.count, diff --git a/website/scripts/options/series.js b/website/scripts/options/series.js index 0b175ff26..e9fa0d293 100644 --- a/website/scripts/options/series.js +++ b/website/scripts/options/series.js @@ -93,13 +93,48 @@ export function price({ function percentileSeries({ pattern, unit, title = "" }) { const { stat } = colors; return [ - line({ series: pattern.max, name: `${title} max`.trim(), color: stat.max, unit }), - line({ series: pattern.pct90, name: `${title} pct90`.trim(), color: stat.pct90, unit }), - line({ series: pattern.pct75, name: `${title} pct75`.trim(), color: stat.pct75, unit }), - line({ series: pattern.median, name: `${title} median`.trim(), color: stat.median, unit }), - line({ series: pattern.pct25, name: `${title} pct25`.trim(), color: stat.pct25, unit }), - line({ series: pattern.pct10, name: `${title} pct10`.trim(), color: stat.pct10, unit }), - line({ series: pattern.min, name: `${title} min`.trim(), color: stat.min, unit }), + line({ + series: pattern.max, + name: `${title} max`.trim(), + color: stat.max, + unit, + }), + line({ + series: pattern.pct90, + name: `${title} pct90`.trim(), + color: stat.pct90, + unit, + }), + line({ + series: pattern.pct75, + name: `${title} pct75`.trim(), + color: stat.pct75, + unit, + }), + line({ + series: pattern.median, + name: `${title} median`.trim(), + color: stat.median, + unit, + }), + line({ + series: pattern.pct25, + name: `${title} pct25`.trim(), + color: stat.pct25, + unit, + }), + line({ + series: pattern.pct10, + name: `${title} pct10`.trim(), + color: stat.pct10, + unit, + }), + line({ + series: pattern.min, + name: `${title} min`.trim(), + color: stat.min, + unit, + }), ]; } @@ -407,36 +442,6 @@ export function statsAtWindow(pattern, window) { }; } -/** - * Rolling folder tree with line series - * @param {Object} args - * @param {{ _24h: AnySeriesPattern, _1w: AnySeriesPattern, _1m: AnySeriesPattern, _1y: AnySeriesPattern }} args.windows - * @param {string} args.title - * @param {(w: typeof ROLLING_WINDOWS[number]) => string} args.windowTitle - * @param {Unit} args.unit - * @param {string} args.name - * @returns {PartialOptionsGroup} - */ -function rollingWindowsTreeLine({ windows, title, windowTitle, unit, name }) { - return { - name, - tree: [ - { - name: "Compare", - title, - bottom: ROLLING_WINDOWS.map((w) => - line({ series: windows[w.key], name: w.name, color: w.color, unit }), - ), - }, - ...ROLLING_WINDOWS.map((w) => ({ - name: w.name, - title: windowTitle(w), - bottom: [line({ series: windows[w.key], name: w.name, unit })], - })), - ], - }; -} - /** * Rolling folder tree with baseline series * @param {Object} args @@ -447,7 +452,13 @@ function rollingWindowsTreeLine({ windows, title, windowTitle, unit, name }) { * @param {string} args.name * @returns {PartialOptionsGroup} */ -function rollingWindowsTreeBaseline({ windows, title, windowTitle, unit, name }) { +function rollingWindowsTreeBaseline({ + windows, + title, + windowTitle, + unit, + name, +}) { return { name, tree: [ @@ -455,7 +466,12 @@ function rollingWindowsTreeBaseline({ windows, title, windowTitle, unit, name }) name: "Compare", title, bottom: ROLLING_WINDOWS.map((w) => - baseline({ series: windows[w.key], name: w.name, color: w.color, unit }), + baseline({ + series: windows[w.key], + name: w.name, + color: w.color, + unit, + }), ), }, ...ROLLING_WINDOWS.map((w) => ({ @@ -467,24 +483,6 @@ function rollingWindowsTreeBaseline({ windows, title, windowTitle, unit, name }) }; } -/** - * Flat array of rolling sum charts (one per window) - * @param {Object} args - * @param {{ _24h: AnySeriesPattern, _1w: AnySeriesPattern, _1m: AnySeriesPattern, _1y: AnySeriesPattern }} args.windows - * @param {string} args.title - * @param {Unit} args.unit - * @returns {PartialChartOption[]} - */ -export function sumsArray({ windows, title, unit }) { - return ROLLING_WINDOWS.map((w) => ({ - name: w.name, - title: `${title} ${w.title} Sum`, - bottom: [ - line({ series: windows[w.key], name: w.name, color: w.color, unit }), - ], - })); -} - /** * Generic helper: compare + per-window sum+avg + cumulative. * @template P @@ -492,7 +490,8 @@ export function sumsArray({ windows, title, unit }) { * @param {{ _24h: P, _1w: P, _1m: P, _1y: P }} args.sum * @param {{ _24h: P, _1w: P, _1m: P, _1y: P }} args.average * @param {P} args.cumulative - * @param {string} args.title + * @param {(metric: string) => string} [args.title] + * @param {string} args.metric * @param {Color} [args.color] * @param {(args: { pattern: P, name: string, color?: Color, defaultActive?: boolean }) => AnyFetchedSeriesBlueprint[]} args.series * @returns {PartialChartOption[]} @@ -501,14 +500,15 @@ export function sumsAndAveragesCumulativeWith({ sum, average, cumulative, - title, + title = (s) => s, + metric, color, series, }) { return [ { name: "Compare", - title: `${title} Averages`, + title: title(metric), bottom: ROLLING_WINDOWS.flatMap((w) => series({ pattern: average[w.key], @@ -519,7 +519,7 @@ export function sumsAndAveragesCumulativeWith({ }, ...ROLLING_WINDOWS.map((w) => ({ name: w.name, - title: `${title} ${w.title}`, + title: title(`${w.name} ${metric}`), bottom: [ ...series({ pattern: sum[w.key], name: "Sum", color: w.color }), ...series({ @@ -532,7 +532,7 @@ export function sumsAndAveragesCumulativeWith({ })), { name: "Cumulative", - title: `${title} (Total)`, + title: title(`Cumulative ${metric}`), bottom: series({ pattern: cumulative, name: "all-time", color }), }, ]; @@ -543,14 +543,15 @@ export function sumsAndAveragesCumulativeWith({ * @param {Object} args * @param {{ _24h: AnySeriesPattern, _1w: AnySeriesPattern, _1m: AnySeriesPattern, _1y: AnySeriesPattern }} args.sum * @param {{ _24h: AnySeriesPattern, _1w: AnySeriesPattern, _1m: AnySeriesPattern, _1y: AnySeriesPattern }} args.average - * @param {string} args.title + * @param {(metric: string) => string} [args.title] + * @param {string} args.metric * @param {Unit} args.unit * @returns {PartialChartOption[]} */ -export function sumsAndAveragesArray({ sum, average, title, unit }) { +export function sumsAndAveragesArray({ sum, average, title = (s) => s, metric, unit }) { return ROLLING_WINDOWS.map((w) => ({ name: w.name, - title: `${title} ${w.title}`, + title: title(`${w.name} ${metric}`), bottom: [ line({ series: sum[w.key], name: "Sum", color: w.color, unit }), line({ @@ -570,17 +571,27 @@ export function sumsAndAveragesArray({ sum, average, title, unit }) { * @param {{ _24h: AnySeriesPattern, _1w: AnySeriesPattern, _1m: AnySeriesPattern, _1y: AnySeriesPattern }} args.sum * @param {{ _24h: AnySeriesPattern, _1w: AnySeriesPattern, _1m: AnySeriesPattern, _1y: AnySeriesPattern }} args.average * @param {AnySeriesPattern} args.cumulative - * @param {string} args.title + * @param {(metric: string) => string} [args.title] + * @param {string} args.metric * @param {Unit} args.unit * @param {Color} [args.color] * @returns {PartialChartOption[]} */ -export function sumsAndAveragesCumulative({ sum, average, cumulative, title, unit, color }) { +export function sumsAndAveragesCumulative({ + sum, + average, + cumulative, + title, + metric, + unit, + color, +}) { return sumsAndAveragesCumulativeWith({ sum, average, cumulative, title, + metric, color, series: ({ pattern, name, color, defaultActive }) => [ line({ series: pattern, name, color, unit, defaultActive }), @@ -589,35 +600,18 @@ export function sumsAndAveragesCumulative({ sum, average, cumulative, title, uni } /** - * Rolling sums tree (Compare + individual windows in a folder) * @param {Object} args * @param {{ _24h: AnySeriesPattern, _1w: AnySeriesPattern, _1m: AnySeriesPattern, _1y: AnySeriesPattern }} args.windows - * @param {string} args.title + * @param {(metric: string) => string} [args.title] + * @param {string} args.metric * @param {Unit} args.unit * @returns {PartialOptionsGroup} */ -export function sumsTree({ windows, title, unit }) { - return rollingWindowsTreeLine({ - windows, - title, - windowTitle: (w) => `${title} ${w.title} Sum`, - unit, - name: "Sums", - }); -} - -/** - * @param {Object} args - * @param {{ _24h: AnySeriesPattern, _1w: AnySeriesPattern, _1m: AnySeriesPattern, _1y: AnySeriesPattern }} args.windows - * @param {string} args.title - * @param {Unit} args.unit - * @returns {PartialOptionsGroup} - */ -export function sumsTreeBaseline({ windows, title, unit }) { +export function sumsTreeBaseline({ windows, title = (s) => s, metric, unit }) { return rollingWindowsTreeBaseline({ windows, - title, - windowTitle: (w) => `${title} ${w.title} Sum`, + title: title(metric), + windowTitle: (w) => title(`${w.name} ${metric}`), unit, name: "Sums", }); @@ -627,22 +621,23 @@ export function sumsTreeBaseline({ windows, title, unit }) { * Flat array of per-window average charts * @param {Object} args * @param {{ _24h: AnySeriesPattern, _1w: AnySeriesPattern, _1m: AnySeriesPattern, _1y: AnySeriesPattern }} args.windows - * @param {string} args.title + * @param {(metric: string) => string} [args.title] + * @param {string} args.metric * @param {Unit} args.unit * @returns {PartialChartOption[]} */ -export function averagesArray({ windows, title, unit }) { +export function averagesArray({ windows, title = (s) => s, metric, unit }) { return [ { name: "Compare", - title: `${title} Averages`, + title: title(metric), bottom: ROLLING_WINDOWS.map((w) => line({ series: windows[w.key], name: w.name, color: w.color, unit }), ), }, ...ROLLING_WINDOWS.map((w) => ({ name: w.name, - title: `${title} ${w.title} Average`, + title: title(`${w.name} ${metric}`), bottom: [ line({ series: windows[w.key], name: w.name, color: w.color, unit }), ], @@ -655,17 +650,18 @@ export function averagesArray({ windows, title, unit }) { * @param {Object} args * @param {Record} args.pattern - Pattern with pct10/pct25/... and average/median/... as _1y24h30d7dPattern * @param {AnySeriesPattern} [args.base] - Optional base series to show as dots on each chart - * @param {string} args.title + * @param {(metric: string) => string} [args.title] + * @param {string} args.metric * @param {Unit} args.unit * @returns {PartialOptionsGroup} */ -export function distributionWindowsTree({ pattern, base, title, unit }) { +export function distributionWindowsTree({ pattern, base, title = (s) => s, metric, unit }) { return { name: "Distribution", tree: [ { name: "Compare", - title: `${title} Median`, + title: title(`${metric} Distribution`), bottom: ROLLING_WINDOWS.map((w) => line({ series: pattern.median[w.key], @@ -677,7 +673,7 @@ export function distributionWindowsTree({ pattern, base, title, unit }) { }, ...ROLLING_WINDOWS.map((w) => ({ name: w.name, - title: `${title} Distribution (${w.title})`, + title: title(`${w.name} ${metric} Distribution`), bottom: [ ...(base ? [line({ series: base, name: "base", unit })] : []), ...percentileSeries({ pattern: statsAtWindow(pattern, w.key), unit }), @@ -847,24 +843,34 @@ export function percentRatioBaseline({ pattern, name, color, defaultActive }) { * Rolling folder tree with percentRatio series (colored in compare, plain in individual) * @param {Object} args * @param {{ _24h: { percent: AnySeriesPattern, ratio: AnySeriesPattern }, _1w: { percent: AnySeriesPattern, ratio: AnySeriesPattern }, _1m: { percent: AnySeriesPattern, ratio: AnySeriesPattern }, _1y: { percent: AnySeriesPattern, ratio: AnySeriesPattern } }} args.windows - * @param {string} args.title + * @param {(metric: string) => string} [args.title] + * @param {string} args.metric * @param {string} [args.name] * @returns {PartialOptionsGroup} */ -export function rollingPercentRatioTree({ windows, title, name = "Sums" }) { +export function rollingPercentRatioTree({ + windows, + title = (s) => s, + metric, + name = "Sums", +}) { return { name, tree: [ { name: "Compare", - title: `${title} Rolling`, + title: title(metric), bottom: ROLLING_WINDOWS.flatMap((w) => - percentRatio({ pattern: windows[w.key], name: w.name, color: w.color }), + percentRatio({ + pattern: windows[w.key], + name: w.name, + color: w.color, + }), ), }, ...ROLLING_WINDOWS.map((w) => ({ name: w.name, - title: `${title} (${w.title})`, + title: title(`${w.name} ${metric}`), bottom: percentRatioBaseline({ pattern: windows[w.key], name: w.name }), })), ], @@ -876,19 +882,20 @@ export function rollingPercentRatioTree({ windows, title, name = "Sums" }) { * @template T * @param {Object} args * @param {{ absolute: { _24h: T, _1w: T, _1m: T, _1y: T }, rate: { _24h: { percent: AnySeriesPattern, ratio: AnySeriesPattern }, _1w: { percent: AnySeriesPattern, ratio: AnySeriesPattern }, _1m: { percent: AnySeriesPattern, ratio: AnySeriesPattern }, _1y: { percent: AnySeriesPattern, ratio: AnySeriesPattern } } }} args.delta - * @param {string} args.title + * @param {(metric: string) => string} [args.title] + * @param {string} args.metric * @param {Unit} args.unit * @param {(v: T) => AnySeriesPattern} args.extract * @returns {PartialOptionsTree} */ -export function deltaTree({ delta, title, unit, extract }) { +export function deltaTree({ delta, title = (s) => s, metric, unit, extract }) { return [ { name: "Change", tree: [ { name: "Compare", - title: `${title} Change`, + title: title(`${metric} Change`), bottom: ROLLING_WINDOWS.map((w) => baseline({ series: extract(delta.absolute[w.key]), @@ -900,7 +907,7 @@ export function deltaTree({ delta, title, unit, extract }) { }, ...ROLLING_WINDOWS.map((w) => ({ name: w.name, - title: `${title} Change (${w.title})`, + title: title(`${w.name} ${metric} Change`), bottom: [ baseline({ series: extract(delta.absolute[w.key]), @@ -913,7 +920,8 @@ export function deltaTree({ delta, title, unit, extract }) { }, rollingPercentRatioTree({ windows: delta.rate, - title: `${title} Growth Rate`, + title, + metric: `${metric} Growth Rate`, name: "Growth Rate", }), ]; @@ -923,12 +931,13 @@ export function deltaTree({ delta, title, unit, extract }) { * deltaTree where absolute windows are directly AnySeriesPattern (no extract needed) * @param {Object} args * @param {{ absolute: { _24h: AnySeriesPattern, _1w: AnySeriesPattern, _1m: AnySeriesPattern, _1y: AnySeriesPattern }, rate: { _24h: { percent: AnySeriesPattern, ratio: AnySeriesPattern }, _1w: { percent: AnySeriesPattern, ratio: AnySeriesPattern }, _1m: { percent: AnySeriesPattern, ratio: AnySeriesPattern }, _1y: { percent: AnySeriesPattern, ratio: AnySeriesPattern } } }} args.delta - * @param {string} args.title + * @param {(metric: string) => string} [args.title] + * @param {string} args.metric * @param {Unit} args.unit * @returns {PartialOptionsTree} */ -export function simpleDeltaTree({ delta, title, unit }) { - return deltaTree({ delta, title, unit, extract: (v) => v }); +export function simpleDeltaTree({ delta, title = (s) => s, metric, unit }) { + return deltaTree({ delta, title, metric, unit, extract: (v) => v }); } // ============================================================================ @@ -941,29 +950,32 @@ export function simpleDeltaTree({ delta, title, unit }) { * Pattern has: .height, .cumulative, .sum (windowed), .average/.pct10/... (windowed, flat) * @param {Object} args * @param {FullPerBlockPattern} args.pattern - * @param {string} args.title + * @param {(metric: string) => string} [args.title] + * @param {string} args.metric * @param {Unit} args.unit * @param {string} [args.distributionSuffix] * @returns {PartialOptionsTree} */ export function chartsFromFull({ pattern, - title, + title = (s) => s, + metric, unit, distributionSuffix = "", }) { - const distTitle = distributionSuffix - ? `${title} ${distributionSuffix}` - : title; + const distMetric = distributionSuffix + ? `${metric} ${distributionSuffix}` + : metric; return [ ...sumsAndAveragesCumulative({ sum: pattern.sum, average: pattern.average, cumulative: pattern.cumulative, title, + metric, unit, }), - distributionWindowsTree({ pattern, title: distTitle, unit }), + distributionWindowsTree({ pattern, title, metric: distMetric, unit }), ]; } @@ -971,7 +983,8 @@ export function chartsFromFull({ * Split pattern into 4 charts with "per Block" in distribution title * @param {Object} args * @param {FullPerBlockPattern} args.pattern - * @param {string} args.title + * @param {(metric: string) => string} [args.title] + * @param {string} args.metric * @param {Unit} args.unit * @returns {PartialOptionsTree} */ @@ -982,31 +995,35 @@ export const chartsFromFullPerBlock = (args) => * Split pattern with sum + distribution + cumulative into 3 charts (no base) * @param {Object} args * @param {AggregatedPattern} args.pattern - * @param {string} args.title + * @param {(metric: string) => string} [args.title] + * @param {string} args.metric * @param {Unit} args.unit * @param {string} [args.distributionSuffix] * @returns {PartialOptionsTree} */ export function chartsFromAggregated({ pattern, - title, + title = (s) => s, + metric, unit, distributionSuffix = "", }) { - const distTitle = distributionSuffix - ? `${title} ${distributionSuffix}` - : title; + const distMetric = distributionSuffix + ? `${metric} ${distributionSuffix}` + : metric; return [ ...sumsAndAveragesCumulative({ sum: pattern.rolling.sum, average: pattern.rolling.average, cumulative: pattern.cumulative, title, + metric, unit, }), distributionWindowsTree({ pattern: pattern.rolling, - title: distTitle, + title, + metric: distMetric, unit, }), ]; @@ -1016,7 +1033,8 @@ export function chartsFromAggregated({ * Split pattern into 3 charts with "per Block" in distribution title (no base) * @param {Object} args * @param {AggregatedPattern} args.pattern - * @param {string} args.title + * @param {(metric: string) => string} [args.title] + * @param {string} args.metric * @param {Unit} args.unit * @returns {PartialOptionsTree} */ @@ -1027,20 +1045,21 @@ export const chartsFromAggregatedPerBlock = (args) => * Create Per Block + Per 6 Blocks stats charts from a _6bBlockTxPattern * @param {Object} args * @param {{ block: DistributionStats, _6b: DistributionStats }} args.pattern - * @param {string} args.title + * @param {(metric: string) => string} [args.title] + * @param {string} args.metric * @param {Unit} args.unit * @returns {PartialOptionsTree} */ -export function chartsFromBlockAnd6b({ pattern, title, unit }) { +export function chartsFromBlockAnd6b({ pattern, title = (s) => s, metric, unit }) { return [ { name: "Block", - title: `${title} (Block)`, + title: title(`${metric} (Block)`), bottom: percentileSeries({ pattern: pattern.block, unit }), }, { - name: "Hourly", - title: `${title} (Hourly)`, + name: "~Hourly", + title: title(`${metric} (~Hourly)`), bottom: percentileSeries({ pattern: pattern._6b, unit }), }, ]; @@ -1050,17 +1069,19 @@ export function chartsFromBlockAnd6b({ pattern, title, unit }) { * Averages + Sums + Cumulative charts * @param {Object} args * @param {CountPattern} args.pattern - * @param {string} args.title + * @param {(metric: string) => string} [args.title] + * @param {string} args.metric * @param {Unit} args.unit * @param {Color} [args.color] * @returns {PartialOptionsTree} */ -export function chartsFromCount({ pattern, title, unit, color }) { +export function chartsFromCount({ pattern, title = (s) => s, metric, unit, color }) { return sumsAndAveragesCumulative({ sum: pattern.sum, average: pattern.average, cumulative: pattern.cumulative, title, + metric, unit, color, }); @@ -1070,19 +1091,12 @@ export function chartsFromCount({ pattern, title, unit, color }) { * Windowed sums + cumulative for multiple named entries (e.g. transaction versions) * @param {Object} args * @param {Array<[string, CountPattern]>} args.entries - * @param {string} args.title + * @param {(metric: string) => string} [args.title] + * @param {string} args.metric * @param {Unit} args.unit * @returns {PartialOptionsTree} */ -/** - * Windowed sums + cumulative for multiple named entries (e.g. transaction versions) - * @param {Object} args - * @param {Array<[string, CountPattern]>} args.entries - * @param {string} args.title - * @param {Unit} args.unit - * @returns {PartialOptionsTree} - */ -export function chartsFromCountEntries({ entries, title, unit }) { +export function chartsFromCountEntries({ entries, title = (s) => s, metric, unit }) { const items = entries.map(([name, data], i, arr) => ({ name, color: colors.at(i, arr.length), @@ -1092,14 +1106,14 @@ export function chartsFromCountEntries({ entries, title, unit }) { return [ ...ROLLING_WINDOWS.map((w) => ({ name: w.name, - title: `${title} ${w.title} Sum`, + title: title(`${w.name} ${metric}`), bottom: items.map((e) => line({ series: e.sum[w.key], name: e.name, color: e.color, unit }), ), })), { name: "Cumulative", - title: `${title} (Total)`, + title: title(`Cumulative ${metric}`), bottom: items.map((e) => line({ series: e.cumulative, name: e.name, color: e.color, unit }), ), @@ -1111,26 +1125,26 @@ export function chartsFromCountEntries({ entries, title, unit }) { * Windowed averages + sums + cumulative for multiple named series (e.g. UTXO flow) * @param {Object} args * @param {Array<{ name: string, color: Color, average: { _24h: AnySeriesPattern, _1w: AnySeriesPattern, _1m: AnySeriesPattern, _1y: AnySeriesPattern }, sum: { _24h: AnySeriesPattern, _1w: AnySeriesPattern, _1m: AnySeriesPattern, _1y: AnySeriesPattern }, cumulative: AnySeriesPattern }>} args.entries - * @param {string} args.title + * @param {(metric: string) => string} [args.title] + * @param {string} args.metric * @param {Unit} args.unit * @returns {PartialOptionsTree} */ -export function multiSeriesTree({ entries, title, unit }) { +export function multiSeriesTree({ entries, title = (s) => s, metric, unit }) { return [ ...ROLLING_WINDOWS.map((w) => ({ name: w.name, - title: `${title} ${w.title} Averages`, + title: title(`${w.name} ${metric}`), bottom: entries.map((e) => line({ series: e.average[w.key], name: e.name, color: e.color, unit }), ), })), { name: "Cumulative", - title: `${title} (Total)`, + title: title(`Cumulative ${metric}`), bottom: entries.map((e) => line({ series: e.cumulative, name: e.name, color: e.color, unit }), ), }, ]; } - diff --git a/website/scripts/options/shared.js b/website/scripts/options/shared.js index 4a54c4cea..d5cd7b4fa 100644 --- a/website/scripts/options/shared.js +++ b/website/scripts/options/shared.js @@ -219,16 +219,18 @@ export function satsBtcUsdRolling({ pattern, name, color, defaultActive }) { * Build a full Sum / Rolling / Cumulative tree from a FullValuePattern * @param {Object} args * @param {FullValuePattern} args.pattern - * @param {string} args.title + * @param {(metric: string) => string} [args.title] + * @param {string} args.metric * @param {Color} [args.color] * @returns {PartialOptionsTree} */ -export function satsBtcUsdFullTree({ pattern, title, color }) { +export function satsBtcUsdFullTree({ pattern, title, metric, color }) { return sumsAndAveragesCumulativeWith({ sum: pattern.sum, average: pattern.average, cumulative: pattern.cumulative, title, + metric, color, series: ({ pattern, name, color, defaultActive }) => satsBtcUsd({ pattern, name, color, defaultActive }),