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

View File

@@ -232,9 +232,10 @@ impl CohortVecs for AddrCohortVecs {
&mut self,
prices: &prices::Vecs,
starting_indexes: &Indexes,
all_utxo_count: &impl ReadableVec<Height, StoredU64>,
exit: &Exit,
) -> Result<()> {
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_types::{Cents, Height, Indexes, Version};
use vecdb::Exit;
use brk_types::{Cents, Height, Indexes, StoredU64, Version};
use vecdb::{Exit, ReadableVec};
use crate::prices;
@@ -62,6 +62,7 @@ pub trait CohortVecs: DynCohortVecs {
&mut self,
prices: &prices::Vecs,
starting_indexes: &Indexes,
all_utxo_count: &impl ReadableVec<Height, StoredU64>,
exit: &Exit,
) -> Result<()>;
}

View File

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

View File

@@ -5,7 +5,7 @@ use brk_types::{
Cents, Dollars, Height, Indexes, Version,
};
use vecdb::AnyStoredVec;
use vecdb::{Exit, ReadableVec, Rw, StorageMode};
use vecdb::{Exit, ReadOnlyClone, ReadableVec, Rw, StorageMode};
use crate::{
blocks,
@@ -135,6 +135,13 @@ impl AllCohortMetrics {
exit,
)?;
let all_utxo_count = self.outputs.unspent_count.height.read_only_clone();
self.outputs.compute_part2(
starting_indexes.height,
&all_utxo_count,
exit,
)?;
self.cost_basis.compute_prices(
starting_indexes,
&prices.spot.cents.height,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

@@ -41,7 +41,8 @@ function volumeTree(tv, color, title) {
return [
...satsBtcUsdFullTree({
pattern: tv,
title: title("Transfer Volume"),
title,
metric: "Transfer Volume",
color,
}),
{
@@ -83,7 +84,8 @@ function volumeTree(tv, color, title) {
name: "In Profit",
tree: satsBtcUsdFullTree({
pattern: tv.inProfit,
title: title("Transfer Volume In Profit"),
title,
metric: "Transfer Volume In Profit",
color: colors.profit,
}),
},
@@ -91,7 +93,8 @@ function volumeTree(tv, color, title) {
name: "In Loss",
tree: satsBtcUsdFullTree({
pattern: tv.inLoss,
title: title("Transfer Volume In Loss"),
title,
metric: "Transfer Volume In Loss",
color: colors.loss,
}),
},
@@ -122,7 +125,7 @@ function volumeFolderWithAdjusted(activity, adjustedTransferVolume, color, title
name: "Volume",
tree: [
...volumeTree(activity.transferVolume, color, title),
{ name: "Adjusted", tree: chartsFromCount({ pattern: adjustedTransferVolume, title: title("Adjusted Transfer Volume"), unit: Unit.usd }) },
{ name: "Adjusted", tree: chartsFromCount({ pattern: adjustedTransferVolume, title, metric: "Adjusted Transfer Volume", unit: Unit.usd }) },
],
};
}
@@ -173,7 +176,7 @@ function singleRollingSoprTree(ratio, title, prefix = "") {
* @returns {PartialOptionsTree}
*/
function valueDestroyedTree(valueDestroyed, title) {
return chartsFromCount({ pattern: valueDestroyed, title: title("Value Destroyed"), unit: Unit.usd });
return chartsFromCount({ pattern: valueDestroyed, title, metric: "Value Destroyed", unit: Unit.usd });
}
/**
@@ -196,7 +199,7 @@ function valueDestroyedFolderWithAdjusted(valueDestroyed, adjusted, title) {
name: "Value Destroyed",
tree: [
...valueDestroyedTree(valueDestroyed, title),
{ name: "Adjusted", tree: chartsFromCount({ pattern: adjusted, title: title("Adjusted Value Destroyed"), unit: Unit.usd }) },
{ name: "Adjusted", tree: chartsFromCount({ pattern: adjusted, title, metric: "Adjusted Value Destroyed", unit: Unit.usd }) },
],
};
}
@@ -258,7 +261,8 @@ function singleFullActivityTree(cohort, title, volumeItem, soprFolder, valueDest
name: "Coindays Destroyed",
tree: chartsFromCount({
pattern: tree.activity.coindaysDestroyed,
title: title("Coindays Destroyed"),
title,
metric: "Coindays Destroyed",
unit: Unit.coindays,
color,
}),
@@ -267,7 +271,8 @@ function singleFullActivityTree(cohort, title, volumeItem, soprFolder, valueDest
name: "Dormancy",
tree: averagesArray({
windows: tree.activity.dormancy,
title: title("Dormancy"),
title,
metric: "Dormancy",
unit: Unit.days,
}),
},
@@ -341,7 +346,8 @@ export function createActivitySectionWithActivity({ cohort, title }) {
name: "Coindays Destroyed",
tree: chartsFromCount({
pattern: tree.activity.coindaysDestroyed,
title: title("Coindays Destroyed"),
title,
metric: "Coindays Destroyed",
unit: Unit.coindays,
color,
}),
@@ -360,7 +366,8 @@ export function createActivitySectionMinimal({ cohort, title }) {
name: "Activity",
tree: satsBtcUsdFullTree({
pattern: cohort.tree.activity.transferVolume,
title: title("Transfer Volume"),
title,
metric: "Transfer Volume",
}),
};
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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