global: snapshot part 17

This commit is contained in:
nym21
2026-03-21 19:41:41 +01:00
parent 2991562234
commit 8859de5393
35 changed files with 567 additions and 360 deletions

View File

@@ -5,10 +5,10 @@ use brk_cohort::{
}; };
use brk_error::Result; use brk_error::Result;
use brk_traversable::Traversable; use brk_traversable::Traversable;
use brk_types::{Height, Indexes, Version}; use brk_types::{Height, Indexes, StoredU64, Version};
use derive_more::{Deref, DerefMut}; use derive_more::{Deref, DerefMut};
use rayon::prelude::*; 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}; use crate::{distribution::DynCohortVecs, indexes, internal::CachedWindowStarts, prices};
@@ -106,11 +106,12 @@ impl AddrCohorts {
&mut self, &mut self,
prices: &prices::Vecs, prices: &prices::Vecs,
starting_indexes: &Indexes, starting_indexes: &Indexes,
all_utxo_count: &impl ReadableVec<Height, StoredU64>,
exit: &Exit, exit: &Exit,
) -> Result<()> { ) -> Result<()> {
self.0 self.0
.par_iter_mut() .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. /// Returns a parallel iterator over all vecs for parallel writing.

View File

@@ -232,9 +232,10 @@ impl CohortVecs for AddrCohortVecs {
&mut self, &mut self,
prices: &prices::Vecs, prices: &prices::Vecs,
starting_indexes: &Indexes, starting_indexes: &Indexes,
all_utxo_count: &impl ReadableVec<Height, StoredU64>,
exit: &Exit, exit: &Exit,
) -> Result<()> { ) -> Result<()> {
self.metrics self.metrics
.compute_rest_part2(prices, starting_indexes, exit) .compute_rest_part2(prices, starting_indexes, all_utxo_count, exit)
} }
} }

View File

@@ -1,6 +1,6 @@
use brk_error::Result; use brk_error::Result;
use brk_types::{Cents, Height, Indexes, Version}; use brk_types::{Cents, Height, Indexes, StoredU64, Version};
use vecdb::Exit; use vecdb::{Exit, ReadableVec};
use crate::prices; use crate::prices;
@@ -62,6 +62,7 @@ pub trait CohortVecs: DynCohortVecs {
&mut self, &mut self,
prices: &prices::Vecs, prices: &prices::Vecs,
starting_indexes: &Indexes, starting_indexes: &Indexes,
all_utxo_count: &impl ReadableVec<Height, StoredU64>,
exit: &Exit, exit: &Exit,
) -> Result<()>; ) -> Result<()>;
} }

View File

