diff --git a/crates/brk_client/src/lib.rs b/crates/brk_client/src/lib.rs index 71d4d54fb..715b43975 100644 --- a/crates/brk_client/src/lib.rs +++ b/crates/brk_client/src/lib.rs @@ -3831,7 +3831,6 @@ pub struct SeriesTree_Scripts { pub raw: SeriesTree_Scripts_Raw, pub count: SeriesTree_Scripts_Count, pub value: SeriesTree_Scripts_Value, - pub adoption: SeriesTree_Scripts_Adoption, } impl SeriesTree_Scripts { @@ -3840,7 +3839,6 @@ impl SeriesTree_Scripts { raw: SeriesTree_Scripts_Raw::new(client.clone(), format!("{base_path}_raw")), count: SeriesTree_Scripts_Count::new(client.clone(), format!("{base_path}_count")), value: SeriesTree_Scripts_Value::new(client.clone(), format!("{base_path}_value")), - adoption: SeriesTree_Scripts_Adoption::new(client.clone(), format!("{base_path}_adoption")), } } } @@ -3938,7 +3936,6 @@ pub struct SeriesTree_Scripts_Count { pub op_return: BaseCumulativeSumPattern, pub empty_output: BaseCumulativeSumPattern, pub unknown_output: BaseCumulativeSumPattern, - pub segwit: BaseCumulativeSumPattern, } impl SeriesTree_Scripts_Count { @@ -3956,7 +3953,6 @@ impl SeriesTree_Scripts_Count { op_return: BaseCumulativeSumPattern::new(client.clone(), "op_return_count".to_string()), empty_output: BaseCumulativeSumPattern::new(client.clone(), "empty_output_count".to_string()), unknown_output: BaseCumulativeSumPattern::new(client.clone(), "unknown_output_count".to_string()), - segwit: BaseCumulativeSumPattern::new(client.clone(), "segwit_count".to_string()), } } } @@ -3974,21 +3970,6 @@ impl SeriesTree_Scripts_Value { } } -/// Series tree node. -pub struct SeriesTree_Scripts_Adoption { - pub taproot: BpsPercentRatioPattern3, - pub segwit: BpsPercentRatioPattern3, -} - -impl SeriesTree_Scripts_Adoption { - pub fn new(client: Arc, base_path: String) -> Self { - Self { - taproot: BpsPercentRatioPattern3::new(client.clone(), "taproot_adoption".to_string()), - segwit: BpsPercentRatioPattern3::new(client.clone(), "segwit_adoption".to_string()), - } - } -} - /// Series tree node. pub struct SeriesTree_Mining { pub rewards: SeriesTree_Mining_Rewards, @@ -4009,7 +3990,7 @@ pub struct SeriesTree_Mining_Rewards { pub coinbase: BaseCumulativeSumPattern4, pub subsidy: SeriesTree_Mining_Rewards_Subsidy, pub fees: SeriesTree_Mining_Rewards_Fees, - pub unclaimed: BaseCumulativeSumPattern4, + pub unclaimed: SeriesTree_Mining_Rewards_Unclaimed, } impl SeriesTree_Mining_Rewards { @@ -4018,7 +3999,7 @@ impl SeriesTree_Mining_Rewards { coinbase: BaseCumulativeSumPattern4::new(client.clone(), "coinbase".to_string()), subsidy: SeriesTree_Mining_Rewards_Subsidy::new(client.clone(), format!("{base_path}_subsidy")), fees: SeriesTree_Mining_Rewards_Fees::new(client.clone(), format!("{base_path}_fees")), - unclaimed: BaseCumulativeSumPattern4::new(client.clone(), "unclaimed_rewards".to_string()), + unclaimed: SeriesTree_Mining_Rewards_Unclaimed::new(client.clone(), format!("{base_path}_unclaimed")), } } } @@ -4098,6 +4079,21 @@ impl SeriesTree_Mining_Rewards_Fees_ToSubsidyRatio { } } +/// Series tree node. +pub struct SeriesTree_Mining_Rewards_Unclaimed { + pub base: BtcCentsSatsUsdPattern, + pub cumulative: BtcCentsSatsUsdPattern, +} + +impl SeriesTree_Mining_Rewards_Unclaimed { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + base: BtcCentsSatsUsdPattern::new(client.clone(), "unclaimed_rewards".to_string()), + cumulative: BtcCentsSatsUsdPattern::new(client.clone(), "unclaimed_rewards_cumulative".to_string()), + } + } +} + /// Series tree node. pub struct SeriesTree_Mining_Hashrate { pub rate: SeriesTree_Mining_Hashrate_Rate, @@ -4279,10 +4275,6 @@ pub struct SeriesTree_Cointime_Prices { pub active: BpsCentsPercentilesRatioSatsUsdPattern, pub true_market_mean: BpsCentsPercentilesRatioSatsUsdPattern, pub cointime: BpsCentsPercentilesRatioSatsUsdPattern, - pub transfer: BpsCentsPercentilesRatioSatsUsdPattern, - pub balanced: BpsCentsPercentilesRatioSatsUsdPattern, - pub terminal: BpsCentsPercentilesRatioSatsUsdPattern, - pub delta: BpsCentsPercentilesRatioSatsUsdPattern, } impl SeriesTree_Cointime_Prices { @@ -4292,10 +4284,6 @@ impl SeriesTree_Cointime_Prices { active: BpsCentsPercentilesRatioSatsUsdPattern::new(client.clone(), "active_price".to_string()), true_market_mean: BpsCentsPercentilesRatioSatsUsdPattern::new(client.clone(), "true_market_mean".to_string()), cointime: BpsCentsPercentilesRatioSatsUsdPattern::new(client.clone(), "cointime_price".to_string()), - transfer: BpsCentsPercentilesRatioSatsUsdPattern::new(client.clone(), "transfer_price".to_string()), - balanced: BpsCentsPercentilesRatioSatsUsdPattern::new(client.clone(), "balanced_price".to_string()), - terminal: BpsCentsPercentilesRatioSatsUsdPattern::new(client.clone(), "terminal_price".to_string()), - delta: BpsCentsPercentilesRatioSatsUsdPattern::new(client.clone(), "delta_price".to_string()), } } } diff --git a/crates/brk_computer/src/blocks/difficulty/compute.rs b/crates/brk_computer/src/blocks/difficulty/compute.rs index 7ae7ea4de..5c213ae15 100644 --- a/crates/brk_computer/src/blocks/difficulty/compute.rs +++ b/crates/brk_computer/src/blocks/difficulty/compute.rs @@ -17,7 +17,7 @@ impl Vecs { self.adjustment.bps.height.compute_ratio_change( starting_indexes.height, &indexer.vecs.blocks.difficulty, - 1, + 2016, exit, )?; diff --git a/crates/brk_computer/src/blocks/difficulty/import.rs b/crates/brk_computer/src/blocks/difficulty/import.rs index 79536857c..95814b107 100644 --- a/crates/brk_computer/src/blocks/difficulty/import.rs +++ b/crates/brk_computer/src/blocks/difficulty/import.rs @@ -52,7 +52,7 @@ impl Vecs { adjustment: PercentPerBlock::forced_import( db, "difficulty_adjustment", - version, + version + Version::ONE, indexes, )?, epoch: PerBlock::forced_import(db, "difficulty_epoch", version, indexes)?, diff --git a/crates/brk_computer/src/cointime/cap/compute.rs b/crates/brk_computer/src/cointime/cap/compute.rs index 76d7aa2e5..ca1b2f581 100644 --- a/crates/brk_computer/src/cointime/cap/compute.rs +++ b/crates/brk_computer/src/cointime/cap/compute.rs @@ -1,5 +1,5 @@ use brk_error::Result; -use brk_types::{Cents, Indexes}; +use brk_types::{Dollars, Indexes}; use vecdb::Exit; use super::super::{activity, value}; @@ -59,7 +59,8 @@ impl Vecs { let destroyed: f64 = *destroyed; let supply: f64 = supply.into(); let stored: f64 = *stored; - (i, Cents::from(destroyed * supply / stored)) + let usd = Dollars::from(destroyed * supply / stored); + (i, usd.to_cents()) }, exit, )?; diff --git a/crates/brk_computer/src/cointime/cap/import.rs b/crates/brk_computer/src/cointime/cap/import.rs index 568d4c6d3..59f367efe 100644 --- a/crates/brk_computer/src/cointime/cap/import.rs +++ b/crates/brk_computer/src/cointime/cap/import.rs @@ -3,7 +3,10 @@ use brk_types::Version; use vecdb::Database; use super::Vecs; -use crate::{indexes, internal::{FiatPerBlock, RatioPerBlock}}; +use crate::{ + indexes, + internal::{FiatPerBlock, RatioPerBlock}, +}; impl Vecs { pub(crate) fn forced_import( @@ -16,7 +19,12 @@ impl Vecs { investor: FiatPerBlock::forced_import(db, "investor_cap", version, indexes)?, vaulted: FiatPerBlock::forced_import(db, "vaulted_cap", version, indexes)?, active: FiatPerBlock::forced_import(db, "active_cap", version, indexes)?, - cointime: FiatPerBlock::forced_import(db, "cointime_cap", version, indexes)?, + cointime: FiatPerBlock::forced_import( + db, + "cointime_cap", + version + Version::ONE, + indexes, + )?, aviv: RatioPerBlock::forced_import(db, "aviv", version, indexes)?, }) } diff --git a/crates/brk_computer/src/cointime/import.rs b/crates/brk_computer/src/cointime/import.rs index 782b0d41c..39df2e410 100644 --- a/crates/brk_computer/src/cointime/import.rs +++ b/crates/brk_computer/src/cointime/import.rs @@ -28,8 +28,8 @@ impl Vecs { let activity = ActivityVecs::forced_import(&db, version, indexes, cached_starts)?; let supply = SupplyVecs::forced_import(&db, v1, indexes)?; let value = ValueVecs::forced_import(&db, v1, indexes, cached_starts)?; - let cap = CapVecs::forced_import(&db, v1, indexes)?; - let prices = PricesVecs::forced_import(&db, version, indexes)?; + let cap = CapVecs::forced_import(&db, version + Version::TWO, indexes)?; + let prices = PricesVecs::forced_import(&db, version + Version::new(3), indexes)?; let adjusted = AdjustedVecs::forced_import(&db, version, indexes)?; let reserve_risk = ReserveRiskVecs::forced_import(&db, v1, indexes)?; diff --git a/crates/brk_computer/src/cointime/prices/compute.rs b/crates/brk_computer/src/cointime/prices/compute.rs index 3136f14b7..997c24e35 100644 --- a/crates/brk_computer/src/cointime/prices/compute.rs +++ b/crates/brk_computer/src/cointime/prices/compute.rs @@ -1,6 +1,6 @@ use brk_error::Result; use brk_types::{Cents, Indexes}; -use vecdb::{Exit, VecIndex}; +use vecdb::Exit; use super::super::{activity, cap, supply}; use super::Vecs; @@ -21,7 +21,6 @@ impl Vecs { let all_metrics = &distribution.utxo_cohorts.all.metrics; let circulating_supply = &all_metrics.supply.total.btc.height; let realized_price = &all_metrics.realized.price.cents.height; - let realized_cap = &all_metrics.realized.cap.cents.height; self.vaulted.compute_all( prices, @@ -45,10 +44,13 @@ impl Vecs { starting_indexes, exit, |v| { - Ok(v.compute_multiply( + Ok(v.compute_transform2( starting_indexes.height, realized_price, &activity.liveliness.height, + |(i, price, liveliness, ..)| { + (i, Cents::from(f64::from(price) / f64::from(liveliness))) + }, exit, )?) }, @@ -71,7 +73,6 @@ impl Vecs { }, )?; - // cointime_price = cointime_cap / circulating_supply self.cointime.compute_all( prices, starting_indexes, @@ -89,72 +90,6 @@ impl Vecs { }, )?; - // transfer_price = cointime_price - vaulted_price - self.transfer.cents.height.compute_transform2( - starting_indexes.height, - &self.cointime.cents.height, - &self.vaulted.cents.height, - |(i, cointime, vaulted, ..)| (i, cointime.saturating_sub(vaulted)), - exit, - )?; - self.transfer.compute_rest(prices, starting_indexes, exit)?; - - // balanced_price = (realized_price + transfer_price) / 2 - self.balanced.cents.height.compute_transform2( - starting_indexes.height, - realized_price, - &self.transfer.cents.height, - |(i, realized, transfer, ..)| (i, (realized + transfer) / 2u64), - exit, - )?; - self.balanced.compute_rest(prices, starting_indexes, exit)?; - - // terminal_price = 21M × transfer_price / circulating_supply_btc - self.terminal.cents.height.compute_transform2( - starting_indexes.height, - &self.transfer.cents.height, - circulating_supply, - |(i, transfer, supply_btc, ..)| { - let supply = f64::from(supply_btc); - if supply == 0.0 { - (i, Cents::ZERO) - } else { - (i, Cents::from(f64::from(transfer) * 21_000_000.0 / supply)) - } - }, - exit, - )?; - self.terminal.compute_rest(prices, starting_indexes, exit)?; - - // cumulative_market_cap = Σ(market_cap) in dollars - self.cumulative_market_cap - .height - .compute_cumulative( - starting_indexes.height, - &all_metrics.supply.total.cents.height, - exit, - )?; - - // delta_price = (realized_cap - average_cap) / circulating_supply - // average_cap = cumulative_market_cap / (height + 1) - self.delta.cents.height.compute_transform3( - starting_indexes.height, - realized_cap, - &self.cumulative_market_cap.height, - circulating_supply, - |(i, realized_cap_cents, cum_mcap_dollars, supply_btc, ..)| { - let supply = f64::from(supply_btc); - if supply == 0.0 { - return (i, Cents::ZERO); - } - let avg_cap_cents = f64::from(cum_mcap_dollars) * 100.0 / (i.to_usize() + 1) as f64; - let delta = (f64::from(realized_cap_cents) - avg_cap_cents) / supply; - (i, Cents::from(delta.max(0.0))) - }, - exit, - )?; - self.delta.compute_rest(prices, starting_indexes, exit)?; - Ok(()) } } diff --git a/crates/brk_computer/src/cointime/prices/import.rs b/crates/brk_computer/src/cointime/prices/import.rs index 1668a9ddb..4fd1faf9b 100644 --- a/crates/brk_computer/src/cointime/prices/import.rs +++ b/crates/brk_computer/src/cointime/prices/import.rs @@ -5,7 +5,7 @@ use vecdb::Database; use super::Vecs; use crate::{ indexes, - internal::{PerBlock, PriceWithRatioExtendedPerBlock}, + internal::PriceWithRatioExtendedPerBlock, }; impl Vecs { @@ -25,16 +25,6 @@ impl Vecs { active: import!("active_price"), true_market_mean: import!("true_market_mean"), cointime: import!("cointime_price"), - transfer: import!("transfer_price"), - balanced: import!("balanced_price"), - terminal: import!("terminal_price"), - delta: import!("delta_price"), - cumulative_market_cap: PerBlock::forced_import( - db, - "cumulative_market_cap", - version, - indexes, - )?, }) } } diff --git a/crates/brk_computer/src/cointime/prices/vecs.rs b/crates/brk_computer/src/cointime/prices/vecs.rs index c665e55a8..4da62e998 100644 --- a/crates/brk_computer/src/cointime/prices/vecs.rs +++ b/crates/brk_computer/src/cointime/prices/vecs.rs @@ -10,11 +10,4 @@ pub struct Vecs { pub active: PriceWithRatioExtendedPerBlock, pub true_market_mean: PriceWithRatioExtendedPerBlock, pub cointime: PriceWithRatioExtendedPerBlock, - pub transfer: PriceWithRatioExtendedPerBlock, - pub balanced: PriceWithRatioExtendedPerBlock, - pub terminal: PriceWithRatioExtendedPerBlock, - pub delta: PriceWithRatioExtendedPerBlock, - - #[traversable(hidden)] - pub cumulative_market_cap: PerBlock, } diff --git a/crates/brk_computer/src/distribution/metrics/realized/full.rs b/crates/brk_computer/src/distribution/metrics/realized/full.rs index 1b298f279..3a1b64971 100644 --- a/crates/brk_computer/src/distribution/metrics/realized/full.rs +++ b/crates/brk_computer/src/distribution/metrics/realized/full.rs @@ -16,7 +16,7 @@ use crate::{ internal::{ CentsUnsignedToDollars, PerBlock, PerBlockCumulative, PerBlockCumulativeWithSums, FiatPerBlockCumulativeWithSums, - LazyPerBlock, PercentPerBlock, PercentRollingWindows, Price, + LazyPerBlock, PercentPerBlock, PercentRollingWindows, PriceWithRatioExtendedPerBlock, RatioCents64, RatioCentsBp32, RatioCentsSignedCentsBps32, RatioCentsSignedDollarsBps32, RatioDollarsBp32, RatioPerBlockPercentiles, RatioPerBlockStdDevBands, RatioSma, RollingWindows, @@ -70,8 +70,6 @@ pub struct RealizedPeakRegret { #[derive(Traversable)] pub struct RealizedInvestor { pub price: PriceWithRatioExtendedPerBlock, - pub investor_lower_band: Price>, - pub investor_upper_band: Price>, #[traversable(hidden)] pub cap_raw: M::Stored>, } @@ -176,8 +174,6 @@ impl RealizedFull { // Investor let investor = RealizedInvestor { price: cfg.import("investor_price", v0)?, - investor_lower_band: cfg.import("investor_lower_band", v0)?, - investor_upper_band: cfg.import("investor_upper_band", v0)?, cap_raw: cfg.import("investor_cap_raw", v0)?, }; @@ -486,46 +482,6 @@ impl RealizedFull { exit, )?; - self.investor - .investor_lower_band - .cents - .height - .compute_transform2( - starting_indexes.height, - &self.core.minimal.price.cents.height, - &self.investor.price.cents.height, - |(i, rp, ip, ..)| { - let rp = rp.as_u128(); - let ip = ip.as_u128(); - if ip == 0 { - (i, Cents::ZERO) - } else { - (i, Cents::from(rp * rp / ip)) - } - }, - exit, - )?; - - self.investor - .investor_upper_band - .cents - .height - .compute_transform2( - starting_indexes.height, - &self.investor.price.cents.height, - &self.core.minimal.price.cents.height, - |(i, ip, rp, ..)| { - let ip = ip.as_u128(); - let rp = rp.as_u128(); - if rp == 0 { - (i, Cents::ZERO) - } else { - (i, Cents::from(ip * ip / rp)) - } - }, - exit, - )?; - // Sell-side risk ratios for (ssrr, rv) in self .sell_side_risk_ratio diff --git a/crates/brk_computer/src/internal/transform/derived.rs b/crates/brk_computer/src/internal/transform/derived.rs index 5a83ff3ba..1271c602d 100644 --- a/crates/brk_computer/src/internal/transform/derived.rs +++ b/crates/brk_computer/src/internal/transform/derived.rs @@ -1,22 +1,8 @@ use std::marker::PhantomData; -use brk_types::{BasisPoints32, Cents, StoredF32, StoredF64, StoredU64, Timestamp}; +use brk_types::{BasisPoints32, Cents, StoredF32, StoredF64}; use vecdb::{BinaryTransform, UnaryTransform}; -pub struct PerSec; - -impl BinaryTransform for PerSec { - #[inline(always)] - fn apply(count: StoredU64, interval: Timestamp) -> StoredF32 { - let interval_f64 = f64::from(*interval); - if interval_f64 > 0.0 { - StoredF32::from(*count as f64 / interval_f64) - } else { - StoredF32::NAN - } - } -} - pub struct DaysToYears; impl UnaryTransform for DaysToYears { diff --git a/crates/brk_computer/src/internal/transform/mod.rs b/crates/brk_computer/src/internal/transform/mod.rs index cc2615590..853f09b8b 100644 --- a/crates/brk_computer/src/internal/transform/mod.rs +++ b/crates/brk_computer/src/internal/transform/mod.rs @@ -6,26 +6,28 @@ mod ratio; mod specialized; pub use arithmetic::{ - BlocksToDaysF32, DifficultyToHashF64, HalveCents, HalveDollars, HalveSats, - HalveSatsToBitcoin, Identity, MaskSats, OneMinusBp16, OneMinusF64, ReturnF32Tenths, ReturnI8, - ReturnU16, ThsToPhsF32, VBytesToWeight, VSizeToWeight, + BlocksToDaysF32, DifficultyToHashF64, HalveCents, HalveDollars, HalveSats, HalveSatsToBitcoin, + Identity, MaskSats, OneMinusBp16, OneMinusF64, ReturnF32Tenths, ReturnI8, ReturnU16, + ThsToPhsF32, VBytesToWeight, VSizeToWeight, }; pub use bps::{ - Bp16ToFloat, Bp16ToPercent, Bp32ToFloat, Bp32ToPercent, Bps16ToFloat, Bps16ToPercent, Bps32ToFloat, - Bps32ToPercent, + Bp16ToFloat, Bp16ToPercent, Bp32ToFloat, Bp32ToPercent, Bps16ToFloat, Bps16ToPercent, + Bps32ToFloat, Bps32ToPercent, }; pub use currency::{ - CentsSignedToDollars, CentsSubtractToCentsSigned, CentsTimesTenths, - CentsUnsignedToDollars, CentsUnsignedToSats, DollarsToSatsFract, NegCentsUnsignedToDollars, - SatsToBitcoin, SatsToCents, + CentsSignedToDollars, CentsSubtractToCentsSigned, CentsTimesTenths, CentsUnsignedToDollars, + CentsUnsignedToSats, DollarsToSatsFract, NegCentsUnsignedToDollars, SatsToBitcoin, SatsToCents, }; pub use derived::{ - Days1, Days7, Days30, Days365, DaysToYears, PerSec, PriceTimesRatioBp32Cents, PriceTimesRatioCents, + Days1, Days7, Days30, Days365, DaysToYears, PriceTimesRatioBp32Cents, PriceTimesRatioCents, RatioCents64, TimesSqrt, }; pub use ratio::{ - RatioCentsBp32, RatioCentsSignedCentsBps32, - RatioCentsSignedDollarsBps32, RatioDiffCentsBps32, RatioDiffDollarsBps32, RatioDiffF32Bps32, - RatioDollarsBp16, RatioDollarsBp32, RatioDollarsBps32, RatioSatsBp16, RatioU64Bp16, + RatioCentsBp32, RatioCentsSignedCentsBps32, RatioCentsSignedDollarsBps32, RatioDiffCentsBps32, + RatioDiffDollarsBps32, RatioDiffF32Bps32, RatioDollarsBp16, RatioDollarsBp32, + RatioDollarsBps32, RatioSatsBp16, RatioU64Bp16, +}; +pub use specialized::{ + BlockCountTarget1m, BlockCountTarget1w, BlockCountTarget1y, BlockCountTarget24h, + OhlcCentsToDollars, OhlcCentsToSats, }; -pub use specialized::{BlockCountTarget24h, BlockCountTarget1w, BlockCountTarget1m, BlockCountTarget1y, OhlcCentsToDollars, OhlcCentsToSats}; diff --git a/crates/brk_computer/src/lib.rs b/crates/brk_computer/src/lib.rs index 656cbaedb..d2afd36c4 100644 --- a/crates/brk_computer/src/lib.rs +++ b/crates/brk_computer/src/lib.rs @@ -349,7 +349,6 @@ impl Computer { timed("Computed scripts", || { self.scripts.compute( indexer, - &self.outputs, &self.prices, &starting_indexes, exit, diff --git a/crates/brk_computer/src/mining/rewards/compute.rs b/crates/brk_computer/src/mining/rewards/compute.rs index 482dba673..ebd12e352 100644 --- a/crates/brk_computer/src/mining/rewards/compute.rs +++ b/crates/brk_computer/src/mining/rewards/compute.rs @@ -97,24 +97,17 @@ impl Vecs { )?; self.subsidy.compute(prices, starting_indexes.height, exit)?; - self.unclaimed.compute( + self.unclaimed.base.sats.height.compute_transform( starting_indexes.height, - prices, - exit, - |vec| { - vec.compute_transform( - starting_indexes.height, - &self.subsidy.base.sats.height, - |(height, subsidy, ..)| { - let halving = Halving::from(height); - let expected = Sats::FIFTY_BTC / 2_usize.pow(halving.to_usize() as u32); - (height, expected.checked_sub(subsidy).unwrap()) - }, - exit, - )?; - Ok(()) + &self.subsidy.base.sats.height, + |(height, subsidy, ..)| { + let halving = Halving::from(height); + let expected = Sats::FIFTY_BTC / 2_usize.pow(halving.to_usize() as u32); + (height, expected.checked_sub(subsidy).unwrap()) }, + exit, )?; + self.unclaimed.compute(prices, starting_indexes.height, exit)?; self.fee_dominance .compute_binary::( diff --git a/crates/brk_computer/src/mining/rewards/import.rs b/crates/brk_computer/src/mining/rewards/import.rs index 38f2f694b..207bc427d 100644 --- a/crates/brk_computer/src/mining/rewards/import.rs +++ b/crates/brk_computer/src/mining/rewards/import.rs @@ -38,12 +38,11 @@ impl Vecs { )?, subsidy: AmountPerBlockCumulative::forced_import(db, "subsidy", version, indexes)?, fees: AmountPerBlockFull::forced_import(db, "fees", version, indexes, cached_starts)?, - unclaimed: AmountPerBlockCumulativeWithSums::forced_import( + unclaimed: AmountPerBlockCumulative::forced_import( db, "unclaimed_rewards", version, indexes, - cached_starts, )?, fee_dominance: PercentPerBlock::forced_import(db, "fee_dominance", version, indexes)?, fee_dominance_rolling, diff --git a/crates/brk_computer/src/mining/rewards/vecs.rs b/crates/brk_computer/src/mining/rewards/vecs.rs index caf4cadc6..5f544098b 100644 --- a/crates/brk_computer/src/mining/rewards/vecs.rs +++ b/crates/brk_computer/src/mining/rewards/vecs.rs @@ -13,7 +13,7 @@ pub struct Vecs { pub coinbase: AmountPerBlockCumulativeWithSums, pub subsidy: AmountPerBlockCumulative, pub fees: AmountPerBlockFull, - pub unclaimed: AmountPerBlockCumulativeWithSums, + pub unclaimed: AmountPerBlockCumulative, #[traversable(wrap = "fees", rename = "dominance")] pub fee_dominance: PercentPerBlock, #[traversable(wrap = "fees", rename = "dominance")] diff --git a/crates/brk_computer/src/scripts/adoption.rs b/crates/brk_computer/src/scripts/adoption.rs deleted file mode 100644 index c2aba4c0b..000000000 --- a/crates/brk_computer/src/scripts/adoption.rs +++ /dev/null @@ -1,55 +0,0 @@ -use brk_error::Result; -use brk_traversable::Traversable; -use brk_types::{BasisPoints16, Indexes, Version}; -use vecdb::{Database, Exit, Rw, StorageMode}; - -use crate::{ - indexes, - internal::{PercentPerBlock, RatioU64Bp16}, - outputs, -}; - -use super::count::Vecs as CountVecs; - -#[derive(Traversable)] -pub struct Vecs { - pub taproot: PercentPerBlock, - pub segwit: PercentPerBlock, -} - -impl Vecs { - pub(crate) fn forced_import( - db: &Database, - version: Version, - indexes: &indexes::Vecs, - ) -> Result { - Ok(Self { - taproot: PercentPerBlock::forced_import(db, "taproot_adoption", version, indexes)?, - segwit: PercentPerBlock::forced_import(db, "segwit_adoption", version, indexes)?, - }) - } - - pub(crate) fn compute( - &mut self, - count: &CountVecs, - outputs_count: &outputs::CountVecs, - starting_indexes: &Indexes, - exit: &Exit, - ) -> Result<()> { - self.taproot.compute_binary::<_, _, RatioU64Bp16>( - starting_indexes.height, - &count.p2tr.base.height, - &outputs_count.total.sum.height, - exit, - )?; - - self.segwit.compute_binary::<_, _, RatioU64Bp16>( - starting_indexes.height, - &count.segwit.base.height, - &outputs_count.total.sum.height, - exit, - )?; - - Ok(()) - } -} diff --git a/crates/brk_computer/src/scripts/compute.rs b/crates/brk_computer/src/scripts/compute.rs index 71730d158..fdf3e9d83 100644 --- a/crates/brk_computer/src/scripts/compute.rs +++ b/crates/brk_computer/src/scripts/compute.rs @@ -3,7 +3,7 @@ use brk_indexer::Indexer; use brk_types::Indexes; use vecdb::Exit; -use crate::{outputs, prices}; +use crate::prices; use super::Vecs; @@ -11,20 +11,15 @@ impl Vecs { pub(crate) fn compute( &mut self, indexer: &Indexer, - outputs: &outputs::Vecs, prices: &prices::Vecs, starting_indexes: &Indexes, exit: &Exit, ) -> Result<()> { - self.count - .compute(indexer, starting_indexes, exit)?; + self.count.compute(indexer, starting_indexes, exit)?; self.value .compute(indexer, prices, starting_indexes, exit)?; - self.adoption - .compute(&self.count, &outputs.count, starting_indexes, exit)?; - let _lock = exit.lock(); self.db.compact()?; Ok(()) diff --git a/crates/brk_computer/src/scripts/count/compute.rs b/crates/brk_computer/src/scripts/count/compute.rs index 0a48c6395..43600f8cb 100644 --- a/crates/brk_computer/src/scripts/count/compute.rs +++ b/crates/brk_computer/src/scripts/count/compute.rs @@ -1,6 +1,6 @@ use brk_error::Result; use brk_indexer::Indexer; -use brk_types::{Indexes, StoredU64}; +use brk_types::Indexes; use vecdb::Exit; use super::Vecs; @@ -12,105 +12,95 @@ impl Vecs { starting_indexes: &Indexes, exit: &Exit, ) -> Result<()> { - self.p2a - .compute(starting_indexes.height, exit, |v| { - Ok(v.compute_count_from_indexes( - starting_indexes.height, - &indexer.vecs.addrs.p2a.first_index, - &indexer.vecs.addrs.p2a.bytes, - exit, - )?) - })?; + self.p2a.compute(starting_indexes.height, exit, |v| { + Ok(v.compute_count_from_indexes( + starting_indexes.height, + &indexer.vecs.addrs.p2a.first_index, + &indexer.vecs.addrs.p2a.bytes, + exit, + )?) + })?; - self.p2ms - .compute(starting_indexes.height, exit, |v| { - Ok(v.compute_count_from_indexes( - starting_indexes.height, - &indexer.vecs.scripts.p2ms.first_index, - &indexer.vecs.scripts.p2ms.to_tx_index, - exit, - )?) - })?; + self.p2ms.compute(starting_indexes.height, exit, |v| { + Ok(v.compute_count_from_indexes( + starting_indexes.height, + &indexer.vecs.scripts.p2ms.first_index, + &indexer.vecs.scripts.p2ms.to_tx_index, + exit, + )?) + })?; - self.p2pk33 - .compute(starting_indexes.height, exit, |v| { - Ok(v.compute_count_from_indexes( - starting_indexes.height, - &indexer.vecs.addrs.p2pk33.first_index, - &indexer.vecs.addrs.p2pk33.bytes, - exit, - )?) - })?; + self.p2pk33.compute(starting_indexes.height, exit, |v| { + Ok(v.compute_count_from_indexes( + starting_indexes.height, + &indexer.vecs.addrs.p2pk33.first_index, + &indexer.vecs.addrs.p2pk33.bytes, + exit, + )?) + })?; - self.p2pk65 - .compute(starting_indexes.height, exit, |v| { - Ok(v.compute_count_from_indexes( - starting_indexes.height, - &indexer.vecs.addrs.p2pk65.first_index, - &indexer.vecs.addrs.p2pk65.bytes, - exit, - )?) - })?; + self.p2pk65.compute(starting_indexes.height, exit, |v| { + Ok(v.compute_count_from_indexes( + starting_indexes.height, + &indexer.vecs.addrs.p2pk65.first_index, + &indexer.vecs.addrs.p2pk65.bytes, + exit, + )?) + })?; - self.p2pkh - .compute(starting_indexes.height, exit, |v| { - Ok(v.compute_count_from_indexes( - starting_indexes.height, - &indexer.vecs.addrs.p2pkh.first_index, - &indexer.vecs.addrs.p2pkh.bytes, - exit, - )?) - })?; + self.p2pkh.compute(starting_indexes.height, exit, |v| { + Ok(v.compute_count_from_indexes( + starting_indexes.height, + &indexer.vecs.addrs.p2pkh.first_index, + &indexer.vecs.addrs.p2pkh.bytes, + exit, + )?) + })?; - self.p2sh - .compute(starting_indexes.height, exit, |v| { - Ok(v.compute_count_from_indexes( - starting_indexes.height, - &indexer.vecs.addrs.p2sh.first_index, - &indexer.vecs.addrs.p2sh.bytes, - exit, - )?) - })?; + self.p2sh.compute(starting_indexes.height, exit, |v| { + Ok(v.compute_count_from_indexes( + starting_indexes.height, + &indexer.vecs.addrs.p2sh.first_index, + &indexer.vecs.addrs.p2sh.bytes, + exit, + )?) + })?; - self.p2tr - .compute(starting_indexes.height, exit, |v| { - Ok(v.compute_count_from_indexes( - starting_indexes.height, - &indexer.vecs.addrs.p2tr.first_index, - &indexer.vecs.addrs.p2tr.bytes, - exit, - )?) - })?; + self.p2tr.compute(starting_indexes.height, exit, |v| { + Ok(v.compute_count_from_indexes( + starting_indexes.height, + &indexer.vecs.addrs.p2tr.first_index, + &indexer.vecs.addrs.p2tr.bytes, + exit, + )?) + })?; - self.p2wpkh - .compute(starting_indexes.height, exit, |v| { - Ok(v.compute_count_from_indexes( - starting_indexes.height, - &indexer.vecs.addrs.p2wpkh.first_index, - &indexer.vecs.addrs.p2wpkh.bytes, - exit, - )?) - })?; + self.p2wpkh.compute(starting_indexes.height, exit, |v| { + Ok(v.compute_count_from_indexes( + starting_indexes.height, + &indexer.vecs.addrs.p2wpkh.first_index, + &indexer.vecs.addrs.p2wpkh.bytes, + exit, + )?) + })?; - self.p2wsh - .compute(starting_indexes.height, exit, |v| { - Ok(v.compute_count_from_indexes( - starting_indexes.height, - &indexer.vecs.addrs.p2wsh.first_index, - &indexer.vecs.addrs.p2wsh.bytes, - exit, - )?) - })?; + self.p2wsh.compute(starting_indexes.height, exit, |v| { + Ok(v.compute_count_from_indexes( + starting_indexes.height, + &indexer.vecs.addrs.p2wsh.first_index, + &indexer.vecs.addrs.p2wsh.bytes, + exit, + )?) + })?; - self.op_return - .compute(starting_indexes.height, exit, |v| { - Ok(v.compute_count_from_indexes( - starting_indexes.height, - &indexer.vecs.scripts.op_return.first_index, - &indexer.vecs.scripts.op_return.to_tx_index, - exit, - )?) - })?; + self.op_return.compute(starting_indexes.height, exit, |v| { + Ok(v.compute_count_from_indexes( + starting_indexes.height, + &indexer.vecs.scripts.op_return.first_index, + &indexer.vecs.scripts.op_return.to_tx_index, + exit, + )?) + })?; self.unknown_output .compute(starting_indexes.height, exit, |v| { @@ -132,19 +122,6 @@ impl Vecs { )?) })?; - // Compute segwit = p2wpkh + p2wsh + p2tr - self.segwit - .compute(starting_indexes.height, exit, |v| { - Ok(v.compute_transform3( - starting_indexes.height, - &self.p2wpkh.base.height, - &self.p2wsh.base.height, - &self.p2tr.base.height, - |(h, p2wpkh, p2wsh, p2tr, ..)| (h, StoredU64::from(*p2wpkh + *p2wsh + *p2tr)), - exit, - )?) - })?; - Ok(()) } } diff --git a/crates/brk_computer/src/scripts/count/import.rs b/crates/brk_computer/src/scripts/count/import.rs index 2735709be..d2a61d8bb 100644 --- a/crates/brk_computer/src/scripts/count/import.rs +++ b/crates/brk_computer/src/scripts/count/import.rs @@ -33,9 +33,6 @@ impl Vecs { PerBlockCumulativeWithSums::forced_import(db, "p2wpkh_count", version, indexes, cached_starts)?; let p2wsh = PerBlockCumulativeWithSums::forced_import(db, "p2wsh_count", version, indexes, cached_starts)?; - let segwit = - PerBlockCumulativeWithSums::forced_import(db, "segwit_count", version, indexes, cached_starts)?; - Ok(Self { p2a, p2ms, @@ -67,7 +64,6 @@ impl Vecs { indexes, cached_starts, )?, - segwit, }) } } diff --git a/crates/brk_computer/src/scripts/count/vecs.rs b/crates/brk_computer/src/scripts/count/vecs.rs index 47131f83c..1d625fb74 100644 --- a/crates/brk_computer/src/scripts/count/vecs.rs +++ b/crates/brk_computer/src/scripts/count/vecs.rs @@ -18,6 +18,4 @@ pub struct Vecs { pub op_return: PerBlockCumulativeWithSums, pub empty_output: PerBlockCumulativeWithSums, pub unknown_output: PerBlockCumulativeWithSums, - - pub segwit: PerBlockCumulativeWithSums, } diff --git a/crates/brk_computer/src/scripts/import.rs b/crates/brk_computer/src/scripts/import.rs index 156d0340b..e93203365 100644 --- a/crates/brk_computer/src/scripts/import.rs +++ b/crates/brk_computer/src/scripts/import.rs @@ -8,7 +8,7 @@ use crate::{ internal::db_utils::{finalize_db, open_db}, }; -use super::{AdoptionVecs, CountVecs, ValueVecs, Vecs}; +use super::{CountVecs, ValueVecs, Vecs}; use crate::internal::CachedWindowStarts; impl Vecs { @@ -23,14 +23,8 @@ impl Vecs { let count = CountVecs::forced_import(&db, version, indexes, cached_starts)?; let value = ValueVecs::forced_import(&db, version, indexes, cached_starts)?; - let adoption = AdoptionVecs::forced_import(&db, version, indexes)?; - let this = Self { - db, - count, - value, - adoption, - }; + let this = Self { db, count, value }; finalize_db(&this.db, &this)?; Ok(this) } diff --git a/crates/brk_computer/src/scripts/mod.rs b/crates/brk_computer/src/scripts/mod.rs index 4fbea183d..1b7042e55 100644 --- a/crates/brk_computer/src/scripts/mod.rs +++ b/crates/brk_computer/src/scripts/mod.rs @@ -1,4 +1,3 @@ -pub mod adoption; pub mod count; pub mod value; @@ -8,7 +7,6 @@ mod import; use brk_traversable::Traversable; use vecdb::{Database, Rw, StorageMode}; -pub use adoption::Vecs as AdoptionVecs; pub use count::Vecs as CountVecs; pub use value::Vecs as ValueVecs; @@ -21,5 +19,4 @@ pub struct Vecs { pub count: CountVecs, pub value: ValueVecs, - pub adoption: AdoptionVecs, } diff --git a/crates/brk_computer/src/transactions/compute.rs b/crates/brk_computer/src/transactions/compute.rs index 68f0981f2..258c9b8ac 100644 --- a/crates/brk_computer/src/transactions/compute.rs +++ b/crates/brk_computer/src/transactions/compute.rs @@ -21,7 +21,10 @@ impl Vecs { exit: &Exit, ) -> Result<()> { let (r1, (r2, r3)) = rayon::join( - || self.count.compute(indexer, &blocks.lookback, starting_indexes, exit), + || { + self.count + .compute(indexer, &blocks.lookback, starting_indexes, exit) + }, || { rayon::join( || self.versions.compute(indexer, starting_indexes, exit), @@ -33,13 +36,18 @@ impl Vecs { r2?; r3?; - self.fees - .compute(indexer, indexes, &inputs.spent, &self.size, starting_indexes, exit)?; + self.fees.compute( + indexer, + indexes, + &inputs.spent, + &self.size, + starting_indexes, + exit, + )?; self.volume.compute( indexer, indexes, - blocks, prices, &self.count, &self.fees, diff --git a/crates/brk_computer/src/transactions/volume/compute.rs b/crates/brk_computer/src/transactions/volume/compute.rs index 582f071c2..803f72222 100644 --- a/crates/brk_computer/src/transactions/volume/compute.rs +++ b/crates/brk_computer/src/transactions/volume/compute.rs @@ -5,14 +5,9 @@ use vecdb::Exit; use super::Vecs; use crate::transactions::{count, fees}; -use crate::{blocks, indexes, inputs, outputs, prices}; +use crate::{indexes, inputs, outputs, prices}; -const WINDOW_SECS: [f64; 4] = [ - 86400.0, - 7.0 * 86400.0, - 30.0 * 86400.0, - 365.0 * 86400.0, -]; +const WINDOW_SECS: [f64; 4] = [86400.0, 7.0 * 86400.0, 30.0 * 86400.0, 365.0 * 86400.0]; impl Vecs { #[allow(clippy::too_many_arguments)] @@ -20,7 +15,6 @@ impl Vecs { &mut self, indexer: &Indexer, indexes: &indexes::Vecs, - blocks: &blocks::Vecs, prices: &prices::Vecs, count_vecs: &count::Vecs, fees_vecs: &fees::Vecs, @@ -29,11 +23,8 @@ impl Vecs { starting_indexes: &Indexes, exit: &Exit, ) -> Result<()> { - self.transfer_volume.compute( - starting_indexes.height, - prices, - exit, - |sats_vec| { + self.transfer_volume + .compute(starting_indexes.height, prices, exit, |sats_vec| { Ok(sats_vec.compute_filtered_sum_from_indexes( starting_indexes.height, &indexer.vecs.transactions.first_tx_index, @@ -42,8 +33,7 @@ impl Vecs { |sats| !sats.is_max(), exit, )?) - }, - )?; + })?; let h = starting_indexes.height; let tx_sums = count_vecs.total.rolling.sum.0.as_array(); @@ -51,14 +41,12 @@ impl Vecs { let output_sums = outputs_count.total.rolling.sum.0.as_array(); for (i, &secs) in WINDOW_SECS.iter().enumerate() { - self.tx_per_sec.as_mut_array()[i] - .height - .compute_transform( - h, - &tx_sums[i].height, - |(h, sum, ..)| (h, StoredF32::from(*sum as f64 / secs)), - exit, - )?; + self.tx_per_sec.as_mut_array()[i].height.compute_transform( + h, + &tx_sums[i].height, + |(h, sum, ..)| (h, StoredF32::from(*sum as f64 / secs)), + exit, + )?; self.inputs_per_sec.as_mut_array()[i] .height .compute_transform( diff --git a/crates/brk_types/src/dollars.rs b/crates/brk_types/src/dollars.rs index d5adc61e6..412043bfd 100644 --- a/crates/brk_types/src/dollars.rs +++ b/crates/brk_types/src/dollars.rs @@ -11,7 +11,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use vecdb::{CheckedSub, Formattable, Pco}; -use crate::{Low, Open}; +use crate::{Cents, Low, Open}; use super::{Bitcoin, CentsSigned, Close, High, Sats, StoredF32, StoredF64}; @@ -52,6 +52,10 @@ impl Dollars { pub fn halved(self) -> Self { Self(self.0 / 2.0) } + + pub fn to_cents(self) -> Cents { + Cents::from(self) + } } impl From for Dollars { diff --git a/modules/brk-client/index.js b/modules/brk-client/index.js index 74da90c6b..98bc86a12 100644 --- a/modules/brk-client/index.js +++ b/modules/brk-client/index.js @@ -4291,7 +4291,6 @@ function createUnspentPattern(client, acc) { * @property {SeriesTree_Scripts_Raw} raw * @property {SeriesTree_Scripts_Count} count * @property {SeriesTree_Scripts_Value} value - * @property {SeriesTree_Scripts_Adoption} adoption */ /** @@ -4340,7 +4339,6 @@ function createUnspentPattern(client, acc) { * @property {BaseCumulativeSumPattern} opReturn * @property {BaseCumulativeSumPattern} emptyOutput * @property {BaseCumulativeSumPattern} unknownOutput - * @property {BaseCumulativeSumPattern} segwit */ /** @@ -4348,12 +4346,6 @@ function createUnspentPattern(client, acc) { * @property {BaseCumulativeSumPattern4} opReturn */ -/** - * @typedef {Object} SeriesTree_Scripts_Adoption - * @property {BpsPercentRatioPattern3} taproot - * @property {BpsPercentRatioPattern3} segwit - */ - /** * @typedef {Object} SeriesTree_Mining * @property {SeriesTree_Mining_Rewards} rewards @@ -4365,7 +4357,7 @@ function createUnspentPattern(client, acc) { * @property {BaseCumulativeSumPattern4} coinbase * @property {SeriesTree_Mining_Rewards_Subsidy} subsidy * @property {SeriesTree_Mining_Rewards_Fees} fees - * @property {BaseCumulativeSumPattern4} unclaimed + * @property {SeriesTree_Mining_Rewards_Unclaimed} unclaimed */ /** @@ -4401,6 +4393,12 @@ function createUnspentPattern(client, acc) { * @property {BpsRatioPattern2} _1y */ +/** + * @typedef {Object} SeriesTree_Mining_Rewards_Unclaimed + * @property {BtcCentsSatsUsdPattern} base + * @property {BtcCentsSatsUsdPattern} cumulative + */ + /** * @typedef {Object} SeriesTree_Mining_Hashrate * @property {SeriesTree_Mining_Hashrate_Rate} rate @@ -4481,10 +4479,6 @@ function createUnspentPattern(client, acc) { * @property {BpsCentsPercentilesRatioSatsUsdPattern} active * @property {BpsCentsPercentilesRatioSatsUsdPattern} trueMarketMean * @property {BpsCentsPercentilesRatioSatsUsdPattern} cointime - * @property {BpsCentsPercentilesRatioSatsUsdPattern} transfer - * @property {BpsCentsPercentilesRatioSatsUsdPattern} balanced - * @property {BpsCentsPercentilesRatioSatsUsdPattern} terminal - * @property {BpsCentsPercentilesRatioSatsUsdPattern} delta */ /** @@ -7767,15 +7761,10 @@ class BrkClient extends BrkClientBase { opReturn: createBaseCumulativeSumPattern(this, 'op_return_count'), emptyOutput: createBaseCumulativeSumPattern(this, 'empty_output_count'), unknownOutput: createBaseCumulativeSumPattern(this, 'unknown_output_count'), - segwit: createBaseCumulativeSumPattern(this, 'segwit_count'), }, value: { opReturn: createBaseCumulativeSumPattern4(this, 'op_return_value'), }, - adoption: { - taproot: createBpsPercentRatioPattern3(this, 'taproot_adoption'), - segwit: createBpsPercentRatioPattern3(this, 'segwit_adoption'), - }, }, mining: { rewards: { @@ -7806,7 +7795,10 @@ class BrkClient extends BrkClientBase { _1y: createBpsRatioPattern2(this, 'fee_to_subsidy_ratio_1y'), }, }, - unclaimed: createBaseCumulativeSumPattern4(this, 'unclaimed_rewards'), + unclaimed: { + base: createBtcCentsSatsUsdPattern(this, 'unclaimed_rewards'), + cumulative: createBtcCentsSatsUsdPattern(this, 'unclaimed_rewards_cumulative'), + }, }, hashrate: { rate: { @@ -7860,10 +7852,6 @@ class BrkClient extends BrkClientBase { active: createBpsCentsPercentilesRatioSatsUsdPattern(this, 'active_price'), trueMarketMean: createBpsCentsPercentilesRatioSatsUsdPattern(this, 'true_market_mean'), cointime: createBpsCentsPercentilesRatioSatsUsdPattern(this, 'cointime_price'), - transfer: createBpsCentsPercentilesRatioSatsUsdPattern(this, 'transfer_price'), - balanced: createBpsCentsPercentilesRatioSatsUsdPattern(this, 'balanced_price'), - terminal: createBpsCentsPercentilesRatioSatsUsdPattern(this, 'terminal_price'), - delta: createBpsCentsPercentilesRatioSatsUsdPattern(this, 'delta_price'), }, adjusted: { inflationRate: createBpsPercentRatioPattern(this, 'cointime_adj_inflation_rate'), diff --git a/packages/brk_client/brk_client/__init__.py b/packages/brk_client/brk_client/__init__.py index 6ebf455fb..68ca3e328 100644 --- a/packages/brk_client/brk_client/__init__.py +++ b/packages/brk_client/brk_client/__init__.py @@ -3594,7 +3594,6 @@ class SeriesTree_Scripts_Count: self.op_return: BaseCumulativeSumPattern[StoredU64] = BaseCumulativeSumPattern(client, 'op_return_count') self.empty_output: BaseCumulativeSumPattern[StoredU64] = BaseCumulativeSumPattern(client, 'empty_output_count') self.unknown_output: BaseCumulativeSumPattern[StoredU64] = BaseCumulativeSumPattern(client, 'unknown_output_count') - self.segwit: BaseCumulativeSumPattern[StoredU64] = BaseCumulativeSumPattern(client, 'segwit_count') class SeriesTree_Scripts_Value: """Series tree node.""" @@ -3602,13 +3601,6 @@ class SeriesTree_Scripts_Value: def __init__(self, client: BrkClientBase, base_path: str = ''): self.op_return: BaseCumulativeSumPattern4 = BaseCumulativeSumPattern4(client, 'op_return_value') -class SeriesTree_Scripts_Adoption: - """Series tree node.""" - - def __init__(self, client: BrkClientBase, base_path: str = ''): - self.taproot: BpsPercentRatioPattern3 = BpsPercentRatioPattern3(client, 'taproot_adoption') - self.segwit: BpsPercentRatioPattern3 = BpsPercentRatioPattern3(client, 'segwit_adoption') - class SeriesTree_Scripts: """Series tree node.""" @@ -3616,7 +3608,6 @@ class SeriesTree_Scripts: self.raw: SeriesTree_Scripts_Raw = SeriesTree_Scripts_Raw(client) self.count: SeriesTree_Scripts_Count = SeriesTree_Scripts_Count(client) self.value: SeriesTree_Scripts_Value = SeriesTree_Scripts_Value(client) - self.adoption: SeriesTree_Scripts_Adoption = SeriesTree_Scripts_Adoption(client) class SeriesTree_Mining_Rewards_Subsidy: """Series tree node.""" @@ -3654,6 +3645,13 @@ class SeriesTree_Mining_Rewards_Fees: self.dominance: _1m1w1y24hBpsPercentRatioPattern = _1m1w1y24hBpsPercentRatioPattern(client, 'fee_dominance') self.to_subsidy_ratio: SeriesTree_Mining_Rewards_Fees_ToSubsidyRatio = SeriesTree_Mining_Rewards_Fees_ToSubsidyRatio(client) +class SeriesTree_Mining_Rewards_Unclaimed: + """Series tree node.""" + + def __init__(self, client: BrkClientBase, base_path: str = ''): + self.base: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'unclaimed_rewards') + self.cumulative: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'unclaimed_rewards_cumulative') + class SeriesTree_Mining_Rewards: """Series tree node.""" @@ -3661,7 +3659,7 @@ class SeriesTree_Mining_Rewards: self.coinbase: BaseCumulativeSumPattern4 = BaseCumulativeSumPattern4(client, 'coinbase') self.subsidy: SeriesTree_Mining_Rewards_Subsidy = SeriesTree_Mining_Rewards_Subsidy(client) self.fees: SeriesTree_Mining_Rewards_Fees = SeriesTree_Mining_Rewards_Fees(client) - self.unclaimed: BaseCumulativeSumPattern4 = BaseCumulativeSumPattern4(client, 'unclaimed_rewards') + self.unclaimed: SeriesTree_Mining_Rewards_Unclaimed = SeriesTree_Mining_Rewards_Unclaimed(client) class SeriesTree_Mining_Hashrate_Rate_Sma: """Series tree node.""" @@ -3749,10 +3747,6 @@ class SeriesTree_Cointime_Prices: self.active: BpsCentsPercentilesRatioSatsUsdPattern = BpsCentsPercentilesRatioSatsUsdPattern(client, 'active_price') self.true_market_mean: BpsCentsPercentilesRatioSatsUsdPattern = BpsCentsPercentilesRatioSatsUsdPattern(client, 'true_market_mean') self.cointime: BpsCentsPercentilesRatioSatsUsdPattern = BpsCentsPercentilesRatioSatsUsdPattern(client, 'cointime_price') - self.transfer: BpsCentsPercentilesRatioSatsUsdPattern = BpsCentsPercentilesRatioSatsUsdPattern(client, 'transfer_price') - self.balanced: BpsCentsPercentilesRatioSatsUsdPattern = BpsCentsPercentilesRatioSatsUsdPattern(client, 'balanced_price') - self.terminal: BpsCentsPercentilesRatioSatsUsdPattern = BpsCentsPercentilesRatioSatsUsdPattern(client, 'terminal_price') - self.delta: BpsCentsPercentilesRatioSatsUsdPattern = BpsCentsPercentilesRatioSatsUsdPattern(client, 'delta_price') class SeriesTree_Cointime_Adjusted: """Series tree node.""" diff --git a/website/scripts/options/cointime.js b/website/scripts/options/cointime.js index fcb509a86..83de839e8 100644 --- a/website/scripts/options/cointime.js +++ b/website/scripts/options/cointime.js @@ -1,7 +1,14 @@ import { colors } from "../utils/colors.js"; import { brk } from "../client.js"; import { Unit } from "../utils/units.js"; -import { dots, line, baseline, price, rollingWindowsTree, percentRatioDots } from "./series.js"; +import { + dots, + line, + price, + sumsTree, + multiSeriesTree, + percentRatioDots, +} from "./series.js"; import { satsBtcUsd, priceRatioPercentilesTree } from "./shared.js"; /** @@ -36,50 +43,59 @@ export function createCointimeSection() { pattern: cointimePrices.trueMarketMean, name: "True Market Mean", color: colors.trueMarketMean, + defaultActive: true, }, { pattern: cointimePrices.vaulted, name: "Vaulted", color: colors.vaulted, + defaultActive: true, }, { pattern: cointimePrices.active, name: "Active", color: colors.active, + defaultActive: true, }, { pattern: cointimePrices.cointime, name: "Cointime", color: colors.cointime, - }, - { - pattern: cointimePrices.transfer, - name: "Transfer", - color: colors.transfer, - }, - { - pattern: cointimePrices.balanced, - name: "Balanced", - color: colors.balanced, - }, - { - pattern: cointimePrices.terminal, - name: "Terminal", - color: colors.terminal, - }, - { - pattern: cointimePrices.delta, - name: "Delta", - color: colors.delta, + defaultActive: true, }, ]); const caps = /** @type {const} */ ([ - { series: cap.vaulted.usd, name: "Vaulted", color: colors.vaulted }, - { series: cap.active.usd, name: "Active", color: colors.active }, - { series: cap.cointime.usd, name: "Cointime", color: colors.cointime }, - { series: cap.investor.usd, name: "Investor", color: colors.investor }, - { series: cap.thermo.usd, name: "Thermo", color: colors.thermo }, + { + series: cap.vaulted.usd, + name: "Vaulted", + color: colors.vaulted, + defaultActive: true, + }, + { + series: cap.active.usd, + name: "Active", + color: colors.active, + defaultActive: true, + }, + { + series: cap.cointime.usd, + name: "Cointime", + color: colors.cointime, + defaultActive: true, + }, + { + series: cap.investor.usd, + name: "Investor", + color: colors.investor, + defaultActive: false, + }, + { + series: cap.thermo.usd, + name: "Thermo", + color: colors.thermo, + defaultActive: false, + }, ]); const supplyBreakdown = /** @type {const} */ ([ @@ -167,8 +183,8 @@ export function createCointimeSection() { name: "Investor", color: colors.investor, }), - ...prices.map(({ pattern, name, color }) => - price({ series: pattern, name, color }), + ...prices.map(({ pattern, name, color, defaultActive }) => + price({ series: pattern, name, color, defaultActive }), ), ], }, @@ -180,7 +196,12 @@ export function createCointimeSection() { legend: name, color, priceReferences: [ - price({ series: all.realized.price, name: "Realized", color: colors.realized, defaultActive: false }), + price({ + series: all.realized.price, + name: "Realized", + color: colors.realized, + defaultActive: false, + }), ], }), })), @@ -198,8 +219,8 @@ export function createCointimeSection() { ...capReferenceLines.map(({ series, name, color }) => line({ series, name, color, unit: Unit.usd }), ), - ...caps.map(({ series, name, color }) => - line({ series, name, color, unit: Unit.usd }), + ...caps.map(({ series, name, color, defaultActive }) => + line({ series, name, color, defaultActive, unit: Unit.usd }), ), ], }, @@ -263,32 +284,17 @@ export function createCointimeSection() { tree: [ { name: "Compare", - tree: [ - { - name: "Base", - title: "Coinblocks", - bottom: coinblocks.map(({ pattern, name, color }) => - line({ - series: pattern.base, - name, - color, - unit: Unit.coinblocks, - }), - ), - }, - { - name: "Cumulative", - title: "Coinblocks (Total)", - bottom: coinblocks.map(({ pattern, name, color }) => - line({ - series: pattern.cumulative, - name, - color, - unit: Unit.coinblocks, - }), - ), - }, - ], + tree: multiSeriesTree({ + entries: coinblocks.map(({ pattern, name, color }) => ({ + name, + color, + base: pattern.base, + rolling: pattern.sum, + cumulative: pattern.cumulative, + })), + title: "Coinblocks", + unit: Unit.coinblocks, + }), }, ...coinblocks.map(({ pattern, name, title, color }) => ({ name, @@ -305,7 +311,7 @@ export function createCointimeSection() { }), ], }, - rollingWindowsTree({ windows: pattern.sum, title, unit: Unit.coinblocks }), + sumsTree({ windows: pattern.sum, title, unit: Unit.coinblocks }), { name: "Cumulative", title: `${title} (Total)`, @@ -329,43 +335,26 @@ export function createCointimeSection() { tree: [ { name: "Compare", - tree: [ - { - name: "Base", - title: "Cointime Value", - bottom: [ - ...cointimeValues.map(({ pattern, name, color }) => - line({ series: pattern.base, name, color, unit: Unit.usd }), - ), - line({ - series: vocdd.pattern.base, - name: vocdd.name, - color: vocdd.color, - unit: Unit.usd, - }), - ], - }, - { - name: "Cumulative", - title: "Cointime Value (Total)", - bottom: [ - ...cointimeValues.map(({ pattern, name, color }) => - line({ - series: pattern.cumulative, - name, - color, - unit: Unit.usd, - }), - ), - line({ - series: vocdd.pattern.cumulative, - name: vocdd.name, - color: vocdd.color, - unit: Unit.usd, - }), - ], - }, - ], + tree: multiSeriesTree({ + entries: [ + ...cointimeValues.map(({ pattern, name, color }) => ({ + name, + color, + base: pattern.base, + rolling: pattern.sum, + cumulative: pattern.cumulative, + })), + { + name: vocdd.name, + color: vocdd.color, + base: vocdd.pattern.base, + rolling: vocdd.pattern.sum, + cumulative: vocdd.pattern.cumulative, + }, + ], + title: "Cointime Value", + unit: Unit.usd, + }), }, ...cointimeValues.map(({ pattern, name, title, color }) => ({ name, @@ -377,7 +366,7 @@ export function createCointimeSection() { line({ series: pattern.base, name, color, unit: Unit.usd }), ], }, - rollingWindowsTree({ windows: pattern.sum, title, unit: Unit.usd }), + sumsTree({ windows: pattern.sum, title, unit: Unit.usd }), { name: "Cumulative", title: `${title} (Total)`, @@ -413,7 +402,11 @@ export function createCointimeSection() { }), ], }, - rollingWindowsTree({ windows: vocdd.pattern.sum, title: vocdd.title, unit: Unit.usd }), + sumsTree({ + windows: vocdd.pattern.sum, + title: vocdd.title, + unit: Unit.usd, + }), { name: "Cumulative", title: `${vocdd.title} (Total)`, @@ -450,25 +443,11 @@ export function createCointimeSection() { { name: "AVIV", title: "AVIV Ratio", - bottom: [ - baseline({ - series: cap.aviv.ratio, - name: "Ratio", - color: colors.reserveRisk, - unit: Unit.ratio, - base: 1, - }), - ], - }, - { - name: "HODL Bank", - title: "HODL Bank", bottom: [ line({ - series: reserveRisk.hodlBank, - name: "Value", - color: colors.hodlBank, - unit: Unit.usd, + series: cap.aviv.ratio, + name: "aviv", + unit: Unit.ratio, }), ], }, diff --git a/website/scripts/options/distribution/holdings.js b/website/scripts/options/distribution/holdings.js index 60c4a3857..bba54b58c 100644 --- a/website/scripts/options/distribution/holdings.js +++ b/website/scripts/options/distribution/holdings.js @@ -10,7 +10,7 @@ */ import { Unit } from "../../utils/units.js"; -import { ROLLING_WINDOWS, line, baseline, rollingWindowsTree, rollingPercentRatioTree, percentRatio } from "../series.js"; +import { ROLLING_WINDOWS, line, baseline, sumsTree, rollingPercentRatioTree, percentRatio } from "../series.js"; import { satsBtcUsd, mapCohorts, @@ -174,7 +174,7 @@ function singleDeltaTree(delta, unit, title, name) { return { name, tree: [ - { ...rollingWindowsTree({ windows: delta.absolute, title: title(`${name} Change`), unit, series: baseline }), name: "Absolute" }, + { ...sumsTree({ windows: delta.absolute, title: title(`${name} Change`), unit, series: baseline }), name: "Absolute" }, { ...rollingPercentRatioTree({ windows: delta.rate, title: title(`${name} Rate`) }), name: "Rate" }, ], }; diff --git a/website/scripts/options/distribution/index.js b/website/scripts/options/distribution/index.js index 20746d7b4..7bf04b36d 100644 --- a/website/scripts/options/distribution/index.js +++ b/website/scripts/options/distribution/index.js @@ -11,7 +11,7 @@ */ import { formatCohortTitle, satsBtcUsd, satsBtcUsdFullTree } from "../shared.js"; -import { ROLLING_WINDOWS, line, baseline, percentRatio, rollingWindowsTree, rollingPercentRatioTree } from "../series.js"; +import { ROLLING_WINDOWS, line, baseline, percentRatio, sumsTree, rollingPercentRatioTree } from "../series.js"; import { Unit } from "../../utils/units.js"; import { colors } from "../../utils/colors.js"; @@ -613,7 +613,7 @@ function singleBucketFolder({ name, color, pattern }) { { name: "Change", tree: [ - { ...rollingWindowsTree({ windows: pattern.supply.all.delta.absolute, title: `${name}: Supply Change`, unit: Unit.sats, series: baseline }), name: "Absolute" }, + { ...sumsTree({ windows: pattern.supply.all.delta.absolute, title: `${name}: Supply Change`, unit: Unit.sats, series: baseline }), name: "Absolute" }, { ...rollingPercentRatioTree({ windows: pattern.supply.all.delta.rate, title: `${name}: Supply Rate` }), name: "Rate" }, ], }, diff --git a/website/scripts/options/distribution/prices.js b/website/scripts/options/distribution/prices.js index fb3b9e7c0..c3d36bcf4 100644 --- a/website/scripts/options/distribution/prices.js +++ b/website/scripts/options/distribution/prices.js @@ -35,8 +35,6 @@ export function createPricesSectionFull({ cohort, title }) { top: [ price({ series: tree.realized.price, name: "Realized", color: colors.realized }), price({ series: tree.realized.investor.price, name: "Investor", color: colors.investor }), - price({ series: tree.realized.investor.investorUpperBand, name: "I²/R", color: colors.stat.max, style: 2, defaultActive: false }), - price({ series: tree.realized.investor.investorLowerBand, name: "R²/I", color: colors.stat.min, style: 2, defaultActive: false }), ], }, { diff --git a/website/scripts/options/distribution/valuation.js b/website/scripts/options/distribution/valuation.js index 98c89d990..b5be81d71 100644 --- a/website/scripts/options/distribution/valuation.js +++ b/website/scripts/options/distribution/valuation.js @@ -11,7 +11,7 @@ */ import { Unit } from "../../utils/units.js"; -import { ROLLING_WINDOWS, line, baseline, mapWindows, rollingWindowsTree, rollingPercentRatioTree, percentRatio, percentRatioBaseline } from "../series.js"; +import { ROLLING_WINDOWS, line, baseline, mapWindows, sumsTree, rollingPercentRatioTree, percentRatio, percentRatioBaseline } from "../series.js"; import { createRatioChart, mapCohortsWithAll, flatMapCohortsWithAll } from "../shared.js"; /** @@ -59,7 +59,7 @@ export function createValuationSectionFull({ cohort, title }) { { name: "Change", tree: [ - { ...rollingWindowsTree({ windows: mapWindows(tree.realized.cap.delta.absolute, (c) => c.usd), title: title("Realized Cap Change"), unit: Unit.usd, series: baseline }), name: "Absolute" }, + { ...sumsTree({ windows: mapWindows(tree.realized.cap.delta.absolute, (c) => c.usd), title: title("Realized Cap Change"), unit: Unit.usd, series: baseline }), name: "Absolute" }, { ...rollingPercentRatioTree({ windows: tree.realized.cap.delta.rate, title: title("Realized Cap Rate") }), name: "Rate" }, ], }, @@ -93,7 +93,7 @@ export function createValuationSection({ cohort, title }) { { name: "Change", tree: [ - { ...rollingWindowsTree({ windows: mapWindows(tree.realized.cap.delta.absolute, (c) => c.usd), title: title("Realized Cap Change"), unit: Unit.usd, series: baseline }), name: "Absolute" }, + { ...sumsTree({ windows: mapWindows(tree.realized.cap.delta.absolute, (c) => c.usd), title: title("Realized Cap Change"), unit: Unit.usd, series: baseline }), name: "Absolute" }, { ...rollingPercentRatioTree({ windows: tree.realized.cap.delta.rate, title: title("Realized Cap Rate") }), name: "Rate" }, ], }, diff --git a/website/scripts/options/investing.js b/website/scripts/options/investing.js index 3fc2428fd..776c73af1 100644 --- a/website/scripts/options/investing.js +++ b/website/scripts/options/investing.js @@ -145,6 +145,59 @@ function createCompareFolder(context, items) { }; } +/** + * Create compare folder from long items (includes CAGR chart) + * @param {string} context + * @param {LongEntryItem[]} items + */ +function createLongCompareFolder(context, items) { + const topPane = items.map(({ name, color, costBasis }) => + price({ series: costBasis, name, color }), + ); + return { + name: "Compare", + tree: [ + { + name: "Cost Basis", + title: `Cost Basis: ${context}`, + top: topPane, + }, + { + name: "Returns", + title: `Returns: ${context}`, + top: topPane, + bottom: items.flatMap(({ name, color, returns }) => + percentRatioBaseline({ + pattern: returns, + name, + color: [color, color], + }), + ), + }, + { + name: "CAGR", + title: `CAGR: ${context}`, + top: topPane, + bottom: items.flatMap(({ name, color, cagr }) => + percentRatioBaseline({ + pattern: cagr, + name, + color: [color, color], + }), + ), + }, + { + name: "Accumulated", + title: `Accumulated Value: ${context}`, + top: topPane, + bottom: items.flatMap(({ name, color, stack }) => + satsBtcUsd({ pattern: stack, name, color }), + ), + }, + ], + }; +} + /** * Create single entry tree structure * @param {BaseEntryItem & { titlePrefix?: string }} item @@ -182,14 +235,36 @@ function createShortSingleEntry(item) { } /** - * Create a single entry from a long item (with CAGR) + * Create a single entry from a long item (with CAGR as its own chart) * @param {LongEntryItem & { titlePrefix?: string }} item */ function createLongSingleEntry(item) { - return createSingleEntryTree(item, [ - ...percentRatioBaseline({ pattern: item.returns, name: "Current" }), - ...percentRatioBaseline({ pattern: item.cagr, name: "CAGR" }), - ]); + const { name, titlePrefix = name, color, costBasis, returns, cagr, stack } = item; + const top = [price({ series: costBasis, name: "Cost Basis", color })]; + return { + name, + tree: [ + { name: "Cost Basis", title: `Cost Basis: ${titlePrefix}`, top }, + { + name: "Returns", + title: `Returns: ${titlePrefix}`, + top, + bottom: percentRatioBaseline({ pattern: returns, name: "Current" }), + }, + { + name: "CAGR", + title: `CAGR: ${titlePrefix}`, + top, + bottom: percentRatioBaseline({ pattern: cagr, name: "CAGR" }), + }, + { + name: "Accumulated", + title: `Accumulated Value: ${titlePrefix}`, + top, + bottom: satsBtcUsd({ pattern: stack, name: "Value" }), + }, + ], + }; } /** @@ -250,13 +325,22 @@ export function createDcaVsLumpSumSection({ dca, lookback, returns }) { name: "Lump Sum", color: colors.bi.p2, }), + ], + }); + + /** @param {string} name @param {LongPeriodKey} key */ + const longCagrChart = (name, key) => ({ + name: "CAGR", + title: `CAGR: ${name} DCA vs Lump Sum`, + top: topPane(key), + bottom: [ ...percentRatioBaseline({ pattern: dca.period.cagr[key], - name: "DCA CAGR", + name: "DCA", }), ...percentRatioBaseline({ pattern: returns.cagr[key], - name: "Lump Sum CAGR", + name: "Lump Sum", color: colors.bi.p2, }), ], @@ -302,6 +386,7 @@ export function createDcaVsLumpSumSection({ dca, lookback, returns }) { tree: [ costBasisChart(name, key), longReturnsChart(name, key), + longCagrChart(name, key), stackChart(name, key), ], }; @@ -381,10 +466,6 @@ function createPeriodSection({ dca, lookback, returns }) { name: `${suffix} by Period`, title: `${suffix} Performance by Investment Period`, tree: [ - createCompareFolder(`All Periods ${suffix}`, [ - ...shortEntries, - ...longEntries, - ]), { name: "Short Term", title: "Up to 1 Year", @@ -397,7 +478,7 @@ function createPeriodSection({ dca, lookback, returns }) { name: "Long Term", title: "2+ Years", tree: [ - createCompareFolder(`Long Term ${suffix}`, longEntries), + createLongCompareFolder(`Long Term ${suffix}`, longEntries), ...longEntries.map(createLongEntry), ], }, diff --git a/website/scripts/options/market.js b/website/scripts/options/market.js index e8cbc1a50..7072734e1 100644 --- a/website/scripts/options/market.js +++ b/website/scripts/options/market.js @@ -86,7 +86,12 @@ function createMaSubSection(label, averages) { name: "Compare", title: `Price ${label}s`, top: averages.map((a) => - price({ series: a.ratio, name: a.id, color: a.color }), + price({ + series: a.ratio, + name: a.id, + color: a.color, + defaultActive: includes(commonMaIds, a.id), + }), ), }, ...common.map(toFolder), diff --git a/website/scripts/options/mining.js b/website/scripts/options/mining.js index eac55f030..67c1774d0 100644 --- a/website/scripts/options/mining.js +++ b/website/scripts/options/mining.js @@ -10,46 +10,44 @@ import { dotted, distributionBtcSatsUsd, statsAtWindow, - rollingWindowsTree, ROLLING_WINDOWS, percentRatio, - percentRatioDots, + chartsFromCount, } from "./series.js"; import { - satsBtcUsd, satsBtcUsdFrom, + satsBtcUsdFullTree, revenueBtcSatsUsd, } from "./shared.js"; import { brk } from "../client.js"; /** Major pools to show in Compare section (by current hashrate dominance) */ const MAJOR_POOL_IDS = /** @type {const} */ ([ - "foundryusa", // ~32% - largest pool - "antpool", // ~18% - Bitmain-owned - "viabtc", // ~14% - independent - "f2pool", // ~10% - one of the oldest pools - "marapool", // MARA Holdings - "braiinspool", // formerly Slush Pool - "spiderpool", // growing Asian pool - "ocean", // decentralization-focused + "foundryusa", + "antpool", + "viabtc", + "f2pool", + "marapool", + "braiinspool", + "spiderpool", + "ocean", ]); /** * AntPool & friends - pools sharing AntPool's block templates * Based on b10c's research: https://b10c.me/blog/015-bitcoin-mining-centralization/ - * Collectively ~35-40% of network hashrate */ const ANTPOOL_AND_FRIENDS_IDS = /** @type {const} */ ([ - "antpool", // Bitmain-owned, template source - "poolin", // shares AntPool templates - "btccom", // CloverPool (formerly BTC.com) - "braiinspool", // shares AntPool templates - "ultimuspool", // shares AntPool templates - "binancepool", // shares AntPool templates - "secpool", // shares AntPool templates - "sigmapoolcom", // SigmaPool - "rawpool", // shares AntPool templates - "luxor", // shares AntPool templates + "antpool", + "poolin", + "btccom", + "braiinspool", + "ultimuspool", + "binancepool", + "secpool", + "sigmapoolcom", + "rawpool", + "luxor", ]); /** @@ -59,7 +57,6 @@ const ANTPOOL_AND_FRIENDS_IDS = /** @type {const} */ ([ export function createMiningSection() { const { blocks, pools, mining } = brk.series; - // Pre-compute pool entries with resolved names const majorPoolData = entries(pools.major).map(([id, pool]) => ({ id, name: brk.POOL_ID_TO_POOL_NAME[id], @@ -71,7 +68,6 @@ export function createMiningSection() { pool, })); - // Filtered pool groups for comparisons (major pools only have windowed dominance) const featuredPools = majorPoolData.filter((p) => includes(MAJOR_POOL_IDS, p.id), ); @@ -79,135 +75,126 @@ export function createMiningSection() { includes(ANTPOOL_AND_FRIENDS_IDS, p.id), ); - // Build individual pool trees - const majorPoolsTree = majorPoolData.map(({ name, pool }) => ({ - name, + /** @param {string} title @param {{ _24h: any, _1w: any, _1m: any, _1y: any, percent: any, ratio: any }} dominance */ + const dominanceTree = (title, dominance) => ({ + name: "Dominance", tree: [ { - name: "Dominance", - title: `Dominance: ${name}`, + name: "Compare", + title, bottom: [ - ...percentRatioDots({ pattern: pool.dominance._24h, name: "24h", color: colors.time._24h, defaultActive: false }), - ...percentRatio({ pattern: pool.dominance._1w, name: "1w", color: colors.time._1w, defaultActive: false }), - ...percentRatio({ pattern: pool.dominance._1m, name: "1m", color: colors.time._1m }), - ...percentRatio({ pattern: pool.dominance._1y, name: "1y", color: colors.time._1y, defaultActive: false }), - ...percentRatio({ pattern: pool.dominance, name: "All Time", color: colors.time.all, defaultActive: false }), + ...ROLLING_WINDOWS.flatMap((w) => + percentRatio({ pattern: dominance[w.key], name: w.name, color: w.color, defaultActive: w.key !== "_24h" }), + ), + ...percentRatio({ pattern: dominance, name: "All Time", color: colors.time.all }), ], }, + ...ROLLING_WINDOWS.map((w) => ({ + name: w.name, + title: `${title} ${w.title}`, + bottom: percentRatio({ pattern: dominance[w.key], name: w.name, color: w.color }), + })), { - name: "Blocks Mined", - tree: [ - { - name: "Base", - title: `Blocks Mined: ${name}`, - bottom: [ - line({ - series: pool.blocksMined.base, - name: "base", - unit: Unit.count, - }), - ], - }, - rollingWindowsTree({ windows: pool.blocksMined.sum, title: `Blocks Mined: ${name}`, unit: Unit.count }), - { - name: "Cumulative", - title: `Blocks Mined: ${name} (Total)`, - bottom: [ - line({ - series: pool.blocksMined.cumulative, - name: "all-time", - unit: Unit.count, - }), - ], - }, - ], - }, - { - name: "Rewards", - tree: [ - { - name: "Sum", - title: `Rewards: ${name}`, - bottom: satsBtcUsdFrom({ - source: pool.rewards, - key: "base", - name: "sum", - }), - }, - { - name: "Rolling", - tree: [ - { - name: "Compare", - title: `Rewards: ${name} Rolling`, - bottom: ROLLING_WINDOWS.flatMap((w) => - satsBtcUsd({ pattern: pool.rewards.sum[w.key], name: w.name, color: w.color }), - ), - }, - ...ROLLING_WINDOWS.map((w) => ({ - name: w.name, - title: `Rewards: ${name} (${w.name})`, - bottom: satsBtcUsd({ pattern: pool.rewards.sum[w.key], name: w.name, color: w.color }), - })), - ], - }, - { - name: "Cumulative", - title: `Rewards: ${name} (Total)`, - bottom: satsBtcUsdFrom({ - source: pool.rewards, - key: "cumulative", - name: "all-time", - }), - }, - ], + name: "All Time", + title: `${title} All Time`, + bottom: percentRatio({ pattern: dominance, name: "All Time", color: colors.time.all }), }, ], - })); + }); - const minorPoolsTree = minorPoolData.map(({ name, pool }) => ({ - name, + /** + * @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, + name: "Rewards", + title: `Rewards: ${name}`, + }), + }, + ], + })); + + /** + * @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, + }), + }, + ], + })); + + /** + * @param {string} groupTitle + * @param {typeof majorPoolData} poolList + */ + const createPoolCompare = (groupTitle, poolList) => ({ + name: "Compare", tree: [ { name: "Dominance", - title: `Dominance: ${name}`, - bottom: percentRatio({ pattern: pool.dominance, name: "All Time", color: colors.time.all }), + tree: ROLLING_WINDOWS.map((w) => ({ + name: w.name, + title: `Dominance: ${groupTitle} ${w.title}`, + bottom: poolList.flatMap((p, i) => + percentRatio({ + pattern: p.pool.dominance[w.key], + name: p.name, + color: colors.at(i, poolList.length), + }), + ), + })), }, { name: "Blocks Mined", - tree: [ - { - name: "Base", - title: `Blocks Mined: ${name}`, - bottom: [ - line({ - series: pool.blocksMined.base, - name: "base", - unit: Unit.count, - }), - ], - }, - rollingWindowsTree({ windows: pool.blocksMined.sum, title: `Blocks Mined: ${name}`, unit: Unit.count }), - { - name: "Cumulative", - title: `Blocks Mined: ${name} (Total)`, - bottom: [ - line({ - series: pool.blocksMined.cumulative, - name: "all-time", - unit: Unit.count, - }), - ], - }, - ], + tree: ROLLING_WINDOWS.map((w) => ({ + name: w.name, + title: `Blocks Mined: ${groupTitle} ${w.title} Sum`, + bottom: poolList.map((p, i) => + line({ + series: p.pool.blocksMined.sum[w.key], + name: p.name, + color: colors.at(i, poolList.length), + unit: Unit.count, + }), + ), + })), }, ], - })); + }); + return { name: "Mining", tree: [ - // Hashrate { name: "Hashrate", tree: [ @@ -293,63 +280,6 @@ export function createMiningSection() { ], }, - // Difficulty - { - name: "Difficulty", - tree: [ - { - name: "Current", - title: "Mining Difficulty", - bottom: [ - line({ - series: blocks.difficulty.value, - name: "Difficulty", - unit: Unit.difficulty, - }), - ], - }, - { - name: "Epoch", - title: "Difficulty Epoch", - bottom: [ - line({ - series: blocks.difficulty.epoch, - name: "Epoch", - unit: Unit.epoch, - }), - ], - }, - { - name: "Adjustment", - title: "Difficulty Adjustment", - bottom: [ - baseline({ - series: blocks.difficulty.adjustment.percent, - name: "Change", - unit: Unit.percentage, - }), - ], - }, - { - name: "Countdown", - title: "Next Difficulty Adjustment", - bottom: [ - line({ - series: blocks.difficulty.blocksToRetarget, - name: "Remaining", - unit: Unit.blocks, - }), - line({ - series: blocks.difficulty.daysToRetarget, - name: "Remaining", - unit: Unit.days, - }), - ], - }, - ], - }, - - // Revenue { name: "Revenue", tree: [ @@ -357,7 +287,7 @@ export function createMiningSection() { name: "Compare", tree: [ { - name: "Sum", + name: "Per Block", title: "Revenue Comparison", bottom: revenueBtcSatsUsd({ coinbase: mining.rewards.coinbase, @@ -380,105 +310,23 @@ export function createMiningSection() { }, { name: "Coinbase", - tree: [ - { - name: "Sum", - title: "Coinbase Rewards", - bottom: satsBtcUsdFrom({ - source: mining.rewards.coinbase, - key: "base", - name: "sum", - }), - }, - { - name: "Rolling", - tree: [ - { - name: "Compare", - title: "Coinbase Rolling Sum", - bottom: [ - ...satsBtcUsd({ - pattern: mining.rewards.coinbase.sum._24h, - name: "24h", - color: colors.time._24h, - }), - ...satsBtcUsd({ - pattern: mining.rewards.coinbase.sum._1w, - name: "7d", - color: colors.time._1w, - }), - ...satsBtcUsd({ - pattern: mining.rewards.coinbase.sum._1m, - name: "30d", - color: colors.time._1m, - }), - ...satsBtcUsd({ - pattern: mining.rewards.coinbase.sum._1y, - name: "1y", - color: colors.time._1y, - }), - ], - }, - { - name: "24h", - title: "Coinbase 24h Rolling Sum", - bottom: satsBtcUsd({ - pattern: mining.rewards.coinbase.sum._24h, - name: "24h", - color: colors.time._24h, - }), - }, - { - name: "7d", - title: "Coinbase 7d Rolling Sum", - bottom: satsBtcUsd({ - pattern: mining.rewards.coinbase.sum._1w, - name: "7d", - color: colors.time._1w, - }), - }, - { - name: "30d", - title: "Coinbase 30d Rolling Sum", - bottom: satsBtcUsd({ - pattern: mining.rewards.coinbase.sum._1m, - name: "30d", - color: colors.time._1m, - }), - }, - { - name: "1y", - title: "Coinbase 1y Rolling Sum", - bottom: satsBtcUsd({ - pattern: mining.rewards.coinbase.sum._1y, - name: "1y", - color: colors.time._1y, - }), - }, - ], - }, - { - name: "Cumulative", - title: "Coinbase Rewards (Total)", - bottom: satsBtcUsdFrom({ - source: mining.rewards.coinbase, - key: "cumulative", - name: "all-time", - }), - }, - ], + tree: satsBtcUsdFullTree({ + pattern: mining.rewards.coinbase, + name: "Coinbase", + title: "Coinbase Rewards", + }), }, { name: "Subsidy", tree: [ { - name: "Sum", + name: "Per Block", title: "Block Subsidy", bottom: [ ...satsBtcUsdFrom({ source: mining.rewards.subsidy, key: "base", - name: "sum", + name: "base", }), line({ series: mining.rewards.subsidy.sma1y.usd, @@ -503,129 +351,32 @@ export function createMiningSection() { { name: "Fees", tree: [ + ...satsBtcUsdFullTree({ + pattern: mining.rewards.fees, + name: "Fees", + title: "Transaction Fee Revenue", + }), { - name: "Sum", - title: "Transaction Fee Revenue per Block", - bottom: satsBtcUsdFrom({ - source: mining.rewards.fees, - key: "base", - name: "sum", - }), - }, - { - name: "Rolling", - tree: [ - { - name: "Compare", - title: "Fee Rolling Sum", - bottom: [ - ...satsBtcUsd({ - pattern: mining.rewards.fees.sum._24h, - name: "24h", - color: colors.time._24h, - }), - ...satsBtcUsd({ - pattern: mining.rewards.fees.sum._1w, - name: "7d", - color: colors.time._1w, - }), - ...satsBtcUsd({ - pattern: mining.rewards.fees.sum._1m, - name: "30d", - color: colors.time._1m, - }), - ...satsBtcUsd({ - pattern: mining.rewards.fees.sum._1y, - name: "1y", - color: colors.time._1y, - }), - ], - }, - { - name: "24h", - title: "Fee 24h Rolling Sum", - bottom: satsBtcUsd({ - pattern: mining.rewards.fees.sum._24h, - name: "24h", - color: colors.time._24h, - }), - }, - { - name: "7d", - title: "Fee 7d Rolling Sum", - bottom: satsBtcUsd({ - pattern: mining.rewards.fees.sum._1w, - name: "7d", - color: colors.time._1w, - }), - }, - { - name: "30d", - title: "Fee 30d Rolling Sum", - bottom: satsBtcUsd({ - pattern: mining.rewards.fees.sum._1m, - name: "30d", - color: colors.time._1m, - }), - }, - { - name: "1y", - title: "Fee 1y Rolling Sum", - bottom: satsBtcUsd({ - pattern: mining.rewards.fees.sum._1y, - name: "1y", - color: colors.time._1y, - }), - }, - ], - }, - { - name: "Distribution", + name: "Distributions", tree: ROLLING_WINDOWS.map((w) => ({ name: w.name, - title: `Fee Revenue per Block Distribution (${w.name})`, + title: `Fee Revenue per Block ${w.title} Distribution`, bottom: distributionBtcSatsUsd(statsAtWindow(mining.rewards.fees, w.key)), })), }, - { - name: "Cumulative", - title: "Transaction Fee Revenue (Total)", - bottom: satsBtcUsdFrom({ - source: mining.rewards.fees, - key: "cumulative", - name: "all-time", - }), - }, ], }, { name: "Dominance", tree: [ - { - name: "Compare", - tree: [ - { - name: "Subsidy", - title: "Subsidy Dominance", - bottom: [ - ...percentRatio({ pattern: mining.rewards.subsidy.dominance, name: "All-time", color: colors.time.all }), - ...ROLLING_WINDOWS.flatMap((w) => - percentRatio({ pattern: mining.rewards.subsidy.dominance[w.key], name: w.name, color: w.color }), - ), - ], - }, - { - name: "Fees", - title: "Fee Dominance", - bottom: [ - ...percentRatio({ pattern: mining.rewards.fees.dominance, name: "All-time", color: colors.time.all }), - ...ROLLING_WINDOWS.flatMap((w) => - percentRatio({ pattern: mining.rewards.fees.dominance[w.key], name: w.name, color: w.color }), - ), - ], - }, + ...ROLLING_WINDOWS.map((w) => ({ + name: w.name, + title: `Revenue Dominance ${w.title}`, + bottom: [ + ...percentRatio({ pattern: mining.rewards.subsidy.dominance[w.key], name: "Subsidy", color: colors.mining.subsidy }), + ...percentRatio({ pattern: mining.rewards.fees.dominance[w.key], name: "Fees", color: colors.mining.fee }), ], - }, + })), { name: "All-time", title: "Revenue Dominance (All-time)", @@ -634,77 +385,28 @@ export function createMiningSection() { ...percentRatio({ pattern: mining.rewards.fees.dominance, name: "Fees", color: colors.mining.fee }), ], }, - ...ROLLING_WINDOWS.map((w) => ({ - name: w.name, - title: `Revenue Dominance (${w.name})`, - bottom: [ - ...percentRatio({ pattern: mining.rewards.subsidy.dominance[w.key], name: "Subsidy", color: colors.mining.subsidy }), - ...percentRatio({ pattern: mining.rewards.fees.dominance[w.key], name: "Fees", color: colors.mining.fee }), - ], - })), ], }, { name: "Fee Multiple", - tree: [ - { - name: "Compare", - title: "Fee-to-Subsidy Ratio", - bottom: ROLLING_WINDOWS.map((w) => - line({ series: mining.rewards.fees.toSubsidyRatio[w.key].ratio, name: w.name, color: w.color, unit: Unit.ratio }), - ), - }, - ...ROLLING_WINDOWS.map((w) => ({ - name: w.name, - title: `Fee-to-Subsidy Ratio (${w.name})`, - bottom: [line({ series: mining.rewards.fees.toSubsidyRatio[w.key].ratio, name: w.name, color: w.color, unit: Unit.ratio })], - })), - ], + tree: ROLLING_WINDOWS.map((w) => ({ + name: w.name, + title: `Fee-to-Subsidy Ratio ${w.title}`, + bottom: [line({ series: mining.rewards.fees.toSubsidyRatio[w.key].ratio, name: "Ratio", color: colors.mining.fee, unit: Unit.ratio })], + })), }, { name: "Unclaimed", - tree: [ - { - name: "Sum", - title: "Unclaimed Rewards", - bottom: satsBtcUsdFrom({ - source: mining.rewards.unclaimed, - key: "base", - name: "sum", - }), - }, - { - name: "Rolling", - tree: [ - { - name: "Compare", - title: "Unclaimed Rewards Rolling", - bottom: ROLLING_WINDOWS.flatMap((w) => - satsBtcUsd({ pattern: mining.rewards.unclaimed.sum[w.key], name: w.name, color: w.color }), - ), - }, - ...ROLLING_WINDOWS.map((w) => ({ - name: w.name, - title: `Unclaimed Rewards ${w.name}`, - bottom: satsBtcUsd({ pattern: mining.rewards.unclaimed.sum[w.key], name: w.name, color: w.color }), - })), - ], - }, - { - name: "Cumulative", - title: "Unclaimed Rewards (Total)", - bottom: satsBtcUsdFrom({ - source: mining.rewards.unclaimed, - key: "cumulative", - name: "all-time", - }), - }, - ], + title: "Unclaimed Rewards (Total)", + bottom: satsBtcUsdFrom({ + source: mining.rewards.unclaimed, + key: "cumulative", + name: "all-time", + }), }, ], }, - // Economics { name: "Economics", tree: [ @@ -712,60 +414,20 @@ export function createMiningSection() { name: "Hash Price", title: "Hash Price", bottom: [ - line({ - series: mining.hashrate.price.ths, - name: "TH/s", - color: colors.usd, - unit: Unit.usdPerThsPerDay, - }), - line({ - series: mining.hashrate.price.phs, - name: "PH/s", - color: colors.usd, - unit: Unit.usdPerPhsPerDay, - }), - dotted({ - series: mining.hashrate.price.thsMin, - name: "TH/s Min", - color: colors.stat.min, - unit: Unit.usdPerThsPerDay, - }), - dotted({ - series: mining.hashrate.price.phsMin, - name: "PH/s Min", - color: colors.stat.min, - unit: Unit.usdPerPhsPerDay, - }), + line({ series: mining.hashrate.price.ths, name: "TH/s", color: colors.usd, unit: Unit.usdPerThsPerDay }), + line({ series: mining.hashrate.price.phs, name: "PH/s", color: colors.usd, unit: Unit.usdPerPhsPerDay }), + dotted({ series: mining.hashrate.price.thsMin, name: "TH/s Min", color: colors.stat.min, unit: Unit.usdPerThsPerDay }), + dotted({ series: mining.hashrate.price.phsMin, name: "PH/s Min", color: colors.stat.min, unit: Unit.usdPerPhsPerDay }), ], }, { name: "Hash Value", title: "Hash Value", bottom: [ - line({ - series: mining.hashrate.value.ths, - name: "TH/s", - color: colors.bitcoin, - unit: Unit.satsPerThsPerDay, - }), - line({ - series: mining.hashrate.value.phs, - name: "PH/s", - color: colors.bitcoin, - unit: Unit.satsPerPhsPerDay, - }), - dotted({ - series: mining.hashrate.value.thsMin, - name: "TH/s Min", - color: colors.stat.min, - unit: Unit.satsPerThsPerDay, - }), - dotted({ - series: mining.hashrate.value.phsMin, - name: "PH/s Min", - color: colors.stat.min, - unit: Unit.satsPerPhsPerDay, - }), + line({ series: mining.hashrate.value.ths, name: "TH/s", color: colors.bitcoin, unit: Unit.satsPerThsPerDay }), + line({ series: mining.hashrate.value.phs, name: "PH/s", color: colors.bitcoin, unit: Unit.satsPerPhsPerDay }), + dotted({ series: mining.hashrate.value.thsMin, name: "TH/s Min", color: colors.stat.min, unit: Unit.satsPerThsPerDay }), + dotted({ series: mining.hashrate.value.phsMin, name: "PH/s Min", color: colors.stat.min, unit: Unit.satsPerPhsPerDay }), ], }, { @@ -779,7 +441,6 @@ export function createMiningSection() { ], }, - // Halving { name: "Halving", tree: [ @@ -787,122 +448,64 @@ export function createMiningSection() { name: "Countdown", title: "Next Halving", bottom: [ - line({ - series: blocks.halving.blocksToHalving, - name: "Remaining", - unit: Unit.blocks, - }), - line({ - series: blocks.halving.daysToHalving, - name: "Remaining", - unit: Unit.days, - }), + line({ series: blocks.halving.blocksToHalving, name: "Remaining", unit: Unit.blocks }), + line({ series: blocks.halving.daysToHalving, name: "Remaining", unit: Unit.days }), ], }, { name: "Epoch", title: "Halving Epoch", - bottom: [ - line({ - series: blocks.halving.epoch, - name: "Epoch", - unit: Unit.epoch, - }), - ], + bottom: [line({ series: blocks.halving.epoch, name: "Epoch", unit: Unit.epoch })], }, ], }, - // Pools + { + name: "Difficulty", + tree: [ + { + name: "Current", + title: "Mining Difficulty", + bottom: [line({ series: blocks.difficulty.value, name: "Difficulty", unit: Unit.difficulty })], + }, + { + name: "Adjustment", + title: "Difficulty Adjustment", + bottom: [baseline({ series: blocks.difficulty.adjustment.percent, name: "Change", unit: Unit.percentage })], + }, + { + name: "Countdown", + title: "Next Difficulty Adjustment", + bottom: [ + line({ series: blocks.difficulty.blocksToRetarget, name: "Remaining", unit: Unit.blocks }), + line({ series: blocks.difficulty.daysToRetarget, name: "Remaining", unit: Unit.days }), + ], + }, + { + name: "Epoch", + title: "Difficulty Epoch", + bottom: [line({ series: blocks.difficulty.epoch, name: "Epoch", unit: Unit.epoch })], + }, + ], + }, { name: "Pools", tree: [ - // Compare section (major pools only) - { - name: "Compare", - tree: [ - { - name: "Dominance", - title: "Dominance: Major Pools (1m)", - bottom: featuredPools.flatMap((p, i) => - percentRatio({ - pattern: p.pool.dominance._1m, - name: p.name, - color: colors.at(i, featuredPools.length), - }), - ), - }, - { - name: "Blocks Mined", - title: "Blocks Mined: Major Pools (1m)", - bottom: featuredPools.map((p, i) => - line({ - series: p.pool.blocksMined.sum._1m, - name: p.name, - color: colors.at(i, featuredPools.length), - unit: Unit.count, - }), - ), - }, - { - name: "Total Rewards", - title: "Total Rewards: Major Pools", - bottom: featuredPools.flatMap((p, i) => - satsBtcUsdFrom({ - source: p.pool.rewards, - key: "base", - name: p.name, - color: colors.at(i, featuredPools.length), - }), - ), - }, - ], - }, - // AntPool & friends - pools sharing block templates + createPoolCompare("Major Pools", featuredPools), { name: "AntPool & Friends", tree: [ - { - name: "Dominance", - title: "Dominance: AntPool & Friends (1m)", - bottom: antpoolFriends.flatMap((p, i) => - percentRatio({ - pattern: p.pool.dominance._1m, - name: p.name, - color: colors.at(i, antpoolFriends.length), - }), - ), - }, - { - name: "Blocks Mined", - title: "Blocks Mined: AntPool & Friends (1m)", - bottom: antpoolFriends.map((p, i) => - line({ - series: p.pool.blocksMined.sum._1m, - name: p.name, - color: colors.at(i, antpoolFriends.length), - unit: Unit.count, - }), - ), - }, - { - name: "Total Rewards", - title: "Total Rewards: AntPool & Friends", - bottom: antpoolFriends.flatMap((p, i) => - satsBtcUsdFrom({ - source: p.pool.rewards, - key: "base", - name: p.name, - color: colors.at(i, antpoolFriends.length), - }), - ), - }, + createPoolCompare("AntPool & Friends", antpoolFriends), + ...createPoolTree(antpoolFriends), ], }, - // All pools { - name: "All Pools", - tree: [...majorPoolsTree, ...minorPoolsTree], + name: "Major", + tree: createPoolTree(majorPoolData), + }, + { + name: "Minor", + tree: createMinorPoolTree(minorPoolData), }, ], }, diff --git a/website/scripts/options/network.js b/website/scripts/options/network.js index 758ddde76..310ecff15 100644 --- a/website/scripts/options/network.js +++ b/website/scripts/options/network.js @@ -8,21 +8,17 @@ import { priceLine } from "./constants.js"; import { line, dots, - baseline, fromSupplyPattern, chartsFromFullPerBlock, chartsFromCount, chartsFromCountEntries, chartsFromAggregatedPerBlock, - rollingWindowsTree, - + averagesTree, + simpleDeltaTree, ROLLING_WINDOWS, chartsFromBlockAnd6b, multiSeriesTree, - simpleDeltaTree, - percentRatio, percentRatioDots, - rollingPercentRatioTree, } from "./series.js"; import { satsBtcUsd, satsBtcUsdFrom, satsBtcUsdFullTree } from "./shared.js"; @@ -59,12 +55,7 @@ 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: false, - }, + { key: "opReturn", name: "OP_RETURN", color: st.opReturn, defaultActive: true }, { key: "emptyOutput", name: "Empty", @@ -82,42 +73,13 @@ export function createNetworkSection() { // All script types = addressable + non-addressable const scriptTypes = [...addressTypes, ...nonAddressableTypes]; - // Address type groups (by era) - const taprootAddresses = /** @type {const} */ ([ - { key: "p2a", name: "P2A", color: st.p2a }, - { key: "p2tr", name: "P2TR", color: st.p2tr }, - ]); - const segwitAddresses = /** @type {const} */ ([ - { key: "p2wsh", name: "P2WSH", color: st.p2wsh }, - { key: "p2wpkh", name: "P2WPKH", color: st.p2wpkh }, - ]); - const legacyAddresses = /** @type {const} */ ([ - { key: "p2sh", name: "P2SH", color: st.p2sh }, - { key: "p2pkh", name: "P2PKH", color: st.p2pkh }, - { key: "p2pk33", name: "P2PK33", color: st.p2pk33 }, - { key: "p2pk65", name: "P2PK65", color: st.p2pk65 }, - ]); // Transacting types (transaction participation) - const transactingTypes = /** @type {const} */ ([ - { - key: "sending", - name: "Sending", - title: "Unique Sending Addresses per Block", - compareTitle: "Unique Sending Addresses per Block by Type", - }, - { - key: "receiving", - name: "Receiving", - title: "Unique Receiving Addresses per Block", - compareTitle: "Unique Receiving Addresses per Block by Type", - }, - { - key: "both", - name: "Sending & Receiving", - title: "Unique Addresses Sending & Receiving per Block", - compareTitle: "Unique Addresses Sending & Receiving per Block by Type", - }, + const activityTypes = /** @type {const} */ ([ + { key: "sending", name: "Sending" }, + { key: "receiving", name: "Receiving" }, + { key: "both", name: "Both" }, + { key: "reactivated", name: "Reactivated" }, ]); const countTypes = /** @type {const} */ ([ @@ -141,311 +103,140 @@ export function createNetworkSection() { }, ]); + const countMetrics = /** @type {const} */ ([ + { key: "funded", name: "Funded", color: undefined }, + { key: "empty", name: "Empty", color: colors.gray }, + { key: "total", name: "Total", color: colors.default }, + ]); + /** - * Create address series tree for a given type key * @param {AddressableType | "all"} key * @param {string} titlePrefix */ const createAddressSeriesTree = (key, titlePrefix) => [ { name: "Count", - title: `${titlePrefix}Address Count`, - bottom: [ - line({ - series: addrs.funded[key], - name: "Funded", - unit: Unit.count, - }), - line({ - series: addrs.empty[key], - name: "Empty", - color: colors.gray, - unit: Unit.count, - defaultActive: false, - }), - line({ - series: addrs.total[key], - name: "Total", - color: colors.default, - unit: Unit.count, - defaultActive: false, - }), - ], - }, - { - name: "Trends", tree: [ - rollingWindowsTree({ - windows: addrs.delta[key].absolute, - title: `${titlePrefix}Address Count Change`, - unit: Unit.count, - series: baseline, - }), { - name: "New", - tree: (() => { - const p = addrs.new[key]; - const t = `${titlePrefix}New Address Count`; - return [ - { - name: "Sum", - title: t, - bottom: [ - line({ series: p.base, name: "base", unit: Unit.count }), - ], - }, - rollingWindowsTree({ - windows: p.sum, - title: t, - unit: Unit.count, - }), - { - name: "Cumulative", - title: `${t} (Total)`, - bottom: [ - line({ - series: p.cumulative, - name: "all-time", - unit: Unit.count, - }), - ], - }, - ]; - })(), - }, - { - name: "Reactivated", - tree: [ - { - name: "Base", - title: `${titlePrefix}Reactivated Addresses per Block`, - bottom: [ - dots({ - series: addrs.activity[key].reactivated.base, - name: "base", - unit: Unit.count, - }), - line({ - series: addrs.activity[key].reactivated._24h, - name: "24h avg", - color: colors.stat.avg, - unit: Unit.count, - }), - ], - }, - rollingWindowsTree({ - windows: addrs.activity[key].reactivated, - title: `${titlePrefix}Reactivated Addresses`, - unit: Unit.count, - }), - ], - }, - rollingPercentRatioTree({ - windows: addrs.delta[key].rate, - title: `${titlePrefix}Address Growth Rate`, - }), - ], - }, - { - name: "Transacting", - tree: transactingTypes.map((t) => ({ - name: t.name, - tree: [ - { - name: "Base", - title: `${titlePrefix}${t.title}`, - bottom: [ - dots({ - series: addrs.activity[key][t.key].base, - name: "base", - unit: Unit.count, - }), - line({ - series: addrs.activity[key][t.key]._24h, - name: "24h avg", - color: colors.stat.avg, - unit: Unit.count, - }), - ], - }, - rollingWindowsTree({ - windows: addrs.activity[key][t.key], - title: `${titlePrefix}${t.title.replace(" per Block", "")}`, - unit: Unit.count, - }), - ], - })), - }, - ]; - - /** - * Create Compare charts for an address group - * @template {AddressableType} K - * @param {string} groupName - * @param {ReadonlyArray<{key: K, name: string, color: Color}>} types - */ - const createAddressCompare = (groupName, types) => ({ - name: "Compare", - tree: [ - { - name: "Count", - tree: countTypes.map((c) => ({ - name: c.name, - title: `${groupName} ${c.title}`, - bottom: types.map((t) => + name: "Compare", + title: `${titlePrefix}Address Count`, + bottom: countMetrics.map((m) => line({ - series: c.getSeries(t.key), - name: t.name, - color: t.color, + series: addrs[m.key][key], + name: m.name, + color: m.color, unit: Unit.count, }), ), + }, + ...countMetrics.map((m) => ({ + name: m.name, + title: `${titlePrefix}${m.name} Addresses`, + bottom: [ + line({ series: addrs[m.key][key], name: m.name, unit: Unit.count }), + ], })), - }, - { - name: "New", - title: `${groupName} New Address Count`, - bottom: types.flatMap((t) => [ - line({ - series: addrs.new[t.key].base, - name: t.name, - color: t.color, - unit: Unit.count, - }), - line({ - series: addrs.new[t.key].sum._24h, - name: t.name, - color: t.color, - unit: Unit.count, - }), - ]), - }, - { - name: "Reactivated", - tree: [ - { - name: "Base", - title: `${groupName} Reactivated Addresses per Block`, - bottom: types.map((t) => + ], + }, + ...simpleDeltaTree({ + delta: addrs.delta[key], + title: `${titlePrefix}Address Count`, + unit: Unit.count, + }), + { + name: "New", + tree: chartsFromCount({ + pattern: addrs.new[key], + title: `${titlePrefix}New Addresses`, + unit: Unit.count, + }), + }, + { + name: "Activity", + tree: [ + { + name: "Compare", + tree: ROLLING_WINDOWS.map((w) => ({ + name: w.name, + title: `${titlePrefix}Active Addresses ${w.title} Average`, + bottom: activityTypes.map((t, i) => line({ - series: addrs.activity[t.key].reactivated.base, + series: addrs.activity[key][t.key][w.key], name: t.name, - color: t.color, + color: colors.at(i, activityTypes.length), unit: Unit.count, }), ), - }, + })), + }, + ...activityTypes.map((t) => + averagesTree({ + windows: addrs.activity[key][t.key], + title: `${titlePrefix}${t.name} Addresses`, + unit: Unit.count, + name: t.name, + }), + ), + ], + }, + ]; + + /** @type {Record} */ + const byKey = Object.fromEntries(scriptTypes.map((t) => [t.key, t])); + + const scriptGroups = [ + { name: "Legacy", types: [byKey.p2pkh, byKey.p2pk33, byKey.p2pk65] }, + { 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] }, + ]; + + /** + * @template {keyof typeof scripts.count} K + * @param {string} groupName + * @param {ReadonlyArray<{key: K, name: string, color: Color}>} types + */ + const createScriptGroup = (groupName, types) => ({ + name: groupName, + tree: [ + { + name: "Compare", + tree: [ ...ROLLING_WINDOWS.map((w) => ({ name: w.name, - title: `${groupName} Reactivated Addresses (${w.name})`, + title: `${groupName} Output Count ${w.title} Sum`, bottom: types.map((t) => line({ - series: addrs.activity[t.key].reactivated[w.key], + series: /** @type {CountPattern} */ (scripts.count[t.key]).sum[w.key], name: t.name, color: t.color, unit: Unit.count, }), ), })), + { + name: "Cumulative", + title: `${groupName} Output Count (Total)`, + bottom: types.map((t) => + line({ + series: scripts.count[t.key].cumulative, + name: t.name, + color: t.color, + unit: Unit.count, + }), + ), + }, ], }, - { - name: "Growth Rate", - tree: ROLLING_WINDOWS.map((w) => ({ - name: w.name, - title: `${groupName} Address Growth Rate (${w.name})`, - bottom: types.flatMap((t) => - percentRatio({ - pattern: addrs.delta[t.key].rate[w.key], - name: t.name, - color: t.color, - }), - ), - })), - }, - { - name: "Transacting", - tree: transactingTypes.map((tr) => ({ - name: tr.name, - tree: [ - { - name: "Base", - title: `${groupName} ${tr.compareTitle}`, - bottom: types.map((t) => - line({ - series: addrs.activity[t.key][tr.key].base, - name: t.name, - color: t.color, - unit: Unit.count, - }), - ), - }, - ...ROLLING_WINDOWS.map((w) => ({ - name: w.name, - title: `${groupName} ${tr.compareTitle} (${w.name})`, - bottom: types.map((t) => - line({ - series: addrs.activity[t.key][tr.key][w.key], - name: t.name, - color: t.color, - unit: Unit.count, - }), - ), - })), - ], - })), - }, - ], - }); - - // Script type groups for Output Counts - const legacyScripts = legacyAddresses.slice(1); // p2pkh, p2pk33, p2pk65 - const scriptHashScripts = [legacyAddresses[0], nonAddressableTypes[0]]; // p2sh, p2ms - const segwitScripts = [ - /** @type {const} */ ({ - key: "segwit", - name: "All SegWit", - color: colors.segwit, - }), - ...segwitAddresses, - ]; - const otherScripts = nonAddressableTypes.slice(1); // opreturn, empty, unknown - - /** - * Create Compare charts for a script group - * @template {keyof typeof scripts.count} K - * @param {string} groupName - * @param {ReadonlyArray<{key: K, name: string, color: Color}>} types - */ - const createScriptCompare = (groupName, types) => ({ - name: "Compare", - tree: [ - { - name: "Sum", - title: `${groupName} Output Count`, - bottom: types.map((t) => - line({ - series: /** @type {CountPattern} */ (scripts.count[t.key]) - .sum._24h, - name: t.name, - color: t.color, - unit: Unit.count, - }), - ), - }, - { - name: "Cumulative", - title: `${groupName} Output Count (Total)`, - bottom: types.map((t) => - line({ - series: /** @type {CountPattern} */ (scripts.count[t.key]) - .cumulative, - name: t.name, - color: t.color, - unit: Unit.count, - }), - ), - }, + ...types.map((t) => ({ + name: t.name, + tree: chartsFromCount({ + pattern: /** @type {CountPattern} */ (scripts.count[t.key]), + title: `${t.name} Output Count`, + unit: Unit.count, + }), + })), ], }); @@ -563,47 +354,19 @@ export function createNetworkSection() { }, { name: "Velocity", - tree: [ - { - name: "Compare", - title: "Transaction Velocity", - bottom: [ - line({ - series: supply.velocity.native, - name: "BTC", - unit: Unit.ratio, - }), - line({ - series: supply.velocity.fiat, - name: "USD", - color: colors.usd, - unit: Unit.ratio, - }), - ], - }, - { - name: "Native", - title: "Transaction Velocity (BTC)", - bottom: [ - line({ - series: supply.velocity.native, - name: "BTC", - unit: Unit.ratio, - }), - ], - }, - { - name: "Fiat", - title: "Transaction Velocity (USD)", - bottom: [ - line({ - series: supply.velocity.fiat, - name: "USD", - color: colors.usd, - unit: Unit.ratio, - }), - ], - }, + title: "Transaction Velocity", + bottom: [ + line({ + series: supply.velocity.native, + name: "BTC", + unit: Unit.ratio, + }), + line({ + series: supply.velocity.fiat, + name: "USD", + color: colors.usd, + unit: Unit.ratio, + }), ], }, ], @@ -621,7 +384,7 @@ export function createNetworkSection() { tree: [ { name: "Compare", - title: "Block Count Rolling", + title: "Block Count", bottom: ROLLING_WINDOWS.map((w) => line({ series: blocks.count.total.sum[w.key], @@ -633,7 +396,7 @@ export function createNetworkSection() { }, ...ROLLING_WINDOWS.map((w) => ({ name: w.name, - title: `Block Count ${w.name}`, + title: `Block Count ${w.title} Sum`, bottom: [ line({ series: blocks.count.total.sum[w.key], @@ -655,7 +418,11 @@ export function createNetworkSection() { name: "Cumulative", title: "Block Count (Total)", bottom: [ - { series: blocks.count.total.cumulative, title: "all-time", unit: Unit.count }, + { + series: blocks.count.total.cumulative, + title: "all-time", + unit: Unit.count, + }, ], }, ], @@ -681,10 +448,9 @@ export function createNetworkSection() { priceLine({ unit: Unit.secs, name: "Target", number: 600 }), ], }, - rollingWindowsTree({ + averagesTree({ windows: blocks.interval, title: "Block Interval", - name: "Averages", unit: Unit.secs, }), ], @@ -778,10 +544,10 @@ export function createNetworkSection() { }), }, { - name: "Activity Rate", + name: "Throughput", tree: ROLLING_WINDOWS.map((w) => ({ name: w.name, - title: `Activity Rate (${w.name})`, + title: `Throughput ${w.title} Average`, bottom: [ line({ series: transactions.volume.txPerSec[w.key], @@ -809,15 +575,12 @@ export function createNetworkSection() { { name: "Addresses", tree: [ - // Overview - global series for all addresses - { name: "Overview", tree: createAddressSeriesTree("all", "") }, - - // Top-level Compare - all types + ...createAddressSeriesTree("all", ""), { - name: "Compare", + name: "By Type", tree: [ { - name: "Count", + name: "Compare", tree: countTypes.map((c) => ({ name: c.name, title: c.title, @@ -832,139 +595,7 @@ export function createNetworkSection() { ), })), }, - { - name: "New", - title: "New Address Count by Type", - bottom: addressTypes.flatMap((t) => [ - line({ - series: addrs.new[t.key].base, - name: t.name, - color: t.color, - unit: Unit.count, - defaultActive: t.defaultActive, - }), - line({ - series: addrs.new[t.key].sum._24h, - name: t.name, - color: t.color, - unit: Unit.count, - defaultActive: t.defaultActive, - }), - ]), - }, - { - name: "Reactivated", - tree: [ - { - name: "Base", - title: "Reactivated Addresses per Block by Type", - bottom: addressTypes.map((t) => - line({ - series: addrs.activity[t.key].reactivated.base, - name: t.name, - color: t.color, - unit: Unit.count, - defaultActive: t.defaultActive, - }), - ), - }, - ...ROLLING_WINDOWS.map((w) => ({ - name: w.name, - title: `Reactivated Addresses by Type (${w.name})`, - bottom: addressTypes.map((t) => - line({ - series: addrs.activity[t.key].reactivated[w.key], - name: t.name, - color: t.color, - unit: Unit.count, - defaultActive: t.defaultActive, - }), - ), - })), - ], - }, - { - name: "Growth Rate", - tree: ROLLING_WINDOWS.map((w) => ({ - name: w.name, - title: `Address Growth Rate by Type (${w.name})`, - bottom: addressTypes.flatMap((t) => - percentRatio({ - pattern: addrs.delta[t.key].rate[w.key], - name: t.name, - color: t.color, - defaultActive: t.defaultActive, - }), - ), - })), - }, - { - name: "Transacting", - tree: transactingTypes.map((tr) => ({ - name: tr.name, - tree: [ - { - name: "Base", - title: tr.compareTitle, - bottom: addressTypes.map((t) => - line({ - series: addrs.activity[t.key][tr.key].base, - name: t.name, - color: t.color, - unit: Unit.count, - defaultActive: t.defaultActive, - }), - ), - }, - ...ROLLING_WINDOWS.map((w) => ({ - name: w.name, - title: `${tr.compareTitle} (${w.name})`, - bottom: addressTypes.map((t) => - line({ - series: addrs.activity[t.key][tr.key][w.key], - name: t.name, - color: t.color, - unit: Unit.count, - defaultActive: t.defaultActive, - }), - ), - })), - ], - })), - }, - ], - }, - - // Legacy (pre-SegWit) - { - name: "Legacy", - tree: [ - createAddressCompare("Legacy", legacyAddresses), - ...legacyAddresses.map((t) => ({ - name: t.name, - tree: createAddressSeriesTree(t.key, `${t.name} `), - })), - ], - }, - - // SegWit - { - name: "SegWit", - tree: [ - createAddressCompare("SegWit", segwitAddresses), - ...segwitAddresses.map((t) => ({ - name: t.name, - tree: createAddressSeriesTree(t.key, `${t.name} `), - })), - ], - }, - - // Taproot - { - name: "Taproot", - tree: [ - createAddressCompare("Taproot", taprootAddresses), - ...taprootAddresses.map((t) => ({ + ...addressTypes.map((t) => ({ name: t.name, tree: createAddressSeriesTree(t.key, `${t.name} `), })), @@ -978,191 +609,37 @@ export function createNetworkSection() { name: "Scripts", tree: [ { - name: "By Type", + name: "Compare", tree: [ - // Compare section - { - name: "Compare", - tree: [ - { - name: "Sum", - title: "Output Count by Script Type", - bottom: scriptTypes.map((t) => - line({ - series: /** @type {CountPattern} */ ( - scripts.count[t.key] - ).sum._24h, - name: t.name, - color: t.color, - unit: Unit.count, - defaultActive: t.defaultActive, - }), - ), - }, - { - name: "Cumulative", - title: "Output Count by Script Type (Total)", - bottom: scriptTypes.map((t) => - line({ - series: scripts.count[t.key].cumulative, - name: t.name, - color: t.color, - unit: Unit.count, - defaultActive: t.defaultActive, - }), - ), - }, - ], - }, - { - name: "Legacy", - tree: [ - createScriptCompare("Legacy", legacyScripts), - ...legacyScripts.map((t) => ({ + ...ROLLING_WINDOWS.map((w) => ({ + name: w.name, + title: `Output Count by Script Type ${w.title} Sum`, + bottom: scriptTypes.map((t) => + line({ + series: /** @type {CountPattern} */ (scripts.count[t.key]).sum[w.key], name: t.name, - tree: chartsFromCount({ - pattern: /** @type {CountPattern} */ ( - scripts.count[t.key] - ), - title: `${t.name} Output Count`, - unit: Unit.count, - }), - })), - ], - }, + color: t.color, + unit: Unit.count, + defaultActive: t.defaultActive, + }), + ), + })), { - name: "Script Hash", - tree: [ - createScriptCompare("Script Hash", scriptHashScripts), - ...scriptHashScripts.map((t) => ({ + name: "Cumulative", + title: "Output Count by Script Type (Total)", + bottom: scriptTypes.map((t) => + line({ + series: scripts.count[t.key].cumulative, name: t.name, - tree: chartsFromCount({ - pattern: /** @type {CountPattern} */ ( - scripts.count[t.key] - ), - title: `${t.name} Output Count`, - unit: Unit.count, - }), - })), - ], - }, - { - name: "SegWit", - tree: [ - createScriptCompare("SegWit", segwitScripts), - ...segwitScripts.map((t) => ({ - name: t.name, - tree: chartsFromCount({ - pattern: /** @type {CountPattern} */ ( - scripts.count[t.key] - ), - title: `${t.name} Output Count`, - unit: Unit.count, - }), - })), - ], - }, - { - name: "Taproot", - tree: [ - createScriptCompare("Taproot", taprootAddresses), - ...taprootAddresses.map((t) => ({ - name: t.name, - tree: chartsFromCount({ - pattern: /** @type {CountPattern} */ ( - scripts.count[t.key] - ), - title: `${t.name} Output Count`, - unit: Unit.count, - }), - })), - ], - }, - { - name: "Other", - tree: [ - createScriptCompare("Other", otherScripts), - ...otherScripts.map((t) => ({ - name: t.name, - tree: chartsFromCount({ - pattern: /** @type {CountPattern} */ ( - scripts.count[t.key] - ), - title: `${t.name} Output Count`, - unit: Unit.count, - }), - })), - ], - }, - ], - }, - { - name: "Adoption", - tree: [ - { - name: "Compare", - title: "Script Adoption", - bottom: [ - line({ - series: scripts.adoption.segwit.percent, - name: "SegWit", - color: colors.segwit, - unit: Unit.percentage, - }), - line({ - series: scripts.adoption.segwit.ratio, - name: "SegWit", - color: colors.segwit, - unit: Unit.ratio, - }), - line({ - series: scripts.adoption.taproot.percent, - name: "Taproot", - color: taprootAddresses[1].color, - unit: Unit.percentage, - }), - line({ - series: scripts.adoption.taproot.ratio, - name: "Taproot", - color: taprootAddresses[1].color, - unit: Unit.ratio, - }), - ], - }, - { - name: "SegWit", - title: "SegWit Adoption", - bottom: [ - line({ - series: scripts.adoption.segwit.percent, - name: "Adoption", - unit: Unit.percentage, - }), - line({ - series: scripts.adoption.segwit.ratio, - name: "Adoption", - unit: Unit.ratio, - }), - ], - }, - { - name: "Taproot", - title: "Taproot Adoption", - bottom: [ - line({ - series: scripts.adoption.taproot.percent, - name: "Adoption", - unit: Unit.percentage, - }), - line({ - series: scripts.adoption.taproot.ratio, - name: "Adoption", - unit: Unit.ratio, - }), - ], + color: t.color, + unit: Unit.count, + defaultActive: t.defaultActive, + }), + ), }, ], }, + ...scriptGroups.map((g) => createScriptGroup(g.name, g.types)), ], }, ], diff --git a/website/scripts/options/partial.js b/website/scripts/options/partial.js index 0621aea80..7f56e5f13 100644 --- a/website/scripts/options/partial.js +++ b/website/scripts/options/partial.js @@ -153,7 +153,9 @@ export function createPartialOptions() { list: utxosUnderAmount, all: cohortAll, }), - ...utxosUnderAmount.map(createCohortFolderBasicWithMarketCap), + ...utxosUnderAmount.map( + createCohortFolderBasicWithMarketCap, + ), ], }, // More Than (≥ X sats) @@ -166,7 +168,9 @@ export function createPartialOptions() { list: utxosOverAmount, all: cohortAll, }), - ...utxosOverAmount.map(createCohortFolderBasicWithMarketCap), + ...utxosOverAmount.map( + createCohortFolderBasicWithMarketCap, + ), ], }, // Range @@ -179,7 +183,9 @@ export function createPartialOptions() { list: utxosAmountRange, all: cohortAll, }), - ...utxosAmountRange.map(createCohortFolderBasicWithoutMarketCap), + ...utxosAmountRange.map( + createCohortFolderBasicWithoutMarketCap, + ), ], }, ], @@ -283,14 +289,14 @@ export function createPartialOptions() { ], }, + // Investing section + createInvestingSection(), + // Frameworks section { name: "Frameworks", tree: [createCointimeSection()], }, - - // Investing section - createInvestingSection(), ], }, diff --git a/website/scripts/options/series.js b/website/scripts/options/series.js index 0b913812c..e3a2e7361 100644 --- a/website/scripts/options/series.js +++ b/website/scripts/options/series.js @@ -9,12 +9,12 @@ import { Unit } from "../utils/units.js"; /** @typedef {'_24h' | '_1w' | '_1m' | '_1y'} RollingWindowKey */ -/** @type {ReadonlyArray<{key: RollingWindowKey, name: string, color: Color}>} */ +/** @type {ReadonlyArray<{key: RollingWindowKey, name: string, title: string, color: Color}>} */ export const ROLLING_WINDOWS = [ - { key: "_24h", name: "24h", color: colors.time._24h }, - { key: "_1w", name: "1w", color: colors.time._1w }, - { key: "_1m", name: "1m", color: colors.time._1m }, - { key: "_1y", name: "1y", color: colors.time._1y }, + { key: "_24h", name: "24h", title: "Daily", color: colors.time._24h }, + { key: "_1w", name: "1w", title: "Weekly", color: colors.time._1w }, + { key: "_1m", name: "1m", title: "Monthly", color: colors.time._1m }, + { key: "_1y", name: "1y", title: "Yearly", color: colors.time._1y }, ]; /** @type {ReadonlyArray<{key: '_24h' | '_1w' | '_1m', name: string, color: Color}>} */ @@ -474,19 +474,20 @@ export function statsAtWindow(pattern, window) { * Create a Rolling folder tree from a _1m1w1y24hPattern (4 rolling windows) * @param {Object} args * @param {{ _24h: AnySeriesPattern, _1w: AnySeriesPattern, _1m: AnySeriesPattern, _1y: AnySeriesPattern }} args.windows - * @param {string} args.title + * @param {string} args.title - Compare chart title + * @param {(w: typeof ROLLING_WINDOWS[number]) => string} args.windowTitle - Individual window chart title * @param {Unit} args.unit - * @param {string} [args.name] + * @param {string} args.name * @param {(args: {series: AnySeriesPattern, name: string, color: Color, unit: Unit}) => AnyFetchedSeriesBlueprint} [args.series] * @returns {PartialOptionsGroup} */ -export function rollingWindowsTree({ windows, title, unit, name = "Sums", series = line }) { +function rollingWindowsTree({ windows, title, windowTitle, unit, name, series = line }) { return { name, tree: [ { name: "Compare", - title: `${title} Rolling`, + title, bottom: ROLLING_WINDOWS.map((w) => series({ series: windows[w.key], @@ -498,7 +499,7 @@ export function rollingWindowsTree({ windows, title, unit, name = "Sums", series }, ...ROLLING_WINDOWS.map((w) => ({ name: w.name, - title: `${title} ${w.name}`, + title: windowTitle(w), bottom: [ series({ series: windows[w.key], @@ -512,6 +513,32 @@ export function rollingWindowsTree({ windows, title, unit, name = "Sums", series }; } +/** + * Rolling sums tree + * @param {Object} args + * @param {{ _24h: AnySeriesPattern, _1w: AnySeriesPattern, _1m: AnySeriesPattern, _1y: AnySeriesPattern }} args.windows + * @param {string} args.title + * @param {Unit} args.unit + * @param {(args: {series: AnySeriesPattern, name: string, color: Color, unit: Unit}) => AnyFetchedSeriesBlueprint} [args.series] + * @returns {PartialOptionsGroup} + */ +export function sumsTree({ windows, title, unit, series }) { + return rollingWindowsTree({ windows, title, windowTitle: (w) => `${title} ${w.title} Sum`, unit, name: "Sums", ...(series ? { series } : {}) }); +} + +/** + * Rolling averages tree + * @param {Object} args + * @param {{ _24h: AnySeriesPattern, _1w: AnySeriesPattern, _1m: AnySeriesPattern, _1y: AnySeriesPattern }} args.windows + * @param {string} args.title + * @param {Unit} args.unit + * @param {string} [args.name] + * @returns {PartialOptionsGroup} + */ +export function averagesTree({ windows, title, unit, name = "Averages" }) { + return rollingWindowsTree({ windows, title, windowTitle: (w) => `${title} ${w.title} Average`, unit, name }); +} + /** * Create a Distribution folder tree with stats at each rolling window (24h/7d/30d/1y) * @param {Object} args @@ -819,7 +846,7 @@ export function chartsFromFull({ title, bottom: [{ series: pattern.base, title: "base", unit }], }, - rollingWindowsTree({ windows: pattern.sum, title, unit }), + sumsTree({ windows: pattern.sum, title, unit }), distributionWindowsTree({ pattern, title: distTitle, unit }), { name: "Cumulative", @@ -865,7 +892,7 @@ export function chartsFromAggregated({ title, bottom: [{ series: pattern.sum, title: "base", color: stat.sum, unit }], }, - rollingWindowsTree({ windows: pattern.rolling.sum, title, unit }), + sumsTree({ windows: pattern.rolling.sum, title, unit }), distributionWindowsTree({ pattern: pattern.rolling, title: distTitle, unit }), { name: "Cumulative", @@ -920,7 +947,7 @@ export function chartsFromBlockAnd6b({ pattern, title, unit }) { */ export function chartsFromSumsCumulative({ pattern, title, unit, color }) { return [ - rollingWindowsTree({ windows: pattern.sum, title, unit }), + sumsTree({ windows: pattern.sum, title, unit }), { name: "Cumulative", title: `${title} (Total)`, @@ -988,16 +1015,13 @@ export function multiSeriesTree({ entries, title, unit }) { line({ series: e.base, name: e.name, color: e.color, unit }), ), }, - { - name: "Sums", - tree: ROLLING_WINDOWS.map((w) => ({ - name: w.name, - title: `${title} (${w.name})`, - bottom: entries.map((e) => - line({ series: e.rolling[w.key], name: e.name, color: e.color, unit }), - ), - })), - }, + ...ROLLING_WINDOWS.map((w) => ({ + name: w.name, + title: `${title} ${w.title} Sum`, + bottom: entries.map((e) => + line({ series: e.rolling[w.key], name: e.name, color: e.color, unit }), + ), + })), { name: "Cumulative", title: `${title} (Total)`,