mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-24 06:39:58 -07:00
global: snapshot part 17
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<()>;
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}),
|
||||
];
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
)?;
|
||||
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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()?;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}),
|
||||
|
||||
@@ -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",
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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"),
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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 }),
|
||||
),
|
||||
|
||||
@@ -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,
|
||||
],
|
||||
|
||||
@@ -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" },
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}),
|
||||
],
|
||||
})),
|
||||
],
|
||||
},
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 }),
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -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 }),
|
||||
|
||||
Reference in New Issue
Block a user