@@ -565,8 +565,9 @@ impl UTXOCohorts<Rw> {
exit, 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_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. // Destructure to allow parallel mutable access to independent fields.
let Self { let Self {
@@ -589,6 +590,7 @@ impl UTXOCohorts<Rw> {
let vc = &under_1h_value_created; let vc = &under_1h_value_created;
let vd = &under_1h_value_destroyed; let vd = &under_1h_value_destroyed;
let ss = &all_supply_sats; let ss = &all_supply_sats;
let au = &all_utxo_count;
let tasks: Vec<Box<dyn FnOnce() -> Result<()> + Send + '_>> = vec![ let tasks: Vec<Box<dyn FnOnce() -> Result<()> + Send + '_>> = vec![
Box::new(|| { Box::new(|| {
@@ -600,6 +602,7 @@ impl UTXOCohorts<Rw> {
vc, vc,
vd, vd,
ss, ss,
au,
exit, exit,
) )
}), }),
@@ -610,58 +613,59 @@ impl UTXOCohorts<Rw> {
starting_indexes, starting_indexes,
height_to_market_cap, height_to_market_cap,
ss, ss,
au,
exit, exit,
) )
}), }),
Box::new(|| { Box::new(|| {
age_range.par_iter_mut().try_for_each(|v| { age_range.par_iter_mut().try_for_each(|v| {
v.metrics v.metrics
.compute_rest_part2(prices, starting_indexes, ss, exit) .compute_rest_part2(prices, starting_indexes, ss, au, exit)
}) })
}), }),
Box::new(|| { Box::new(|| {
under_age.par_iter_mut().try_for_each(|v| { under_age.par_iter_mut().try_for_each(|v| {
v.metrics v.metrics
.compute_rest_part2(prices, starting_indexes, ss, exit) .compute_rest_part2(prices, starting_indexes, ss, au, exit)
}) })
}), }),
Box::new(|| { Box::new(|| {
over_age.par_iter_mut().try_for_each(|v| { over_age.par_iter_mut().try_for_each(|v| {
v.metrics v.metrics
.compute_rest_part2(prices, starting_indexes, ss, exit) .compute_rest_part2(prices, starting_indexes, ss, au, exit)
}) })
}), }),
Box::new(|| { Box::new(|| {
over_amount over_amount
.par_iter_mut() .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(|| { Box::new(|| {
epoch.par_iter_mut().try_for_each(|v| { epoch.par_iter_mut().try_for_each(|v| {
v.metrics v.metrics
.compute_rest_part2(prices, starting_indexes, ss, exit) .compute_rest_part2(prices, starting_indexes, ss, au, exit)
}) })
}), }),
Box::new(|| { Box::new(|| {
class.par_iter_mut().try_for_each(|v| { class.par_iter_mut().try_for_each(|v| {
v.metrics v.metrics
.compute_rest_part2(prices, starting_indexes, ss, exit) .compute_rest_part2(prices, starting_indexes, ss, au, exit)
}) })
}), }),
Box::new(|| { Box::new(|| {
amount_range amount_range
.par_iter_mut() .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(|| { Box::new(|| {
under_amount under_amount
.par_iter_mut() .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(|| { Box::new(|| {
type_ type_
.par_iter_mut() .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))
}), }),
]; ];

View File

@@ -5,7 +5,7 @@ use brk_types::{
Cents, Dollars, Height, Indexes, Version, Cents, Dollars, Height, Indexes, Version,
}; };
use vecdb::AnyStoredVec; use vecdb::AnyStoredVec;
use vecdb::{Exit, ReadableVec, Rw, StorageMode}; use vecdb::{Exit, ReadOnlyClone, ReadableVec, Rw, StorageMode};
use crate::{ use crate::{
blocks, blocks,
@@ -135,6 +135,13 @@ impl AllCohortMetrics {
exit, 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( self.cost_basis.compute_prices(
starting_indexes, starting_indexes,
&prices.spot.cents.height, &prices.spot.cents.height,

View File

@@ -1,7 +1,7 @@
use brk_cohort::Filter; use brk_cohort::Filter;
use brk_error::Result; use brk_error::Result;
use brk_traversable::Traversable; 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 vecdb::{AnyStoredVec, Exit, ReadableVec, Rw, StorageMode};
use crate::{ use crate::{
@@ -69,6 +69,7 @@ impl BasicCohortMetrics {
prices: &prices::Vecs, prices: &prices::Vecs,
starting_indexes: &Indexes, starting_indexes: &Indexes,
all_supply_sats: &impl ReadableVec<Height, Sats>, all_supply_sats: &impl ReadableVec<Height, Sats>,
all_utxo_count: &impl ReadableVec<Height, StoredU64>,
exit: &Exit, exit: &Exit,
) -> Result<()> { ) -> Result<()> {
self.realized.compute_rest_part2( self.realized.compute_rest_part2(
@@ -93,6 +94,8 @@ impl BasicCohortMetrics {
exit, exit,
)?; )?;
self.outputs.compute_part2(starting_indexes.height, all_utxo_count, exit)?;
Ok(()) Ok(())
} }

View File

@@ -1,7 +1,7 @@
use brk_cohort::Filter; use brk_cohort::Filter;
use brk_error::Result; use brk_error::Result;
use brk_traversable::Traversable; 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 vecdb::{AnyStoredVec, Exit, ReadableVec, Rw, StorageMode};
use crate::{ use crate::{
@@ -108,6 +108,8 @@ impl CoreCohortMetrics {
self.supply self.supply
.compute(prices, starting_indexes.height, exit)?; .compute(prices, starting_indexes.height, exit)?;
self.outputs.compute_rest(starting_indexes.height, exit)?;
self.activity self.activity
.compute_rest_part1(prices, starting_indexes, exit)?; .compute_rest_part1(prices, starting_indexes, exit)?;
@@ -124,6 +126,7 @@ impl CoreCohortMetrics {
prices: &prices::Vecs, prices: &prices::Vecs,
starting_indexes: &Indexes, starting_indexes: &Indexes,
all_supply_sats: &impl ReadableVec<Height, Sats>, all_supply_sats: &impl ReadableVec<Height, Sats>,
all_utxo_count: &impl ReadableVec<Height, StoredU64>,
exit: &Exit, exit: &Exit,
) -> Result<()> { ) -> Result<()> {
self.realized.compute_rest_part2( self.realized.compute_rest_part2(
@@ -148,6 +151,8 @@ impl CoreCohortMetrics {
exit, exit,
)?; )?;
self.outputs.compute_part2(starting_indexes.height, all_utxo_count, exit)?;
Ok(()) Ok(())
} }
} }

View File

@@ -2,7 +2,7 @@ use brk_cohort::Filter;
use brk_error::Result; use brk_error::Result;
use brk_traversable::Traversable; use brk_traversable::Traversable;
use brk_types::{ use brk_types::{
Dollars, Height, Indexes, Sats, Version, Dollars, Height, Indexes, Sats, StoredU64, Version,
}; };
use vecdb::AnyStoredVec; use vecdb::AnyStoredVec;
use vecdb::{Exit, ReadableVec, Rw, StorageMode}; use vecdb::{Exit, ReadableVec, Rw, StorageMode};
@@ -94,6 +94,7 @@ impl ExtendedCohortMetrics {
starting_indexes: &Indexes, starting_indexes: &Indexes,
height_to_market_cap: &impl ReadableVec<Height, Dollars>, height_to_market_cap: &impl ReadableVec<Height, Dollars>,
all_supply_sats: &impl ReadableVec<Height, Sats>, all_supply_sats: &impl ReadableVec<Height, Sats>,
all_utxo_count: &impl ReadableVec<Height, StoredU64>,
exit: &Exit, exit: &Exit,
) -> Result<()> { ) -> Result<()> {
self.realized.compute_rest_part2( self.realized.compute_rest_part2(
@@ -141,6 +142,8 @@ impl ExtendedCohortMetrics {
exit, exit,
)?; )?;
self.outputs.compute_part2(starting_indexes.height, all_utxo_count, exit)?;
Ok(()) Ok(())
} }

View File

@@ -1,6 +1,6 @@
use brk_error::Result; use brk_error::Result;
use brk_traversable::Traversable; 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 derive_more::{Deref, DerefMut};
use vecdb::{AnyStoredVec, Exit, ReadableVec, Rw, StorageMode}; use vecdb::{AnyStoredVec, Exit, ReadableVec, Rw, StorageMode};
@@ -67,6 +67,7 @@ impl ExtendedAdjustedCohortMetrics {
under_1h_value_created: &impl ReadableVec<Height, Cents>, under_1h_value_created: &impl ReadableVec<Height, Cents>,
under_1h_value_destroyed: &impl ReadableVec<Height, Cents>, under_1h_value_destroyed: &impl ReadableVec<Height, Cents>,
all_supply_sats: &impl ReadableVec<Height, Sats>, all_supply_sats: &impl ReadableVec<Height, Sats>,
all_utxo_count: &impl ReadableVec<Height, StoredU64>,
exit: &Exit, exit: &Exit,
) -> Result<()> { ) -> Result<()> {
self.inner.compute_rest_part2( self.inner.compute_rest_part2(
@@ -75,6 +76,7 @@ impl ExtendedAdjustedCohortMetrics {
starting_indexes, starting_indexes,
height_to_market_cap, height_to_market_cap,
all_supply_sats, all_supply_sats,
all_utxo_count,
exit, exit,
)?; )?;

View File

@@ -1,8 +1,8 @@
use brk_cohort::Filter; use brk_cohort::Filter;
use brk_error::Result; use brk_error::Result;
use brk_traversable::Traversable; use brk_traversable::Traversable;
use brk_types::Indexes; use brk_types::{Height, Indexes, StoredU64};
use vecdb::{AnyStoredVec, Exit, Rw, StorageMode}; use vecdb::{AnyStoredVec, Exit, ReadableVec, Rw, StorageMode};
use crate::{ use crate::{
distribution::metrics::{ distribution::metrics::{
@@ -101,6 +101,7 @@ impl MinimalCohortMetrics {
exit: &Exit, exit: &Exit,
) -> Result<()> { ) -> Result<()> {
self.supply.compute(prices, starting_indexes.height, exit)?; self.supply.compute(prices, starting_indexes.height, exit)?;
self.outputs.compute_rest(starting_indexes.height, exit)?;
self.activity self.activity
.compute_rest_part1(prices, starting_indexes, exit)?; .compute_rest_part1(prices, starting_indexes, exit)?;
self.realized self.realized
@@ -112,6 +113,7 @@ impl MinimalCohortMetrics {
&mut self, &mut self,
prices: &prices::Vecs, prices: &prices::Vecs,
starting_indexes: &Indexes, starting_indexes: &Indexes,
all_utxo_count: &impl ReadableVec<Height, StoredU64>,
exit: &Exit, exit: &Exit,
) -> Result<()> { ) -> Result<()> {
self.realized.compute_rest_part2( self.realized.compute_rest_part2(
@@ -128,6 +130,8 @@ impl MinimalCohortMetrics {
exit, exit,
)?; )?;
self.outputs.compute_part2(starting_indexes.height, all_utxo_count, exit)?;
Ok(()) Ok(())
} }
} }

View File

@@ -1,8 +1,8 @@
use brk_cohort::Filter; use brk_cohort::Filter;
use brk_error::Result; use brk_error::Result;
use brk_traversable::Traversable; use brk_traversable::Traversable;
use brk_types::Indexes; use brk_types::{Height, Indexes, StoredU64};
use vecdb::{AnyStoredVec, Exit, Rw, StorageMode}; use vecdb::{AnyStoredVec, Exit, ReadableVec, Rw, StorageMode};
use crate::{ use crate::{
distribution::metrics::{ distribution::metrics::{
@@ -63,6 +63,7 @@ impl TypeCohortMetrics {
exit: &Exit, exit: &Exit,
) -> Result<()> { ) -> Result<()> {
self.supply.compute(prices, starting_indexes.height, exit)?; self.supply.compute(prices, starting_indexes.height, exit)?;
self.outputs.compute_rest(starting_indexes.height, exit)?;
self.activity self.activity
.compute_rest_part1(prices, starting_indexes, exit)?; .compute_rest_part1(prices, starting_indexes, exit)?;
self.realized self.realized
@@ -74,6 +75,7 @@ impl TypeCohortMetrics {
&mut self, &mut self,
prices: &prices::Vecs, prices: &prices::Vecs,
starting_indexes: &Indexes, starting_indexes: &Indexes,
all_utxo_count: &impl ReadableVec<Height, StoredU64>,
exit: &Exit, exit: &Exit,
) -> Result<()> { ) -> Result<()> {
self.realized.compute_rest_part2( self.realized.compute_rest_part2(
@@ -90,6 +92,8 @@ impl TypeCohortMetrics {
exit, exit,
)?; )?;
self.outputs.compute_part2(starting_indexes.height, all_utxo_count, exit)?;
Ok(()) Ok(())
} }
} }

View File

@@ -269,6 +269,8 @@ pub trait CohortMetricsBase:
) -> Result<()> { ) -> Result<()> {
self.supply_mut() self.supply_mut()
.compute(prices, starting_indexes.height, exit)?; .compute(prices, starting_indexes.height, exit)?;
self.outputs_mut()
.compute_rest(starting_indexes.height, exit)?;
self.activity_mut() self.activity_mut()
.compute_rest_part1(prices, starting_indexes, exit)?; .compute_rest_part1(prices, starting_indexes, exit)?;

View File

@@ -1,38 +1,44 @@
use brk_error::Result; use brk_error::Result;
use brk_traversable::Traversable; use brk_traversable::Traversable;
use brk_types::{BasisPointsSigned32, Indexes, StoredI64, StoredU64, Version}; use brk_types::{BasisPointsSigned32, Height, Indexes, StoredF32, StoredI64, StoredU32, StoredU64, Version};
use vecdb::{AnyStoredVec, AnyVec, Exit, Rw, StorageMode, WritableVec}; use vecdb::{AnyStoredVec, AnyVec, Exit, ReadableVec, Rw, StorageMode, WritableVec};
use crate::{ use crate::{
distribution::{ distribution::{
metrics::ImportConfig, metrics::ImportConfig,
state::{CohortState, CostBasisOps, RealizedOps}, state::{CohortState, CostBasisOps, RealizedOps},
}, },
internal::PerBlockWithDeltas, internal::{PerBlock, PerBlockCumulativeRolling, PerBlockWithDeltas, RatioU32U64F32},
}; };
/// Base output metrics: utxo_count + delta. /// Base output metrics: utxo_count + delta.
#[derive(Traversable)] #[derive(Traversable)]
pub struct OutputsBase<M: StorageMode = Rw> { pub struct OutputsBase<M: StorageMode = Rw> {
pub unspent_count: PerBlockWithDeltas<StoredU64, StoredI64, BasisPointsSigned32, M>, pub unspent_count: PerBlockWithDeltas<StoredU64, StoredI64, BasisPointsSigned32, M>,
pub spent_count: PerBlockCumulativeRolling<StoredU32, StoredU64, M>,
pub spending_rate: PerBlock<StoredF32, M>,
} }
impl OutputsBase { impl OutputsBase {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> { pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
let v1 = Version::ONE;
Ok(Self { Ok(Self {
unspent_count: PerBlockWithDeltas::forced_import( unspent_count: PerBlockWithDeltas::forced_import(
cfg.db, cfg.db,
&cfg.name("utxo_count"), &cfg.name("utxo_count"),
cfg.version, cfg.version,
Version::ONE, v1,
cfg.indexes, cfg.indexes,
cfg.cached_starts, 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 { pub(crate) fn min_len(&self) -> usize {
self.unspent_count.height.len() self.unspent_count.height.len()
.min(self.spent_count.block.len())
} }
#[inline(always)] #[inline(always)]
@@ -40,10 +46,35 @@ impl OutputsBase {
self.unspent_count self.unspent_count
.height .height
.push(StoredU64::from(state.supply.utxo_count)); .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> { 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<Height, StoredU64>,
exit: &Exit,
) -> Result<()> {
self.spending_rate
.compute_binary::<StoredU32, StoredU64, RatioU32U64F32>(
max_from,
&self.spent_count.block,
all_utxo_count,
exit,
)
} }
pub(crate) fn compute_from_stateful( pub(crate) fn compute_from_stateful(
@@ -60,6 +91,7 @@ impl OutputsBase {
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
exit, exit,
)?; )?;
sum_others!(self, starting_indexes, others, exit; spent_count.block);
Ok(()) Ok(())
} }
} }

View File

@@ -28,6 +28,7 @@ impl<R: RealizedOps> AddrCohortState<R> {
self.addr_count = 0; self.addr_count = 0;
self.inner.supply = SupplyState::default(); self.inner.supply = SupplyState::default();
self.inner.sent = Sats::ZERO; self.inner.sent = Sats::ZERO;
self.inner.spent_utxo_count = 0;
self.inner.satdays_destroyed = Sats::ZERO; self.inner.satdays_destroyed = Sats::ZERO;
self.inner.realized = R::default(); self.inner.realized = R::default();
} }

View File

@@ -53,6 +53,7 @@ pub struct CohortState<R: RealizedOps, C: CostBasisOps> {
pub supply: SupplyState, pub supply: SupplyState,
pub realized: R, pub realized: R,
pub sent: Sats, pub sent: Sats,
pub spent_utxo_count: u64,
pub satdays_destroyed: Sats, pub satdays_destroyed: Sats,
cost_basis: C, cost_basis: C,
} }
@@ -63,6 +64,7 @@ impl<R: RealizedOps, C: CostBasisOps> CohortState<R, C> {
supply: SupplyState::default(), supply: SupplyState::default(),
realized: R::default(), realized: R::default(),
sent: Sats::ZERO, sent: Sats::ZERO,
spent_utxo_count: 0,
satdays_destroyed: Sats::ZERO, satdays_destroyed: Sats::ZERO,
cost_basis: C::create(path, name), cost_basis: C::create(path, name),
} }
@@ -97,6 +99,7 @@ impl<R: RealizedOps, C: CostBasisOps> CohortState<R, C> {
pub(crate) fn reset_single_iteration_values(&mut self) { pub(crate) fn reset_single_iteration_values(&mut self) {
self.sent = Sats::ZERO; self.sent = Sats::ZERO;
self.spent_utxo_count = 0;
if R::TRACK_ACTIVITY { if R::TRACK_ACTIVITY {
self.satdays_destroyed = Sats::ZERO; self.satdays_destroyed = Sats::ZERO;
} }
@@ -197,6 +200,7 @@ impl<R: RealizedOps, C: CostBasisOps> CohortState<R, C> {
) { ) {
self.supply -= supply; self.supply -= supply;
self.sent += pre.sats; self.sent += pre.sats;
self.spent_utxo_count += supply.utxo_count;
if R::TRACK_ACTIVITY { if R::TRACK_ACTIVITY {
self.satdays_destroyed += pre.age.satdays_destroyed(pre.sats); self.satdays_destroyed += pre.age.satdays_destroyed(pre.sats);
} }
@@ -220,6 +224,7 @@ impl<R: RealizedOps, C: CostBasisOps> CohortState<R, C> {
self.send_utxo_precomputed(supply, &pre); self.send_utxo_precomputed(supply, &pre);
} else if supply.utxo_count > 0 { } else if supply.utxo_count > 0 {
self.supply -= supply; self.supply -= supply;
self.spent_utxo_count += supply.utxo_count;
} }
} }
@@ -239,6 +244,7 @@ impl<R: RealizedOps, C: CostBasisOps> CohortState<R, C> {
} }
self.supply -= supply; self.supply -= supply;
self.spent_utxo_count += supply.utxo_count;
if supply.value > Sats::ZERO { if supply.value > Sats::ZERO {
self.sent += supply.value; self.sent += supply.value;

View File

@@ -23,6 +23,7 @@ impl<R: RealizedOps, C: CostBasisOps> UTXOCohortState<R, C> {
pub(crate) fn reset(&mut self) { pub(crate) fn reset(&mut self) {
self.0.supply = SupplyState::default(); self.0.supply = SupplyState::default();
self.0.sent = Sats::ZERO; self.0.sent = Sats::ZERO;
self.0.spent_utxo_count = 0;
self.0.satdays_destroyed = Sats::ZERO; self.0.satdays_destroyed = Sats::ZERO;
self.0.realized = R::default(); self.0.realized = R::default();
} }

View File

@@ -467,8 +467,10 @@ impl Vecs {
&height_to_market_cap, &height_to_market_cap,
exit, exit,
)?; )?;
let all_utxo_count = self.utxo_cohorts.all.metrics.outputs.unspent_count.height.read_only_clone();
self.addr_cohorts self.addr_cohorts
.compute_rest_part2(prices, starting_indexes, exit)?; .compute_rest_part2(prices, starting_indexes, &all_utxo_count, exit)?;
let _lock = exit.lock(); let _lock = exit.lock();
self.db.compact()?; self.db.compact()?;

View File

@@ -26,7 +26,7 @@ pub use derived::{
pub use ratio::{ pub use ratio::{
RatioCentsBp32, RatioCentsSignedCentsBps32, RatioCentsSignedDollarsBps32, RatioDiffCentsBps32, RatioCentsBp32, RatioCentsSignedCentsBps32, RatioCentsSignedDollarsBps32, RatioDiffCentsBps32,
RatioDiffDollarsBps32, RatioDiffF32Bps32, RatioDollarsBp16, RatioDollarsBp32, RatioDiffDollarsBps32, RatioDiffF32Bps32, RatioDollarsBp16, RatioDollarsBp32,
RatioDollarsBps32, RatioSatsBp16, RatioU64Bp16, RatioDollarsBps32, RatioSatsBp16, RatioU32U64F32, RatioU64Bp16,
}; };
pub use specialized::{ pub use specialized::{
BlockCountTarget1m, BlockCountTarget1w, BlockCountTarget1y, BlockCountTarget24h, BlockCountTarget1m, BlockCountTarget1w, BlockCountTarget1y, BlockCountTarget24h,

View File

@@ -1,6 +1,6 @@
use brk_types::{ use brk_types::{
BasisPoints16, BasisPoints32, BasisPointsSigned32, Cents, CentsSigned, Dollars, Sats, StoredF32, BasisPoints16, BasisPoints32, BasisPointsSigned32, Cents, CentsSigned, Dollars, Sats, StoredF32,
StoredU64, StoredU32, StoredU64,
}; };
use vecdb::BinaryTransform; use vecdb::BinaryTransform;
@@ -112,6 +112,19 @@ impl BinaryTransform<Dollars, Dollars, BasisPoints32> for RatioDollarsBp32 {
} }
} }
pub struct RatioU32U64F32;
impl BinaryTransform<StoredU32, StoredU64, StoredF32> 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; pub struct RatioDiffF32Bps32;
impl BinaryTransform<StoredF32, StoredF32, BasisPointsSigned32> for RatioDiffF32Bps32 { impl BinaryTransform<StoredF32, StoredF32, BasisPointsSigned32> for RatioDiffF32Bps32 {

View File

@@ -1,4 +1,3 @@
use core::panic;
use std::{ use std::{
cmp::Ordering, cmp::Ordering,
f32, f32,
@@ -33,9 +32,7 @@ impl From<f32> for StoredF32 {
impl From<f64> for StoredF32 { impl From<f64> for StoredF32 {
#[inline] #[inline]
fn from(value: f64) -> Self { fn from(value: f64) -> Self {
if value > f32::MAX as f64 { debug_assert!(value <= f32::MAX as f64);
panic!("f64 is too big")
}
Self(value as f32) Self(value as f32)
} }
} }

View File

@@ -40,9 +40,7 @@ impl From<i16> for StoredI16 {
impl From<usize> for StoredI16 { impl From<usize> for StoredI16 {
#[inline] #[inline]
fn from(value: usize) -> Self { fn from(value: usize) -> Self {
if value > i16::MAX as usize { debug_assert!(value <= i16::MAX as usize);
panic!("usize too big (value = {value})")
}
Self(value as i16) Self(value as i16)
} }
} }
@@ -76,9 +74,7 @@ impl AddAssign for StoredI16 {
impl From<f64> for StoredI16 { impl From<f64> for StoredI16 {
#[inline] #[inline]
fn from(value: f64) -> Self { fn from(value: f64) -> Self {
if value < 0.0 || value > i16::MAX as f64 { debug_assert!(value >= 0.0 && value <= i16::MAX as f64);
panic!()
}
Self(value as i16) Self(value as i16)
} }
} }

View File

@@ -40,9 +40,7 @@ impl From<i8> for StoredI8 {
impl From<usize> for StoredI8 { impl From<usize> for StoredI8 {
#[inline] #[inline]
fn from(value: usize) -> Self { fn from(value: usize) -> Self {
if value > i8::MAX as usize { debug_assert!(value <= i8::MAX as usize);
panic!("usize too big (value = {value})")
}
Self(value as i8) Self(value as i8)
} }
} }
@@ -76,9 +74,7 @@ impl AddAssign for StoredI8 {
impl From<f64> for StoredI8 { impl From<f64> for StoredI8 {
#[inline] #[inline]
fn from(value: f64) -> Self { fn from(value: f64) -> Self {
if value < i8::MIN as f64 || value > i8::MAX as f64 { debug_assert!(value >= i8::MIN as f64 && value <= i8::MAX as f64);
panic!()
}
Self(value as i8) Self(value as i8)
} }
} }

View File

@@ -47,9 +47,7 @@ impl From<u16> for StoredU16 {
impl From<usize> for StoredU16 { impl From<usize> for StoredU16 {
#[inline] #[inline]
fn from(value: usize) -> Self { fn from(value: usize) -> Self {
if value > u16::MAX as usize { debug_assert!(value <= u16::MAX as usize);
panic!("usize too big (value = {value})")
}
Self(value as u16) Self(value as u16)
} }
} }

View File

@@ -63,12 +63,18 @@ impl From<StoredU32> for f32 {
} }
} }
impl From<u64> for StoredU32 {
#[inline]
fn from(value: u64) -> Self {
debug_assert!(value <= u32::MAX as u64);
Self(value as u32)
}
}
impl From<usize> for StoredU32 { impl From<usize> for StoredU32 {
#[inline] #[inline]
fn from(value: usize) -> Self { fn from(value: usize) -> Self {
if value > u32::MAX as usize { debug_assert!(value <= u32::MAX as usize);
panic!("usize too big (value = {value})")
}
Self(value as u32) Self(value as u32)
} }
} }
@@ -81,9 +87,7 @@ impl CheckedSub<StoredU32> for StoredU32 {
impl CheckedSub<usize> for StoredU32 { impl CheckedSub<usize> for StoredU32 {
fn checked_sub(self, rhs: usize) -> Option<Self> { fn checked_sub(self, rhs: usize) -> Option<Self> {
if rhs > u32::MAX as usize { debug_assert!(rhs <= u32::MAX as usize);
panic!()
}
self.0.checked_sub(rhs as u32).map(Self) self.0.checked_sub(rhs as u32).map(Self)
} }
} }
@@ -125,9 +129,7 @@ impl Mul<usize> for StoredU32 {
type Output = Self; type Output = Self;
fn mul(self, rhs: usize) -> Self::Output { fn mul(self, rhs: usize) -> Self::Output {
let res = self.0 as usize * rhs; let res = self.0 as usize * rhs;
if res > u32::MAX as usize { debug_assert!(res <= u32::MAX as usize);
panic!()
}
Self::from(res) Self::from(res)
} }
} }

View File

@@ -285,16 +285,16 @@ export function createCointimeSection() {
sum: pattern.sum, sum: pattern.sum,
cumulative: pattern.cumulative, cumulative: pattern.cumulative,
})), })),
title: "Coinblocks", metric: "Coinblocks",
unit: Unit.coinblocks, unit: Unit.coinblocks,
}), }),
...coinblocks.map(({ pattern, name, title, color }) => ({ ...coinblocks.map(({ pattern, name, title: metric, color }) => ({
name, name,
tree: sumsAndAveragesCumulative({ tree: sumsAndAveragesCumulative({
sum: pattern.sum, sum: pattern.sum,
average: pattern.average, average: pattern.average,
cumulative: pattern.cumulative, cumulative: pattern.cumulative,
title, metric,
unit: Unit.coinblocks, unit: Unit.coinblocks,
color, color,
}), }),
@@ -322,16 +322,16 @@ export function createCointimeSection() {
cumulative: vocdd.pattern.cumulative, cumulative: vocdd.pattern.cumulative,
}, },
], ],
title: "Cointime Value", metric: "Cointime Value",
unit: Unit.usd, unit: Unit.usd,
}), }),
...cointimeValues.map(({ pattern, name, title, color }) => ({ ...cointimeValues.map(({ pattern, name, title: metric, color }) => ({
name, name,
tree: sumsAndAveragesCumulative({ tree: sumsAndAveragesCumulative({
sum: pattern.sum, sum: pattern.sum,
average: pattern.average, average: pattern.average,
cumulative: pattern.cumulative, cumulative: pattern.cumulative,
title, metric,
unit: Unit.usd, unit: Unit.usd,
color, color,
}), }),
@@ -342,7 +342,7 @@ export function createCointimeSection() {
sum: vocdd.pattern.sum, sum: vocdd.pattern.sum,
average: vocdd.pattern.average, average: vocdd.pattern.average,
cumulative: vocdd.pattern.cumulative, cumulative: vocdd.pattern.cumulative,
title: vocdd.title, metric: vocdd.title,
unit: Unit.usd, unit: Unit.usd,
color: vocdd.color, color: vocdd.color,
}), }),

View File

@@ -41,7 +41,8 @@ function volumeTree(tv, color, title) {
return [ return [
...satsBtcUsdFullTree({ ...satsBtcUsdFullTree({
pattern: tv, pattern: tv,
title: title("Transfer Volume"), title,
metric: "Transfer Volume",
color, color,
}), }),
{ {
@@ -83,7 +84,8 @@ function volumeTree(tv, color, title) {
name: "In Profit", name: "In Profit",
tree: satsBtcUsdFullTree({ tree: satsBtcUsdFullTree({
pattern: tv.inProfit, pattern: tv.inProfit,
title: title("Transfer Volume In Profit"), title,
metric: "Transfer Volume In Profit",
color: colors.profit, color: colors.profit,
}), }),
}, },
@@ -91,7 +93,8 @@ function volumeTree(tv, color, title) {
name: "In Loss", name: "In Loss",
tree: satsBtcUsdFullTree({ tree: satsBtcUsdFullTree({
pattern: tv.inLoss, pattern: tv.inLoss,
title: title("Transfer Volume In Loss"), title,
metric: "Transfer Volume In Loss",
color: colors.loss, color: colors.loss,
}), }),
}, },
@@ -122,7 +125,7 @@ function volumeFolderWithAdjusted(activity, adjustedTransferVolume, color, title
name: "Volume", name: "Volume",
tree: [ tree: [
...volumeTree(activity.transferVolume, color, title), ...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} * @returns {PartialOptionsTree}
*/ */
function valueDestroyedTree(valueDestroyed, title) { 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", name: "Value Destroyed",
tree: [ tree: [
...valueDestroyedTree(valueDestroyed, title), ...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", name: "Coindays Destroyed",
tree: chartsFromCount({ tree: chartsFromCount({
pattern: tree.activity.coindaysDestroyed, pattern: tree.activity.coindaysDestroyed,
title: title("Coindays Destroyed"), title,
metric: "Coindays Destroyed",
unit: Unit.coindays, unit: Unit.coindays,
color, color,
}), }),
@@ -267,7 +271,8 @@ function singleFullActivityTree(cohort, title, volumeItem, soprFolder, valueDest
name: "Dormancy", name: "Dormancy",
tree: averagesArray({ tree: averagesArray({
windows: tree.activity.dormancy, windows: tree.activity.dormancy,
title: title("Dormancy"), title,
metric: "Dormancy",
unit: Unit.days, unit: Unit.days,
}), }),
}, },
@@ -341,7 +346,8 @@ export function createActivitySectionWithActivity({ cohort, title }) {
name: "Coindays Destroyed", name: "Coindays Destroyed",
tree: chartsFromCount({ tree: chartsFromCount({
pattern: tree.activity.coindaysDestroyed, pattern: tree.activity.coindaysDestroyed,
title: title("Coindays Destroyed"), title,
metric: "Coindays Destroyed",
unit: Unit.coindays, unit: Unit.coindays,
color, color,
}), }),
@@ -360,7 +366,8 @@ export function createActivitySectionMinimal({ cohort, title }) {
name: "Activity", name: "Activity",
tree: satsBtcUsdFullTree({ tree: satsBtcUsdFullTree({
pattern: cohort.tree.activity.transferVolume, pattern: cohort.tree.activity.transferVolume,
title: title("Transfer Volume"), title,
metric: "Transfer Volume",
}), }),
}; };
} }

View File

@@ -52,12 +52,12 @@ function groupedUtxoCountFolder(list, all, title) {
tree: [ tree: [
{ {
name: "Count", name: "Count",
title: title("UTXOs"), title: title("UTXO Count"),
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
line({ series: tree.outputs.unspentCount.base, name, color, unit: Unit.count }), 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({ ...sumsTreeBaseline({
windows: delta.absolute, windows: delta.absolute,
title: title(`${name} Change`), title,
metric: `${name} Change`,
unit, unit,
}), }),
name: "Change", name: "Change",
@@ -82,7 +83,8 @@ function singleDeltaItems(delta, unit, title, name) {
{ {
...rollingPercentRatioTree({ ...rollingPercentRatioTree({
windows: delta.rate, windows: delta.rate,
title: title(`${name} Growth Rate`), title,
metric: `${name} Growth Rate`,
}), }),
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"), title: title("Supply"),
bottom: simpleSupplySeries(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), countFolder(cohort.tree.outputs.unspentCount, "UTXOs", "UTXO Count", cohort.color, title),
@@ -281,7 +283,7 @@ export function createHoldingsSectionAll({ cohort, title }) {
}, },
profitabilityChart(supply, title), profitabilityChart(supply, title),
ownSupplyChart(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), countFolder(cohort.tree.outputs.unspentCount, "UTXOs", "UTXO Count", cohort.color, title),
@@ -307,7 +309,7 @@ export function createHoldingsSectionWithRelative({ cohort, title }) {
profitabilityChart(supply, title), profitabilityChart(supply, title),
circulatingChart(supply, title), circulatingChart(supply, title),
ownSupplyChart(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), countFolder(cohort.tree.outputs.unspentCount, "UTXOs", "UTXO Count", cohort.color, title),
@@ -331,7 +333,7 @@ export function createHoldingsSectionWithOwnSupply({ cohort, title }) {
}, },
profitabilityChart(supply, title), profitabilityChart(supply, title),
circulatingChart(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), countFolder(cohort.tree.outputs.unspentCount, "UTXOs", "UTXO Count", cohort.color, title),
@@ -354,7 +356,7 @@ export function createHoldingsSectionWithProfitLoss({ cohort, title }) {
bottom: simpleSupplySeries(supply), bottom: simpleSupplySeries(supply),
}, },
profitabilityChart(supply, title), 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), countFolder(cohort.tree.outputs.unspentCount, "UTXOs", "UTXO Count", cohort.color, title),
@@ -377,7 +379,7 @@ export function createHoldingsSectionAddress({ cohort, title }) {
bottom: simpleSupplySeries(supply), bottom: simpleSupplySeries(supply),
}, },
profitabilityChart(supply, title), 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), countFolder(cohort.tree.outputs.unspentCount, "UTXOs", "UTXO Count", cohort.color, title),
@@ -400,7 +402,7 @@ export function createHoldingsSectionAddressAmount({ cohort, title }) {
title: title("Supply"), title: title("Supply"),
bottom: simpleSupplySeries(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), countFolder(cohort.tree.outputs.unspentCount, "UTXOs", "UTXO Count", cohort.color, title),
@@ -461,12 +463,12 @@ export function createGroupedHoldingsSectionAddress({ list, all, title }) {
tree: [ tree: [
{ {
name: "Count", name: "Count",
title: title("Addresses"), title: title("Address Count"),
bottom: mapCohortsWithAll(list, all, ({ name, color, addressCount }) => bottom: mapCohortsWithAll(list, all, ({ name, color, addressCount }) =>
line({ series: addressCount.base, name, color, unit: Unit.count }), 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: [ tree: [
{ {
name: "Count", name: "Count",
title: title("Addresses"), title: title("Address Count"),
bottom: mapCohortsWithAll(list, all, ({ name, color, addressCount }) => bottom: mapCohortsWithAll(list, all, ({ name, color, addressCount }) =>
line({ series: addressCount.base, name, color, unit: Unit.count }), 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"),
], ],
}, },
]; ];

View File

@@ -216,7 +216,8 @@ export function createCohortFolderAgeRangeWithMatured(cohort) {
name: "Matured", name: "Matured",
tree: satsBtcUsdFullTree({ tree: satsBtcUsdFullTree({
pattern: cohort.matured, pattern: cohort.matured,
title: title("Matured Supply"), title,
metric: "Matured Supply",
}), }),
}); });
return folder; return folder;
@@ -498,6 +499,7 @@ export function createGroupedAddressCohortFolder({
* @returns {PartialOptionsGroup} * @returns {PartialOptionsGroup}
*/ */
function singleBucketFolder({ name, color, pattern }) { function singleBucketFolder({ name, color, pattern }) {
const title = formatCohortTitle(name);
return { return {
name, name,
tree: [ tree: [
@@ -506,7 +508,7 @@ function singleBucketFolder({ name, color, pattern }) {
tree: [ tree: [
{ {
name: "Value", name: "Value",
title: `${name}: Supply`, title: title("Supply"),
bottom: [ bottom: [
...satsBtcUsd({ pattern: pattern.supply.all, name: "Total" }), ...satsBtcUsd({ pattern: pattern.supply.all, name: "Total" }),
...satsBtcUsd({ ...satsBtcUsd({
@@ -522,7 +524,8 @@ function singleBucketFolder({ name, color, pattern }) {
{ {
...sumsTreeBaseline({ ...sumsTreeBaseline({
windows: pattern.supply.all.delta.absolute, windows: pattern.supply.all.delta.absolute,
title: `${name}: Supply Change`, title,
metric: "Supply Change",
unit: Unit.sats, unit: Unit.sats,
}), }),
name: "Change", name: "Change",
@@ -530,7 +533,8 @@ function singleBucketFolder({ name, color, pattern }) {
{ {
...rollingPercentRatioTree({ ...rollingPercentRatioTree({
windows: pattern.supply.all.delta.rate, windows: pattern.supply.all.delta.rate,
title: `${name}: Supply Rate`, title,
metric: "Supply Growth Rate",
}), }),
name: "Growth Rate", name: "Growth Rate",
}, },
@@ -540,7 +544,7 @@ function singleBucketFolder({ name, color, pattern }) {
}, },
{ {
name: "Realized Cap", name: "Realized Cap",
title: `${name}: Realized Cap`, title: title("Realized Cap"),
bottom: [ bottom: [
line({ line({
series: pattern.realizedCap.all, series: pattern.realizedCap.all,
@@ -557,7 +561,7 @@ function singleBucketFolder({ name, color, pattern }) {
}, },
{ {
name: "NUPL", name: "NUPL",
title: `${name}: NUPL`, title: title("NUPL"),
bottom: [ bottom: [
line({ series: pattern.nupl.ratio, name, color, unit: Unit.ratio }), 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 {{ name: string, color: Color, pattern: RealizedSupplyPattern }[]} list
* @param {string} titlePrefix * @param {string} groupTitle
* @returns {PartialOptionsTree} * @returns {PartialOptionsTree}
*/ */
function groupedBucketCharts(list, titlePrefix) { function groupedBucketCharts(list, groupTitle) {
const title = formatCohortTitle(groupTitle);
return [ return [
{ {
name: "Supply", name: "Supply",
tree: [ tree: [
{ {
name: "All", name: "All",
title: `${titlePrefix}: Supply`, title: title("Supply"),
bottom: list.flatMap(({ name, color, pattern }) => bottom: list.flatMap(({ name, color, pattern }) =>
satsBtcUsd({ pattern: pattern.supply.all, name, color }), satsBtcUsd({ pattern: pattern.supply.all, name, color }),
), ),
}, },
{ {
name: "STH", name: "STH",
title: `${titlePrefix}: STH Supply`, title: title("STH Supply"),
bottom: list.flatMap(({ name, color, pattern }) => bottom: list.flatMap(({ name, color, pattern }) =>
satsBtcUsd({ pattern: pattern.supply.sth, name, color }), satsBtcUsd({ pattern: pattern.supply.sth, name, color }),
), ),
@@ -598,7 +603,7 @@ function groupedBucketCharts(list, titlePrefix) {
tree: [ tree: [
{ {
name: "Compare", name: "Compare",
title: `${titlePrefix}: Supply Change`, title: title("Supply Change"),
bottom: ROLLING_WINDOWS.flatMap((w) => bottom: ROLLING_WINDOWS.flatMap((w) =>
list.map(({ name, color, pattern }) => list.map(({ name, color, pattern }) =>
baseline({ baseline({
@@ -612,7 +617,7 @@ function groupedBucketCharts(list, titlePrefix) {
}, },
...ROLLING_WINDOWS.map((w) => ({ ...ROLLING_WINDOWS.map((w) => ({
name: w.name, name: w.name,
title: `${titlePrefix}: Supply Change (${w.title})`, title: title(`Supply Change (${w.title})`),
bottom: list.map(({ name, color, pattern }) => bottom: list.map(({ name, color, pattern }) =>
baseline({ baseline({
series: pattern.supply.all.delta.absolute[w.key], series: pattern.supply.all.delta.absolute[w.key],
@@ -629,7 +634,7 @@ function groupedBucketCharts(list, titlePrefix) {
tree: [ tree: [
{ {
name: "Compare", name: "Compare",
title: `${titlePrefix}: Supply Rate`, title: title("Supply Growth Rate"),
bottom: ROLLING_WINDOWS.flatMap((w) => bottom: ROLLING_WINDOWS.flatMap((w) =>
list.flatMap(({ name, color, pattern }) => list.flatMap(({ name, color, pattern }) =>
percentRatio({ percentRatio({
@@ -642,7 +647,7 @@ function groupedBucketCharts(list, titlePrefix) {
}, },
...ROLLING_WINDOWS.map((w) => ({ ...ROLLING_WINDOWS.map((w) => ({
name: w.name, name: w.name,
title: `${titlePrefix}: Supply Rate (${w.title})`, title: title(`Supply Growth Rate (${w.title})`),
bottom: list.flatMap(({ name, color, pattern }) => bottom: list.flatMap(({ name, color, pattern }) =>
percentRatio({ percentRatio({
pattern: pattern.supply.all.delta.rate[w.key], pattern: pattern.supply.all.delta.rate[w.key],
@@ -662,7 +667,7 @@ function groupedBucketCharts(list, titlePrefix) {
tree: [ tree: [
{ {
name: "All", name: "All",
title: `${titlePrefix}: Realized Cap`, title: title("Realized Cap"),
bottom: list.map(({ name, color, pattern }) => bottom: list.map(({ name, color, pattern }) =>
line({ line({
series: pattern.realizedCap.all, series: pattern.realizedCap.all,
@@ -674,7 +679,7 @@ function groupedBucketCharts(list, titlePrefix) {
}, },
{ {
name: "STH", name: "STH",
title: `${titlePrefix}: STH Realized Cap`, title: title("STH Realized Cap"),
bottom: list.map(({ name, color, pattern }) => bottom: list.map(({ name, color, pattern }) =>
line({ line({
series: pattern.realizedCap.sth, series: pattern.realizedCap.sth,
@@ -688,7 +693,7 @@ function groupedBucketCharts(list, titlePrefix) {
}, },
{ {
name: "NUPL", name: "NUPL",
title: `${titlePrefix}: NUPL`, title: title("NUPL"),
bottom: list.map(({ name, color, pattern }) => bottom: list.map(({ name, color, pattern }) =>
line({ series: pattern.nupl.ratio, name, color, unit: Unit.ratio }), line({ series: pattern.nupl.ratio, name, color, unit: Unit.ratio }),
), ),

View File

@@ -389,7 +389,8 @@ function realizedNetFolder({ netPnl, title, extraChange = [] }) {
{ {
...sumsTreeBaseline({ ...sumsTreeBaseline({
windows: mapWindows(netPnl.delta.absolute, (c) => c.usd), 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, unit: Unit.usd,
}), }),
name: "Change", name: "Change",
@@ -399,7 +400,8 @@ function realizedNetFolder({ netPnl, title, extraChange = [] }) {
tree: [ tree: [
...rollingPercentRatioTree({ ...rollingPercentRatioTree({
windows: netPnl.delta.rate, windows: netPnl.delta.rate,
title: title("Net Realized P&L Growth Rate"), title,
metric: "Net Realized P&L Growth Rate",
}).tree, }).tree,
...extraChange, ...extraChange,
], ],

View File

@@ -19,8 +19,8 @@ import { ratioBottomSeries, mapCohortsWithAll, flatMapCohortsWithAll } from "../
*/ */
function singleDeltaItems(tree, title) { function singleDeltaItems(tree, title) {
return [ return [
{ ...sumsTreeBaseline({ windows: mapWindows(tree.realized.cap.delta.absolute, (c) => c.usd), title: title("Realized Cap Change"), unit: Unit.usd }), name: "Change" }, { ...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: title("Realized Cap Growth Rate") }), name: "Growth Rate" }, { ...rollingPercentRatioTree({ windows: tree.realized.cap.delta.rate, title, metric: "Realized Cap Growth Rate" }), name: "Growth Rate" },
]; ];
} }

View File

@@ -46,8 +46,20 @@ import { periodIdToName } from "./utils.js";
*/ */
function indexRatio({ pattern, name, color, defaultActive }) { function indexRatio({ pattern, name, color, defaultActive }) {
return [ return [
line({ series: pattern.percent, name, color, defaultActive, unit: Unit.index }), line({
line({ series: pattern.ratio, name, color, defaultActive, unit: Unit.ratio }), 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({ ...deltaTree({
delta: supply.marketCap.delta, delta: supply.marketCap.delta,
title: "Market Cap", metric: "Market Cap",
unit: Unit.usd, unit: Unit.usd,
extract: (v) => v.usd, extract: (v) => v.usd,
}), }),
@@ -553,7 +565,7 @@ export function createMarketSection() {
}, },
...deltaTree({ ...deltaTree({
delta: cohorts.utxo.all.realized.cap.delta, delta: cohorts.utxo.all.realized.cap.delta,
title: "Realized Cap", metric: "Realized Cap",
unit: Unit.usd, unit: Unit.usd,
extract: (v) => v.usd, extract: (v) => v.usd,
}), }),
@@ -650,7 +662,11 @@ export function createMarketSection() {
title: "RSI Comparison", title: "RSI Comparison",
bottom: [ bottom: [
...ROLLING_WINDOWS_TO_1M.flatMap((w) => ...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: 70 }),
priceLine({ unit: Unit.index, number: 30 }), priceLine({ unit: Unit.index, number: 30 }),
@@ -662,9 +678,17 @@ export function createMarketSection() {
name: w.name, name: w.name,
title: `RSI (${w.title})`, title: `RSI (${w.title})`,
bottom: [ 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: 70 }),
priceLine({ unit: Unit.index, number: 50, defaultActive: false }), priceLine({
unit: Unit.index,
number: 50,
defaultActive: false,
}),
priceLine({ unit: Unit.index, number: 30 }), priceLine({ unit: Unit.index, number: 30 }),
], ],
}; };
@@ -672,17 +696,28 @@ export function createMarketSection() {
{ {
name: "Stochastic", name: "Stochastic",
tree: ROLLING_WINDOWS_TO_1M.map((w) => { tree: ROLLING_WINDOWS_TO_1M.map((w) => {
const rsi = technical.rsi[w.key]; const rsi = technical.rsi[w.key];
return { return {
name: w.name, name: w.name,
title: `Stochastic RSI (${w.title})`, title: `Stochastic RSI (${w.title})`,
bottom: [ bottom: [
...indexRatio({ pattern: rsi.stochRsiK, name: "K", color: colors.indicator.fast }), ...indexRatio({
...indexRatio({ pattern: rsi.stochRsiD, name: "D", color: colors.indicator.slow }), pattern: rsi.stochRsiK,
...priceLines({ unit: Unit.index, numbers: [80, 20] }), 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", name: "Compare",
title: "MACD Comparison", title: "MACD Comparison",
bottom: ROLLING_WINDOWS_TO_1M.map((w) => 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) => ({ ...ROLLING_WINDOWS_TO_1M.map((w) => ({
name: w.name, name: w.name,
title: `MACD (${w.title})`, title: `MACD (${w.title})`,
bottom: [ bottom: [
line({ series: technical.macd[w.key].line, name: "MACD", color: colors.indicator.fast, unit: Unit.usd }), line({
line({ series: technical.macd[w.key].signal, name: "Signal", color: colors.indicator.slow, unit: Unit.usd }), series: technical.macd[w.key].line,
histogram({ series: technical.macd[w.key].histogram, name: "Histogram", unit: Unit.usd }), 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", name: "Compare",
title: "Volatility Index", title: "Volatility Index",
bottom: ROLLING_WINDOWS.map((w) => 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) => ({ ...ROLLING_WINDOWS.map((w) => ({
name: w.name, name: w.name,
title: `Volatility Index (${w.title})`, 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,
}),
],
})), })),
], ],
}, },

View File

@@ -19,6 +19,7 @@ import {
satsBtcUsdFullTree, satsBtcUsdFullTree,
revenueBtcSatsUsd, revenueBtcSatsUsd,
revenueRollingBtcSatsUsd, revenueRollingBtcSatsUsd,
formatCohortTitle,
} from "./shared.js"; } from "./shared.js";
import { brk } from "../client.js"; import { brk } from "../client.js";
@@ -76,13 +77,17 @@ export function createMiningSection() {
includes(ANTPOOL_AND_FRIENDS_IDS, p.id), 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", name: "Dominance",
tree: [ tree: [
{ {
name: "Compare", name: "Compare",
title, title: title(metric),
bottom: [ bottom: [
...ROLLING_WINDOWS.flatMap((w) => ...ROLLING_WINDOWS.flatMap((w) =>
percentRatio({ pattern: dominance[w.key], name: w.name, color: w.color, defaultActive: w.key !== "_24h" }), 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) => ({ ...ROLLING_WINDOWS.map((w) => ({
name: w.name, name: w.name,
title: `${title} ${w.title}`, title: title(`${w.name} ${metric}`),
bottom: percentRatio({ pattern: dominance[w.key], name: w.name, color: w.color }), bottom: percentRatio({ pattern: dominance[w.key], name: w.name, color: w.color }),
})), })),
{ {
name: "All Time", name: "All Time",
title: `${title} All Time`, title: title(`${metric} All Time`),
bottom: percentRatio({ pattern: dominance, name: "All Time", color: colors.time.all }), bottom: percentRatio({ pattern: dominance, name: "All Time", color: colors.time.all }),
}, },
], ],
@@ -107,50 +112,59 @@ export function createMiningSection() {
* @param {typeof majorPoolData} poolList * @param {typeof majorPoolData} poolList
*/ */
const createPoolTree = (poolList) => const createPoolTree = (poolList) =>
poolList.map(({ name, pool }) => ({ poolList.map(({ name, pool }) => {
name, const title = formatCohortTitle(name);
tree: [ return {
dominanceTree(`Dominance: ${name}`, pool.dominance), name,
{ tree: [
name: "Blocks Mined", dominanceTree(title, "Dominance", pool.dominance),
tree: chartsFromCount({ {
pattern: pool.blocksMined, name: "Blocks Mined",
title: `Blocks Mined: ${name}`, tree: chartsFromCount({
unit: Unit.count, pattern: pool.blocksMined,
}), title,
}, metric: "Blocks Mined",
{ unit: Unit.count,
name: "Rewards", }),
tree: satsBtcUsdFullTree({ },
pattern: pool.rewards, {
title: `Rewards: ${name}`, name: "Rewards",
}), tree: satsBtcUsdFullTree({
}, pattern: pool.rewards,
], title,
})); metric: "Rewards",
}),
},
],
};
});
/** /**
* @param {typeof minorPoolData} poolList * @param {typeof minorPoolData} poolList
*/ */
const createMinorPoolTree = (poolList) => const createMinorPoolTree = (poolList) =>
poolList.map(({ name, pool }) => ({ poolList.map(({ name, pool }) => {
name, const title = formatCohortTitle(name);
tree: [ return {
{ name,
name: "Dominance", tree: [
title: `Dominance: ${name}`, {
bottom: percentRatio({ pattern: pool.dominance, name: "All Time", color: colors.time.all }), 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, name: "Blocks Mined",
title: `Blocks Mined: ${name}`, tree: chartsFromCount({
unit: Unit.count, pattern: pool.blocksMined,
}), title,
}, metric: "Blocks Mined",
], unit: Unit.count,
})); }),
},
],
};
});
/** /**
* @param {string} groupTitle * @param {string} groupTitle
@@ -306,14 +320,14 @@ export function createMiningSection() {
name: "Coinbase", name: "Coinbase",
tree: satsBtcUsdFullTree({ tree: satsBtcUsdFullTree({
pattern: mining.rewards.coinbase, pattern: mining.rewards.coinbase,
title: "Coinbase Rewards", metric: "Coinbase Rewards",
}), }),
}, },
{ {
name: "Subsidy", name: "Subsidy",
tree: satsBtcUsdFullTree({ tree: satsBtcUsdFullTree({
pattern: mining.rewards.subsidy, pattern: mining.rewards.subsidy,
title: "Block Subsidy", metric: "Block Subsidy",
}), }),
}, },
{ {
@@ -321,7 +335,7 @@ export function createMiningSection() {
tree: [ tree: [
...satsBtcUsdFullTree({ ...satsBtcUsdFullTree({
pattern: mining.rewards.fees, pattern: mining.rewards.fees,
title: "Transaction Fee Revenue", metric: "Transaction Fee Revenue",
}), }),
{ {
name: "Distributions", name: "Distributions",

View File

@@ -53,7 +53,12 @@ export function createNetworkSection() {
// Non-addressable script types // Non-addressable script types
const nonAddressableTypes = /** @type {const} */ ([ const nonAddressableTypes = /** @type {const} */ ([
{ key: "p2ms", name: "P2MS", color: st.p2ms, defaultActive: false }, { 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", key: "emptyOutput",
name: "Empty", name: "Empty",
@@ -71,7 +76,6 @@ export function createNetworkSection() {
// All script types = addressable + non-addressable // All script types = addressable + non-addressable
const scriptTypes = [...addressTypes, ...nonAddressableTypes]; const scriptTypes = [...addressTypes, ...nonAddressableTypes];
// Transacting types (transaction participation) // Transacting types (transaction participation)
const activityTypes = /** @type {const} */ ([ const activityTypes = /** @type {const} */ ([
{ key: "sending", name: "Sending" }, { key: "sending", name: "Sending" },
@@ -138,14 +142,16 @@ export function createNetworkSection() {
}, },
...simpleDeltaTree({ ...simpleDeltaTree({
delta: addrs.delta[key], delta: addrs.delta[key],
title: `${titlePrefix}Address Count`, title: (s) => `${titlePrefix}${s}`,
metric: "Address Count",
unit: Unit.count, unit: Unit.count,
}), }),
{ {
name: "New", name: "New",
tree: chartsFromCount({ tree: chartsFromCount({
pattern: addrs.new[key], pattern: addrs.new[key],
title: `${titlePrefix}New Addresses`, title: (s) => `${titlePrefix}${s}`,
metric: "New Addresses",
unit: Unit.count, unit: Unit.count,
}), }),
}, },
@@ -171,7 +177,8 @@ export function createNetworkSection() {
name: t.name, name: t.name,
tree: averagesArray({ tree: averagesArray({
windows: addrs.activity[key][t.key], windows: addrs.activity[key][t.key],
title: `${titlePrefix}${t.name} Addresses`, title: (s) => `${titlePrefix}${s}`,
metric: `${t.name} Addresses`,
unit: Unit.count, unit: Unit.count,
}), }),
})), })),
@@ -187,7 +194,10 @@ export function createNetworkSection() {
{ name: "Script Hash", types: [byKey.p2sh, byKey.p2ms] }, { name: "Script Hash", types: [byKey.p2sh, byKey.p2ms] },
{ name: "SegWit", types: [byKey.p2wsh, byKey.p2wpkh] }, { name: "SegWit", types: [byKey.p2wsh, byKey.p2wpkh] },
{ name: "Taproot", types: [byKey.p2a, byKey.p2tr] }, { 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`, title: `${groupName} Output Count ${w.title} Sum`,
bottom: types.map((t) => bottom: types.map((t) =>
line({ line({
series: /** @type {CountPattern<number>} */ (scripts.count[t.key]).sum[w.key], series: /** @type {CountPattern<number>} */ (
scripts.count[t.key]
).sum[w.key],
name: t.name, name: t.name,
color: t.color, color: t.color,
unit: Unit.count, unit: Unit.count,
@@ -231,7 +243,7 @@ export function createNetworkSection() {
name: t.name, name: t.name,
tree: chartsFromCount({ tree: chartsFromCount({
pattern: /** @type {CountPattern<number>} */ (scripts.count[t.key]), pattern: /** @type {CountPattern<number>} */ (scripts.count[t.key]),
title: `${t.name} Output Count`, metric: `${t.name} Output Count`,
unit: Unit.count, unit: Unit.count,
}), }),
})), })),
@@ -298,7 +310,7 @@ export function createNetworkSection() {
name: "Count", name: "Count",
tree: chartsFromFullPerBlock({ tree: chartsFromFullPerBlock({
pattern: transactions.count.total, pattern: transactions.count.total,
title: "Transaction Count", metric: "Transaction Count",
unit: Unit.count, unit: Unit.count,
}), }),
}, },
@@ -306,14 +318,14 @@ export function createNetworkSection() {
name: "Volume", name: "Volume",
tree: satsBtcUsdFullTree({ tree: satsBtcUsdFullTree({
pattern: transactions.volume.transferVolume, pattern: transactions.volume.transferVolume,
title: "Transaction Volume", metric: "Transaction Volume",
}), }),
}, },
{ {
name: "Fee Rate", name: "Fee Rate",
tree: chartsFromBlockAnd6b({ tree: chartsFromBlockAnd6b({
pattern: transactions.fees.feeRate, pattern: transactions.fees.feeRate,
title: "Transaction Fee Rate", metric: "Transaction Fee Rate",
unit: Unit.feeRate, unit: Unit.feeRate,
}), }),
}, },
@@ -321,7 +333,7 @@ export function createNetworkSection() {
name: "Fee", name: "Fee",
tree: chartsFromBlockAnd6b({ tree: chartsFromBlockAnd6b({
pattern: transactions.fees.fee, pattern: transactions.fees.fee,
title: "Transaction Fee", metric: "Transaction Fee",
unit: Unit.sats, unit: Unit.sats,
}), }),
}, },
@@ -329,7 +341,7 @@ export function createNetworkSection() {
name: "Weight", name: "Weight",
tree: chartsFromBlockAnd6b({ tree: chartsFromBlockAnd6b({
pattern: transactions.size.weight, pattern: transactions.size.weight,
title: "Transaction Weight", metric: "Transaction Weight",
unit: Unit.wu, unit: Unit.wu,
}), }),
}, },
@@ -337,7 +349,7 @@ export function createNetworkSection() {
name: "vSize", name: "vSize",
tree: chartsFromBlockAnd6b({ tree: chartsFromBlockAnd6b({
pattern: transactions.size.vsize, pattern: transactions.size.vsize,
title: "Transaction vSize", metric: "Transaction vSize",
unit: Unit.vb, unit: Unit.vb,
}), }),
}, },
@@ -345,7 +357,7 @@ export function createNetworkSection() {
name: "Versions", name: "Versions",
tree: chartsFromCountEntries({ tree: chartsFromCountEntries({
entries: entries(transactions.versions), entries: entries(transactions.versions),
title: "Transaction Versions", metric: "Transaction Versions",
unit: Unit.count, unit: Unit.count,
}), }),
}, },
@@ -423,7 +435,7 @@ export function createNetworkSection() {
name: "Interval", name: "Interval",
tree: averagesArray({ tree: averagesArray({
windows: blocks.interval, windows: blocks.interval,
title: "Block Interval", metric: "Block Interval",
unit: Unit.secs, unit: Unit.secs,
}), }),
}, },
@@ -431,7 +443,7 @@ export function createNetworkSection() {
name: "Size", name: "Size",
tree: chartsFromFullPerBlock({ tree: chartsFromFullPerBlock({
pattern: blocks.size, pattern: blocks.size,
title: "Block Size", metric: "Block Size",
unit: Unit.bytes, unit: Unit.bytes,
}), }),
}, },
@@ -439,7 +451,7 @@ export function createNetworkSection() {
name: "Weight", name: "Weight",
tree: chartsFromFullPerBlock({ tree: chartsFromFullPerBlock({
pattern: blocks.weight, pattern: blocks.weight,
title: "Block Weight", metric: "Block Weight",
unit: Unit.wu, unit: Unit.wu,
}), }),
}, },
@@ -447,7 +459,7 @@ export function createNetworkSection() {
name: "vBytes", name: "vBytes",
tree: chartsFromFullPerBlock({ tree: chartsFromFullPerBlock({
pattern: blocks.vbytes, pattern: blocks.vbytes,
title: "Block vBytes", metric: "Block vBytes",
unit: Unit.vb, unit: Unit.vb,
}), }),
}, },
@@ -471,7 +483,7 @@ export function createNetworkSection() {
}, },
...simpleDeltaTree({ ...simpleDeltaTree({
delta: cohorts.utxo.all.outputs.unspentCount.delta, delta: cohorts.utxo.all.outputs.unspentCount.delta,
title: "UTXO Count", metric: "UTXO Count",
unit: Unit.count, unit: Unit.count,
}), }),
{ {
@@ -493,7 +505,7 @@ export function createNetworkSection() {
cumulative: inputs.count.cumulative, cumulative: inputs.count.cumulative,
}, },
], ],
title: "UTXO Flow", metric: "UTXO Flow",
unit: Unit.count, unit: Unit.count,
}), }),
}, },
@@ -503,7 +515,7 @@ export function createNetworkSection() {
name: "Inputs", name: "Inputs",
tree: chartsFromAggregatedPerBlock({ tree: chartsFromAggregatedPerBlock({
pattern: inputs.count, pattern: inputs.count,
title: "Input Count", metric: "Input Count",
unit: Unit.count, unit: Unit.count,
}), }),
}, },
@@ -511,7 +523,7 @@ export function createNetworkSection() {
name: "Outputs", name: "Outputs",
tree: chartsFromAggregatedPerBlock({ tree: chartsFromAggregatedPerBlock({
pattern: outputs.count.total, pattern: outputs.count.total,
title: "Output Count", metric: "Output Count",
unit: Unit.count, unit: Unit.count,
}), }),
}, },
@@ -588,7 +600,9 @@ export function createNetworkSection() {
title: `Output Count by Script Type ${w.title} Sum`, title: `Output Count by Script Type ${w.title} Sum`,
bottom: scriptTypes.map((t) => bottom: scriptTypes.map((t) =>
line({ line({
series: /** @type {CountPattern<number>} */ (scripts.count[t.key]).sum[w.key], series: /** @type {CountPattern<number>} */ (
scripts.count[t.key]
).sum[w.key],
name: t.name, name: t.name,
color: t.color, color: t.color,
unit: Unit.count, unit: Unit.count,

View File

@@ -93,13 +93,48 @@ export function price({
function percentileSeries({ pattern, unit, title = "" }) { function percentileSeries({ pattern, unit, title = "" }) {
const { stat } = colors; const { stat } = colors;
return [ return [
line({ series: pattern.max, name: `${title} max`.trim(), color: stat.max, unit }), line({
line({ series: pattern.pct90, name: `${title} pct90`.trim(), color: stat.pct90, unit }), series: pattern.max,
line({ series: pattern.pct75, name: `${title} pct75`.trim(), color: stat.pct75, unit }), name: `${title} max`.trim(),
line({ series: pattern.median, name: `${title} median`.trim(), color: stat.median, unit }), color: stat.max,
line({ series: pattern.pct25, name: `${title} pct25`.trim(), color: stat.pct25, unit }), 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.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 * Rolling folder tree with baseline series
* @param {Object} args * @param {Object} args
@@ -447,7 +452,13 @@ function rollingWindowsTreeLine({ windows, title, windowTitle, unit, name }) {
* @param {string} args.name * @param {string} args.name
* @returns {PartialOptionsGroup} * @returns {PartialOptionsGroup}
*/ */
function rollingWindowsTreeBaseline({ windows, title, windowTitle, unit, name }) { function rollingWindowsTreeBaseline({
windows,
title,
windowTitle,
unit,
name,
}) {
return { return {
name, name,
tree: [ tree: [
@@ -455,7 +466,12 @@ function rollingWindowsTreeBaseline({ windows, title, windowTitle, unit, name })
name: "Compare", name: "Compare",
title, title,
bottom: ROLLING_WINDOWS.map((w) => 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) => ({ ...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. * Generic helper: compare + per-window sum+avg + cumulative.
* @template P * @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.sum
* @param {{ _24h: P, _1w: P, _1m: P, _1y: P }} args.average * @param {{ _24h: P, _1w: P, _1m: P, _1y: P }} args.average
* @param {P} args.cumulative * @param {P} args.cumulative
* @param {string} args.title * @param {(metric: string) => string} [args.title]
* @param {string} args.metric
* @param {Color} [args.color] * @param {Color} [args.color]
* @param {(args: { pattern: P, name: string, color?: Color, defaultActive?: boolean }) => AnyFetchedSeriesBlueprint[]} args.series * @param {(args: { pattern: P, name: string, color?: Color, defaultActive?: boolean }) => AnyFetchedSeriesBlueprint[]} args.series
* @returns {PartialChartOption[]} * @returns {PartialChartOption[]}
@@ -501,14 +500,15 @@ export function sumsAndAveragesCumulativeWith({
sum, sum,
average, average,
cumulative, cumulative,
title, title = (s) => s,
metric,
color, color,
series, series,
}) { }) {
return [ return [
{ {
name: "Compare", name: "Compare",
title: `${title} Averages`, title: title(metric),
bottom: ROLLING_WINDOWS.flatMap((w) => bottom: ROLLING_WINDOWS.flatMap((w) =>
series({ series({
pattern: average[w.key], pattern: average[w.key],
@@ -519,7 +519,7 @@ export function sumsAndAveragesCumulativeWith({
}, },
...ROLLING_WINDOWS.map((w) => ({ ...ROLLING_WINDOWS.map((w) => ({
name: w.name, name: w.name,
title: `${title} ${w.title}`, title: title(`${w.name} ${metric}`),
bottom: [ bottom: [
...series({ pattern: sum[w.key], name: "Sum", color: w.color }), ...series({ pattern: sum[w.key], name: "Sum", color: w.color }),
...series({ ...series({
@@ -532,7 +532,7 @@ export function sumsAndAveragesCumulativeWith({
})), })),
{ {
name: "Cumulative", name: "Cumulative",
title: `${title} (Total)`, title: title(`Cumulative ${metric}`),
bottom: series({ pattern: cumulative, name: "all-time", color }), bottom: series({ pattern: cumulative, name: "all-time", color }),
}, },
]; ];
@@ -543,14 +543,15 @@ export function sumsAndAveragesCumulativeWith({
* @param {Object} args * @param {Object} args
* @param {{ _24h: AnySeriesPattern, _1w: AnySeriesPattern, _1m: AnySeriesPattern, _1y: AnySeriesPattern }} args.sum * @param {{ _24h: AnySeriesPattern, _1w: AnySeriesPattern, _1m: AnySeriesPattern, _1y: AnySeriesPattern }} args.sum
* @param {{ _24h: AnySeriesPattern, _1w: AnySeriesPattern, _1m: AnySeriesPattern, _1y: AnySeriesPattern }} args.average * @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 * @param {Unit} args.unit
* @returns {PartialChartOption[]} * @returns {PartialChartOption[]}
*/ */
export function sumsAndAveragesArray({ sum, average, title, unit }) { export function sumsAndAveragesArray({ sum, average, title = (s) => s, metric, unit }) {
return ROLLING_WINDOWS.map((w) => ({ return ROLLING_WINDOWS.map((w) => ({
name: w.name, name: w.name,
title: `${title} ${w.title}`, title: title(`${w.name} ${metric}`),
bottom: [ bottom: [
line({ series: sum[w.key], name: "Sum", color: w.color, unit }), line({ series: sum[w.key], name: "Sum", color: w.color, unit }),
line({ 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.sum
* @param {{ _24h: AnySeriesPattern, _1w: AnySeriesPattern, _1m: AnySeriesPattern, _1y: AnySeriesPattern }} args.average * @param {{ _24h: AnySeriesPattern, _1w: AnySeriesPattern, _1m: AnySeriesPattern, _1y: AnySeriesPattern }} args.average
* @param {AnySeriesPattern} args.cumulative * @param {AnySeriesPattern} args.cumulative
* @param {string} args.title * @param {(metric: string) => string} [args.title]
* @param {string} args.metric
* @param {Unit} args.unit * @param {Unit} args.unit
* @param {Color} [args.color] * @param {Color} [args.color]
* @returns {PartialChartOption[]} * @returns {PartialChartOption[]}
*/ */
export function sumsAndAveragesCumulative({ sum, average, cumulative, title, unit, color }) { export function sumsAndAveragesCumulative({
sum,
average,
cumulative,
title,
metric,
unit,
color,
}) {
return sumsAndAveragesCumulativeWith({ return sumsAndAveragesCumulativeWith({
sum, sum,
average, average,
cumulative, cumulative,
title, title,
metric,
color, color,
series: ({ pattern, name, color, defaultActive }) => [ series: ({ pattern, name, color, defaultActive }) => [
line({ series: pattern, name, color, unit, 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 {Object} args
* @param {{ _24h: AnySeriesPattern, _1w: AnySeriesPattern, _1m: AnySeriesPattern, _1y: AnySeriesPattern }} args.windows * @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 * @param {Unit} args.unit
* @returns {PartialOptionsGroup} * @returns {PartialOptionsGroup}
*/ */
export function sumsTree({ windows, title, unit }) { export function sumsTreeBaseline({ windows, title = (s) => s, metric, 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 }) {
return rollingWindowsTreeBaseline({ return rollingWindowsTreeBaseline({
windows, windows,
title, title: title(metric),
windowTitle: (w) => `${title} ${w.title} Sum`, windowTitle: (w) => title(`${w.name} ${metric}`),
unit, unit,
name: "Sums", name: "Sums",
}); });
@@ -627,22 +621,23 @@ export function sumsTreeBaseline({ windows, title, unit }) {
* Flat array of per-window average charts * Flat array of per-window average charts
* @param {Object} args * @param {Object} args
* @param {{ _24h: AnySeriesPattern, _1w: AnySeriesPattern, _1m: AnySeriesPattern, _1y: AnySeriesPattern }} args.windows * @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 * @param {Unit} args.unit
* @returns {PartialChartOption[]} * @returns {PartialChartOption[]}
*/ */
export function averagesArray({ windows, title, unit }) { export function averagesArray({ windows, title = (s) => s, metric, unit }) {
return [ return [
{ {
name: "Compare", name: "Compare",
title: `${title} Averages`, title: title(metric),
bottom: ROLLING_WINDOWS.map((w) => bottom: ROLLING_WINDOWS.map((w) =>
line({ series: windows[w.key], name: w.name, color: w.color, unit }), line({ series: windows[w.key], name: w.name, color: w.color, unit }),
), ),
}, },
...ROLLING_WINDOWS.map((w) => ({ ...ROLLING_WINDOWS.map((w) => ({
name: w.name, name: w.name,
title: `${title} ${w.title} Average`, title: title(`${w.name} ${metric}`),
bottom: [ bottom: [
line({ series: windows[w.key], name: w.name, color: w.color, unit }), 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 {Object} args
* @param {Record<string, any>} args.pattern - Pattern with pct10/pct25/... and average/median/... as _1y24h30d7dPattern * @param {Record<string, any>} 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 {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 * @param {Unit} args.unit
* @returns {PartialOptionsGroup} * @returns {PartialOptionsGroup}
*/ */
export function distributionWindowsTree({ pattern, base, title, unit }) { export function distributionWindowsTree({ pattern, base, title = (s) => s, metric, unit }) {
return { return {
name: "Distribution", name: "Distribution",
tree: [ tree: [
{ {
name: "Compare", name: "Compare",
title: `${title} Median`, title: title(`${metric} Distribution`),
bottom: ROLLING_WINDOWS.map((w) => bottom: ROLLING_WINDOWS.map((w) =>
line({ line({
series: pattern.median[w.key], series: pattern.median[w.key],
@@ -677,7 +673,7 @@ export function distributionWindowsTree({ pattern, base, title, unit }) {
}, },
...ROLLING_WINDOWS.map((w) => ({ ...ROLLING_WINDOWS.map((w) => ({
name: w.name, name: w.name,
title: `${title} Distribution (${w.title})`, title: title(`${w.name} ${metric} Distribution`),
bottom: [ bottom: [
...(base ? [line({ series: base, name: "base", unit })] : []), ...(base ? [line({ series: base, name: "base", unit })] : []),
...percentileSeries({ pattern: statsAtWindow(pattern, w.key), 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) * Rolling folder tree with percentRatio series (colored in compare, plain in individual)
* @param {Object} args * @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 {{ _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] * @param {string} [args.name]
* @returns {PartialOptionsGroup} * @returns {PartialOptionsGroup}
*/ */
export function rollingPercentRatioTree({ windows, title, name = "Sums" }) { export function rollingPercentRatioTree({
windows,
title = (s) => s,
metric,
name = "Sums",
}) {
return { return {
name, name,
tree: [ tree: [
{ {
name: "Compare", name: "Compare",
title: `${title} Rolling`, title: title(metric),
bottom: ROLLING_WINDOWS.flatMap((w) => 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) => ({ ...ROLLING_WINDOWS.map((w) => ({
name: w.name, name: w.name,
title: `${title} (${w.title})`, title: title(`${w.name} ${metric}`),
bottom: percentRatioBaseline({ pattern: windows[w.key], name: w.name }), bottom: percentRatioBaseline({ pattern: windows[w.key], name: w.name }),
})), })),
], ],
@@ -876,19 +882,20 @@ export function rollingPercentRatioTree({ windows, title, name = "Sums" }) {
* @template T * @template T
* @param {Object} args * @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 {{ 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 {Unit} args.unit
* @param {(v: T) => AnySeriesPattern} args.extract * @param {(v: T) => AnySeriesPattern} args.extract
* @returns {PartialOptionsTree} * @returns {PartialOptionsTree}
*/ */
export function deltaTree({ delta, title, unit, extract }) { export function deltaTree({ delta, title = (s) => s, metric, unit, extract }) {
return [ return [
{ {
name: "Change", name: "Change",
tree: [ tree: [
{ {
name: "Compare", name: "Compare",
title: `${title} Change`, title: title(`${metric} Change`),
bottom: ROLLING_WINDOWS.map((w) => bottom: ROLLING_WINDOWS.map((w) =>
baseline({ baseline({
series: extract(delta.absolute[w.key]), series: extract(delta.absolute[w.key]),
@@ -900,7 +907,7 @@ export function deltaTree({ delta, title, unit, extract }) {
}, },
...ROLLING_WINDOWS.map((w) => ({ ...ROLLING_WINDOWS.map((w) => ({
name: w.name, name: w.name,
title: `${title} Change (${w.title})`, title: title(`${w.name} ${metric} Change`),
bottom: [ bottom: [
baseline({ baseline({
series: extract(delta.absolute[w.key]), series: extract(delta.absolute[w.key]),
@@ -913,7 +920,8 @@ export function deltaTree({ delta, title, unit, extract }) {
}, },
rollingPercentRatioTree({ rollingPercentRatioTree({
windows: delta.rate, windows: delta.rate,
title: `${title} Growth Rate`, title,
metric: `${metric} Growth Rate`,
name: "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) * deltaTree where absolute windows are directly AnySeriesPattern (no extract needed)
* @param {Object} args * @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 {{ 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 * @param {Unit} args.unit
* @returns {PartialOptionsTree} * @returns {PartialOptionsTree}
*/ */
export function simpleDeltaTree({ delta, title, unit }) { export function simpleDeltaTree({ delta, title = (s) => s, metric, unit }) {
return deltaTree({ delta, title, unit, extract: (v) => v }); 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) * Pattern has: .height, .cumulative, .sum (windowed), .average/.pct10/... (windowed, flat)
* @param {Object} args * @param {Object} args
* @param {FullPerBlockPattern} args.pattern * @param {FullPerBlockPattern} args.pattern
* @param {string} args.title * @param {(metric: string) => string} [args.title]
* @param {string} args.metric
* @param {Unit} args.unit * @param {Unit} args.unit
* @param {string} [args.distributionSuffix] * @param {string} [args.distributionSuffix]
* @returns {PartialOptionsTree} * @returns {PartialOptionsTree}
*/ */
export function chartsFromFull({ export function chartsFromFull({
pattern, pattern,
title, title = (s) => s,
metric,
unit, unit,
distributionSuffix = "", distributionSuffix = "",
}) { }) {
const distTitle = distributionSuffix const distMetric = distributionSuffix
? `${title} ${distributionSuffix}` ? `${metric} ${distributionSuffix}`
: title; : metric;
return [ return [
...sumsAndAveragesCumulative({ ...sumsAndAveragesCumulative({
sum: pattern.sum, sum: pattern.sum,
average: pattern.average, average: pattern.average,
cumulative: pattern.cumulative, cumulative: pattern.cumulative,
title, title,
metric,
unit, 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 * Split pattern into 4 charts with "per Block" in distribution title
* @param {Object} args * @param {Object} args
* @param {FullPerBlockPattern} args.pattern * @param {FullPerBlockPattern} args.pattern
* @param {string} args.title * @param {(metric: string) => string} [args.title]
* @param {string} args.metric
* @param {Unit} args.unit * @param {Unit} args.unit
* @returns {PartialOptionsTree} * @returns {PartialOptionsTree}
*/ */
@@ -982,31 +995,35 @@ export const chartsFromFullPerBlock = (args) =>
* Split pattern with sum + distribution + cumulative into 3 charts (no base) * Split pattern with sum + distribution + cumulative into 3 charts (no base)
* @param {Object} args * @param {Object} args
* @param {AggregatedPattern} args.pattern * @param {AggregatedPattern} args.pattern
* @param {string} args.title * @param {(metric: string) => string} [args.title]
* @param {string} args.metric
* @param {Unit} args.unit * @param {Unit} args.unit
* @param {string} [args.distributionSuffix] * @param {string} [args.distributionSuffix]
* @returns {PartialOptionsTree} * @returns {PartialOptionsTree}
*/ */
export function chartsFromAggregated({ export function chartsFromAggregated({
pattern, pattern,
title, title = (s) => s,
metric,
unit, unit,
distributionSuffix = "", distributionSuffix = "",
}) { }) {
const distTitle = distributionSuffix const distMetric = distributionSuffix
? `${title} ${distributionSuffix}` ? `${metric} ${distributionSuffix}`
: title; : metric;
return [ return [
...sumsAndAveragesCumulative({ ...sumsAndAveragesCumulative({
sum: pattern.rolling.sum, sum: pattern.rolling.sum,
average: pattern.rolling.average, average: pattern.rolling.average,
cumulative: pattern.cumulative, cumulative: pattern.cumulative,
title, title,
metric,
unit, unit,
}), }),
distributionWindowsTree({ distributionWindowsTree({
pattern: pattern.rolling, pattern: pattern.rolling,
title: distTitle, title,
metric: distMetric,
unit, unit,
}), }),
]; ];
@@ -1016,7 +1033,8 @@ export function chartsFromAggregated({
* Split pattern into 3 charts with "per Block" in distribution title (no base) * Split pattern into 3 charts with "per Block" in distribution title (no base)
* @param {Object} args * @param {Object} args
* @param {AggregatedPattern} args.pattern * @param {AggregatedPattern} args.pattern
* @param {string} args.title * @param {(metric: string) => string} [args.title]
* @param {string} args.metric
* @param {Unit} args.unit * @param {Unit} args.unit
* @returns {PartialOptionsTree} * @returns {PartialOptionsTree}
*/ */
@@ -1027,20 +1045,21 @@ export const chartsFromAggregatedPerBlock = (args) =>
* Create Per Block + Per 6 Blocks stats charts from a _6bBlockTxPattern * Create Per Block + Per 6 Blocks stats charts from a _6bBlockTxPattern
* @param {Object} args * @param {Object} args
* @param {{ block: DistributionStats, _6b: DistributionStats }} args.pattern * @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 * @param {Unit} args.unit
* @returns {PartialOptionsTree} * @returns {PartialOptionsTree}
*/ */
export function chartsFromBlockAnd6b({ pattern, title, unit }) { export function chartsFromBlockAnd6b({ pattern, title = (s) => s, metric, unit }) {
return [ return [
{ {
name: "Block", name: "Block",
title: `${title} (Block)`, title: title(`${metric} (Block)`),
bottom: percentileSeries({ pattern: pattern.block, unit }), bottom: percentileSeries({ pattern: pattern.block, unit }),
}, },
{ {
name: "Hourly", name: "~Hourly",
title: `${title} (Hourly)`, title: title(`${metric} (~Hourly)`),
bottom: percentileSeries({ pattern: pattern._6b, unit }), bottom: percentileSeries({ pattern: pattern._6b, unit }),
}, },
]; ];
@@ -1050,17 +1069,19 @@ export function chartsFromBlockAnd6b({ pattern, title, unit }) {
* Averages + Sums + Cumulative charts * Averages + Sums + Cumulative charts
* @param {Object} args * @param {Object} args
* @param {CountPattern<any>} args.pattern * @param {CountPattern<any>} args.pattern
* @param {string} args.title * @param {(metric: string) => string} [args.title]
* @param {string} args.metric
* @param {Unit} args.unit * @param {Unit} args.unit
* @param {Color} [args.color] * @param {Color} [args.color]
* @returns {PartialOptionsTree} * @returns {PartialOptionsTree}
*/ */
export function chartsFromCount({ pattern, title, unit, color }) { export function chartsFromCount({ pattern, title = (s) => s, metric, unit, color }) {
return sumsAndAveragesCumulative({ return sumsAndAveragesCumulative({
sum: pattern.sum, sum: pattern.sum,
average: pattern.average, average: pattern.average,
cumulative: pattern.cumulative, cumulative: pattern.cumulative,
title, title,
metric,
unit, unit,
color, color,
}); });
@@ -1070,19 +1091,12 @@ export function chartsFromCount({ pattern, title, unit, color }) {
* Windowed sums + cumulative for multiple named entries (e.g. transaction versions) * Windowed sums + cumulative for multiple named entries (e.g. transaction versions)
* @param {Object} args * @param {Object} args
* @param {Array<[string, CountPattern<any>]>} args.entries * @param {Array<[string, CountPattern<any>]>} args.entries
* @param {string} args.title * @param {(metric: string) => string} [args.title]
* @param {string} args.metric
* @param {Unit} args.unit * @param {Unit} args.unit
* @returns {PartialOptionsTree} * @returns {PartialOptionsTree}
*/ */
/** export function chartsFromCountEntries({ entries, title = (s) => s, metric, unit }) {
* Windowed sums + cumulative for multiple named entries (e.g. transaction versions)
* @param {Object} args
* @param {Array<[string, CountPattern<any>]>} args.entries
* @param {string} args.title
* @param {Unit} args.unit
* @returns {PartialOptionsTree}
*/
export function chartsFromCountEntries({ entries, title, unit }) {
const items = entries.map(([name, data], i, arr) => ({ const items = entries.map(([name, data], i, arr) => ({
name, name,
color: colors.at(i, arr.length), color: colors.at(i, arr.length),
@@ -1092,14 +1106,14 @@ export function chartsFromCountEntries({ entries, title, unit }) {
return [ return [
...ROLLING_WINDOWS.map((w) => ({ ...ROLLING_WINDOWS.map((w) => ({
name: w.name, name: w.name,
title: `${title} ${w.title} Sum`, title: title(`${w.name} ${metric}`),
bottom: items.map((e) => bottom: items.map((e) =>
line({ series: e.sum[w.key], name: e.name, color: e.color, unit }), line({ series: e.sum[w.key], name: e.name, color: e.color, unit }),
), ),
})), })),
{ {
name: "Cumulative", name: "Cumulative",
title: `${title} (Total)`, title: title(`Cumulative ${metric}`),
bottom: items.map((e) => bottom: items.map((e) =>
line({ series: e.cumulative, name: e.name, color: e.color, unit }), 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) * Windowed averages + sums + cumulative for multiple named series (e.g. UTXO flow)
* @param {Object} args * @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 {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 * @param {Unit} args.unit
* @returns {PartialOptionsTree} * @returns {PartialOptionsTree}
*/ */
export function multiSeriesTree({ entries, title, unit }) { export function multiSeriesTree({ entries, title = (s) => s, metric, unit }) {
return [ return [
...ROLLING_WINDOWS.map((w) => ({ ...ROLLING_WINDOWS.map((w) => ({
name: w.name, name: w.name,
title: `${title} ${w.title} Averages`, title: title(`${w.name} ${metric}`),
bottom: entries.map((e) => bottom: entries.map((e) =>
line({ series: e.average[w.key], name: e.name, color: e.color, unit }), line({ series: e.average[w.key], name: e.name, color: e.color, unit }),
), ),
})), })),
{ {
name: "Cumulative", name: "Cumulative",
title: `${title} (Total)`, title: title(`Cumulative ${metric}`),
bottom: entries.map((e) => bottom: entries.map((e) =>
line({ series: e.cumulative, name: e.name, color: e.color, unit }), line({ series: e.cumulative, name: e.name, color: e.color, unit }),
), ),
}, },
]; ];
} }

View File

@@ -219,16 +219,18 @@ export function satsBtcUsdRolling({ pattern, name, color, defaultActive }) {
* Build a full Sum / Rolling / Cumulative tree from a FullValuePattern * Build a full Sum / Rolling / Cumulative tree from a FullValuePattern
* @param {Object} args * @param {Object} args
* @param {FullValuePattern} args.pattern * @param {FullValuePattern} args.pattern
* @param {string} args.title * @param {(metric: string) => string} [args.title]
* @param {string} args.metric
* @param {Color} [args.color] * @param {Color} [args.color]
* @returns {PartialOptionsTree} * @returns {PartialOptionsTree}
*/ */
export function satsBtcUsdFullTree({ pattern, title, color }) { export function satsBtcUsdFullTree({ pattern, title, metric, color }) {
return sumsAndAveragesCumulativeWith({ return sumsAndAveragesCumulativeWith({
sum: pattern.sum, sum: pattern.sum,
average: pattern.average, average: pattern.average,
cumulative: pattern.cumulative, cumulative: pattern.cumulative,
title, title,
metric,
color, color,
series: ({ pattern, name, color, defaultActive }) => series: ({ pattern, name, color, defaultActive }) =>
satsBtcUsd({ pattern, name, color, defaultActive }), satsBtcUsd({ pattern, name, color, defaultActive }),