From b397b811f938dfbd57dec56a7ab99b639b885efb Mon Sep 17 00:00:00 2001 From: nym21 Date: Wed, 18 Mar 2026 12:02:53 +0100 Subject: [PATCH] global: snapshot --- crates/brk_client/src/lib.rs | 139 +++-- .../brk_computer/src/blocks/count/import.rs | 16 +- crates/brk_computer/src/blocks/count/vecs.rs | 4 +- crates/brk_computer/src/blocks/mod.rs | 8 - .../brk_computer/src/blocks/weight/compute.rs | 16 +- .../brk_computer/src/blocks/weight/import.rs | 10 +- crates/brk_computer/src/blocks/weight/vecs.rs | 4 +- .../src/distribution/compute/block_loop.rs | 4 +- crates/brk_computer/src/indicators/gini.rs | 14 +- .../src/internal/aggregate/distribution.rs | 124 ----- .../internal/aggregate/distribution_full.rs | 75 --- .../src/internal/aggregate/mod.rs | 7 - .../src/internal/containers/mod.rs | 2 + .../src/internal/containers/percent.rs | 8 + crates/brk_computer/src/internal/mod.rs | 2 - .../internal/per_block/computed/aggregated.rs | 16 +- .../per_block/computed/distribution.rs | 103 ++++ .../per_block/computed/distribution_full.rs | 73 +++ .../computed}/lazy_distribution.rs | 26 +- .../src/internal/per_block/computed/mod.rs | 14 +- .../src/internal/per_block/lazy/base.rs | 19 +- .../src/internal/per_block/percent/base.rs | 20 +- .../src/internal/per_block/percent/lazy.rs | 18 +- .../src/internal/per_block/percent/mod.rs | 4 +- .../per_block/percent/rolling_average.rs | 66 --- .../src/internal/per_block/percent/vec.rs | 43 ++ .../src/internal/per_tx/derived.rs | 49 +- .../src/internal/per_tx/distribution.rs | 4 +- .../src/internal/per_tx/lazy_derived.rs | 15 +- .../src/internal/per_tx/lazy_distribution.rs | 3 +- .../src/internal/transform/mod.rs | 2 +- .../src/internal/transform/specialized.rs | 142 +---- .../brk_computer/src/outputs/count/compute.rs | 4 +- crates/brk_computer/src/scripts/adoption.rs | 4 +- .../src/transactions/fees/import.rs | 8 +- .../brk_computer/src/transactions/import.rs | 4 +- .../src/transactions/size/import.rs | 5 +- .../src/transactions/volume/compute.rs | 51 +- .../src/transactions/volume/import.rs | 7 - .../src/transactions/volume/vecs.rs | 1 - modules/brk-client/index.js | 136 +++-- packages/brk_client/brk_client/__init__.py | 69 ++- website/scripts/options/market.js | 117 +--- website/scripts/options/network.js | 499 +++++------------- website/scripts/options/series.js | 177 ++++++- website/scripts/options/shared.js | 8 +- 46 files changed, 943 insertions(+), 1197 deletions(-) delete mode 100644 crates/brk_computer/src/internal/aggregate/distribution.rs delete mode 100644 crates/brk_computer/src/internal/aggregate/distribution_full.rs delete mode 100644 crates/brk_computer/src/internal/aggregate/mod.rs create mode 100644 crates/brk_computer/src/internal/containers/percent.rs create mode 100644 crates/brk_computer/src/internal/per_block/computed/distribution.rs create mode 100644 crates/brk_computer/src/internal/per_block/computed/distribution_full.rs rename crates/brk_computer/src/internal/{aggregate => per_block/computed}/lazy_distribution.rs (73%) delete mode 100644 crates/brk_computer/src/internal/per_block/percent/rolling_average.rs create mode 100644 crates/brk_computer/src/internal/per_block/percent/vec.rs diff --git a/crates/brk_client/src/lib.rs b/crates/brk_client/src/lib.rs index aa99847b7..4cc2efcd0 100644 --- a/crates/brk_client/src/lib.rs +++ b/crates/brk_client/src/lib.rs @@ -1087,34 +1087,34 @@ pub struct CapGrossInvestorLossMvrvNetPeakPriceProfitSellSoprPattern { /// Pattern struct for repeated tree structure. pub struct AverageCumulativeMaxMedianMinPct10Pct25Pct75Pct90RollingSumPattern { - pub average: SeriesPattern18, - pub cumulative: SeriesPattern18, - pub max: SeriesPattern18, - pub median: SeriesPattern18, - pub min: SeriesPattern18, - pub pct10: SeriesPattern18, - pub pct25: SeriesPattern18, - pub pct75: SeriesPattern18, - pub pct90: SeriesPattern18, + pub average: SeriesPattern1, + pub cumulative: SeriesPattern1, + pub max: SeriesPattern1, + pub median: SeriesPattern1, + pub min: SeriesPattern1, + pub pct10: SeriesPattern1, + pub pct25: SeriesPattern1, + pub pct75: SeriesPattern1, + pub pct90: SeriesPattern1, pub rolling: AverageMaxMedianMinPct10Pct25Pct75Pct90SumPattern, - pub sum: SeriesPattern18, + pub sum: SeriesPattern1, } impl AverageCumulativeMaxMedianMinPct10Pct25Pct75Pct90RollingSumPattern { /// Create a new pattern node with accumulated series name. pub fn new(client: Arc, acc: String) -> Self { Self { - average: SeriesPattern18::new(client.clone(), _m(&acc, "average")), - cumulative: SeriesPattern18::new(client.clone(), _m(&acc, "cumulative")), - max: SeriesPattern18::new(client.clone(), _m(&acc, "max")), - median: SeriesPattern18::new(client.clone(), _m(&acc, "median")), - min: SeriesPattern18::new(client.clone(), _m(&acc, "min")), - pct10: SeriesPattern18::new(client.clone(), _m(&acc, "pct10")), - pct25: SeriesPattern18::new(client.clone(), _m(&acc, "pct25")), - pct75: SeriesPattern18::new(client.clone(), _m(&acc, "pct75")), - pct90: SeriesPattern18::new(client.clone(), _m(&acc, "pct90")), + average: SeriesPattern1::new(client.clone(), _m(&acc, "average")), + cumulative: SeriesPattern1::new(client.clone(), _m(&acc, "cumulative")), + max: SeriesPattern1::new(client.clone(), _m(&acc, "max")), + median: SeriesPattern1::new(client.clone(), _m(&acc, "median")), + min: SeriesPattern1::new(client.clone(), _m(&acc, "min")), + pct10: SeriesPattern1::new(client.clone(), _m(&acc, "pct10")), + pct25: SeriesPattern1::new(client.clone(), _m(&acc, "pct25")), + pct75: SeriesPattern1::new(client.clone(), _m(&acc, "pct75")), + pct90: SeriesPattern1::new(client.clone(), _m(&acc, "pct90")), rolling: AverageMaxMedianMinPct10Pct25Pct75Pct90SumPattern::new(client.clone(), acc.clone()), - sum: SeriesPattern18::new(client.clone(), _m(&acc, "sum")), + sum: SeriesPattern1::new(client.clone(), _m(&acc, "sum")), } } } @@ -1260,6 +1260,34 @@ impl AverageMaxMedianMinPct10Pct25Pct75Pct90SumPattern { } } +/// Pattern struct for repeated tree structure. +pub struct AverageMaxMedianMinPct10Pct25Pct75Pct90Pattern2 { + pub average: SeriesPattern18, + pub max: SeriesPattern18, + pub median: SeriesPattern18, + pub min: SeriesPattern18, + pub pct10: SeriesPattern18, + pub pct25: SeriesPattern18, + pub pct75: SeriesPattern18, + pub pct90: SeriesPattern18, +} + +impl AverageMaxMedianMinPct10Pct25Pct75Pct90Pattern2 { + /// Create a new pattern node with accumulated series name. + pub fn new(client: Arc, acc: String) -> Self { + Self { + average: SeriesPattern18::new(client.clone(), _m(&acc, "average")), + max: SeriesPattern18::new(client.clone(), _m(&acc, "max")), + median: SeriesPattern18::new(client.clone(), _m(&acc, "median")), + min: SeriesPattern18::new(client.clone(), _m(&acc, "min")), + pct10: SeriesPattern18::new(client.clone(), _m(&acc, "pct10")), + pct25: SeriesPattern18::new(client.clone(), _m(&acc, "pct25")), + pct75: SeriesPattern18::new(client.clone(), _m(&acc, "pct75")), + pct90: SeriesPattern18::new(client.clone(), _m(&acc, "pct90")), + } + } +} + /// Pattern struct for repeated tree structure. pub struct BaseCapitulationCumulativeNegativeSumToValuePattern { pub base: CentsUsdPattern2, @@ -1286,28 +1314,28 @@ pub struct BpsCentsPercentilesRatioSatsSmaStdUsdPattern { /// Pattern struct for repeated tree structure. pub struct AverageMaxMedianMinPct10Pct25Pct75Pct90Pattern { - pub average: SeriesPattern18, - pub max: SeriesPattern18, - pub median: SeriesPattern18, - pub min: SeriesPattern18, - pub pct10: SeriesPattern18, - pub pct25: SeriesPattern18, - pub pct75: SeriesPattern18, - pub pct90: SeriesPattern18, + pub average: SeriesPattern1, + pub max: SeriesPattern1, + pub median: SeriesPattern1, + pub min: SeriesPattern1, + pub pct10: SeriesPattern1, + pub pct25: SeriesPattern1, + pub pct75: SeriesPattern1, + pub pct90: SeriesPattern1, } impl AverageMaxMedianMinPct10Pct25Pct75Pct90Pattern { /// Create a new pattern node with accumulated series name. pub fn new(client: Arc, acc: String) -> Self { Self { - average: SeriesPattern18::new(client.clone(), _m(&acc, "average")), - max: SeriesPattern18::new(client.clone(), _m(&acc, "max")), - median: SeriesPattern18::new(client.clone(), _m(&acc, "median")), - min: SeriesPattern18::new(client.clone(), _m(&acc, "min")), - pct10: SeriesPattern18::new(client.clone(), _m(&acc, "pct10")), - pct25: SeriesPattern18::new(client.clone(), _m(&acc, "pct25")), - pct75: SeriesPattern18::new(client.clone(), _m(&acc, "pct75")), - pct90: SeriesPattern18::new(client.clone(), _m(&acc, "pct90")), + average: SeriesPattern1::new(client.clone(), _m(&acc, "average")), + max: SeriesPattern1::new(client.clone(), _m(&acc, "max")), + median: SeriesPattern1::new(client.clone(), _m(&acc, "median")), + min: SeriesPattern1::new(client.clone(), _m(&acc, "min")), + pct10: SeriesPattern1::new(client.clone(), _m(&acc, "pct10")), + pct25: SeriesPattern1::new(client.clone(), _m(&acc, "pct25")), + pct75: SeriesPattern1::new(client.clone(), _m(&acc, "pct75")), + pct90: SeriesPattern1::new(client.clone(), _m(&acc, "pct90")), } } } @@ -3086,14 +3114,14 @@ impl SeriesTree_Blocks_Weight { /// Series tree node. pub struct SeriesTree_Blocks_Count { - pub target: SeriesPattern1, + pub target: _1m1w1y24hPattern, pub total: BaseCumulativeSumPattern2, } impl SeriesTree_Blocks_Count { pub fn new(client: Arc, base_path: String) -> Self { Self { - target: SeriesPattern1::new(client.clone(), "block_count_target".to_string()), + target: _1m1w1y24hPattern::new(client.clone(), "block_count_target".to_string()), total: BaseCumulativeSumPattern2::new(client.clone(), "block_count".to_string()), } } @@ -3198,17 +3226,17 @@ impl SeriesTree_Blocks_Lookback { /// Series tree node. pub struct SeriesTree_Blocks_Fullness { - pub bps: _1m1w1y24hBasePattern, - pub ratio: SeriesPattern1, - pub percent: SeriesPattern1, + pub bps: SeriesPattern18, + pub ratio: SeriesPattern18, + pub percent: SeriesPattern18, } impl SeriesTree_Blocks_Fullness { pub fn new(client: Arc, base_path: String) -> Self { Self { - bps: _1m1w1y24hBasePattern::new(client.clone(), "block_fullness_bps".to_string()), - ratio: SeriesPattern1::new(client.clone(), "block_fullness_ratio".to_string()), - percent: SeriesPattern1::new(client.clone(), "block_fullness".to_string()), + bps: SeriesPattern18::new(client.clone(), "block_fullness_bps".to_string()), + ratio: SeriesPattern18::new(client.clone(), "block_fullness_ratio".to_string()), + percent: SeriesPattern18::new(client.clone(), "block_fullness".to_string()), } } } @@ -3302,14 +3330,31 @@ impl SeriesTree_Transactions_Count { /// Series tree node. pub struct SeriesTree_Transactions_Size { pub vsize: _6bBlockTxPattern, - pub weight: _6bBlockTxPattern, + pub weight: SeriesTree_Transactions_Size_Weight, } impl SeriesTree_Transactions_Size { pub fn new(client: Arc, base_path: String) -> Self { Self { vsize: _6bBlockTxPattern::new(client.clone(), "tx_vsize".to_string()), - weight: _6bBlockTxPattern::new(client.clone(), "tx_weight".to_string()), + weight: SeriesTree_Transactions_Size_Weight::new(client.clone(), format!("{base_path}_weight")), + } + } +} + +/// Series tree node. +pub struct SeriesTree_Transactions_Size_Weight { + pub tx_index: SeriesPattern19, + pub block: AverageMaxMedianMinPct10Pct25Pct75Pct90Pattern2, + pub _6b: AverageMaxMedianMinPct10Pct25Pct75Pct90Pattern2, +} + +impl SeriesTree_Transactions_Size_Weight { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + tx_index: SeriesPattern19::new(client.clone(), "tx_weight".to_string()), + block: AverageMaxMedianMinPct10Pct25Pct75Pct90Pattern2::new(client.clone(), "tx_weight".to_string()), + _6b: AverageMaxMedianMinPct10Pct25Pct75Pct90Pattern2::new(client.clone(), "tx_weight_6b".to_string()), } } } @@ -3353,7 +3398,6 @@ impl SeriesTree_Transactions_Versions { /// Series tree node. pub struct SeriesTree_Transactions_Volume { pub transfer_volume: BaseCumulativeSumPattern4, - pub output_volume: BaseCumulativeSumPattern4, pub tx_per_sec: SeriesPattern1, pub outputs_per_sec: SeriesPattern1, pub inputs_per_sec: SeriesPattern1, @@ -3363,7 +3407,6 @@ impl SeriesTree_Transactions_Volume { pub fn new(client: Arc, base_path: String) -> Self { Self { transfer_volume: BaseCumulativeSumPattern4::new(client.clone(), "transfer_volume_bis".to_string()), - output_volume: BaseCumulativeSumPattern4::new(client.clone(), "output_volume".to_string()), tx_per_sec: SeriesPattern1::new(client.clone(), "tx_per_sec".to_string()), outputs_per_sec: SeriesPattern1::new(client.clone(), "outputs_per_sec".to_string()), inputs_per_sec: SeriesPattern1::new(client.clone(), "inputs_per_sec".to_string()), diff --git a/crates/brk_computer/src/blocks/count/import.rs b/crates/brk_computer/src/blocks/count/import.rs index abad7b32b..6208bdaa1 100644 --- a/crates/brk_computer/src/blocks/count/import.rs +++ b/crates/brk_computer/src/blocks/count/import.rs @@ -5,7 +5,10 @@ use vecdb::Database; use super::Vecs; use crate::{ indexes, - internal::{BlockCountTarget, CachedWindowStarts, PerBlockCumulativeWithSums, ConstantVecs}, + internal::{ + BlockCountTarget24h, BlockCountTarget1w, BlockCountTarget1m, BlockCountTarget1y, + CachedWindowStarts, PerBlockCumulativeWithSums, ConstantVecs, Windows, + }, }; impl Vecs { @@ -16,11 +19,12 @@ impl Vecs { cached_starts: &CachedWindowStarts, ) -> Result { Ok(Self { - target: ConstantVecs::new::( - "block_count_target", - version, - indexes, - ), + target: Windows { + _24h: ConstantVecs::new::("block_count_target_24h", version, indexes), + _1w: ConstantVecs::new::("block_count_target_1w", version, indexes), + _1m: ConstantVecs::new::("block_count_target_1m", version, indexes), + _1y: ConstantVecs::new::("block_count_target_1y", version, indexes), + }, total: PerBlockCumulativeWithSums::forced_import( db, "block_count", diff --git a/crates/brk_computer/src/blocks/count/vecs.rs b/crates/brk_computer/src/blocks/count/vecs.rs index 2736bc339..5ea13d6e4 100644 --- a/crates/brk_computer/src/blocks/count/vecs.rs +++ b/crates/brk_computer/src/blocks/count/vecs.rs @@ -2,10 +2,10 @@ use brk_traversable::Traversable; use brk_types::{StoredU32, StoredU64}; use vecdb::{Rw, StorageMode}; -use crate::internal::{PerBlockCumulativeWithSums, ConstantVecs}; +use crate::internal::{PerBlockCumulativeWithSums, ConstantVecs, Windows}; #[derive(Traversable)] pub struct Vecs { - pub target: ConstantVecs, + pub target: Windows>, pub total: PerBlockCumulativeWithSums, } diff --git a/crates/brk_computer/src/blocks/mod.rs b/crates/brk_computer/src/blocks/mod.rs index 1d32ed51e..fc7607043 100644 --- a/crates/brk_computer/src/blocks/mod.rs +++ b/crates/brk_computer/src/blocks/mod.rs @@ -26,20 +26,12 @@ pub const DB_NAME: &str = "blocks"; pub(crate) const TARGET_BLOCKS_PER_DAY_F64: f64 = 144.0; pub(crate) const TARGET_BLOCKS_PER_DAY_F32: f32 = 144.0; -pub(crate) const TARGET_BLOCKS_PER_MINUTE10: u64 = 1; -pub(crate) const TARGET_BLOCKS_PER_MINUTE30: u64 = 3; -pub(crate) const TARGET_BLOCKS_PER_HOUR1: u64 = 6; -pub(crate) const TARGET_BLOCKS_PER_HOUR4: u64 = 24; -pub(crate) const TARGET_BLOCKS_PER_HOUR12: u64 = 72; pub(crate) const TARGET_BLOCKS_PER_DAY: u64 = 144; -pub(crate) const TARGET_BLOCKS_PER_DAY3: u64 = 3 * TARGET_BLOCKS_PER_DAY; pub(crate) const TARGET_BLOCKS_PER_WEEK: u64 = 7 * TARGET_BLOCKS_PER_DAY; pub(crate) const TARGET_BLOCKS_PER_MONTH: u64 = 30 * TARGET_BLOCKS_PER_DAY; pub(crate) const TARGET_BLOCKS_PER_QUARTER: u64 = 3 * TARGET_BLOCKS_PER_MONTH; pub(crate) const TARGET_BLOCKS_PER_SEMESTER: u64 = 2 * TARGET_BLOCKS_PER_QUARTER; pub(crate) const TARGET_BLOCKS_PER_YEAR: u64 = 2 * TARGET_BLOCKS_PER_SEMESTER; -pub(crate) const TARGET_BLOCKS_PER_DECADE: u64 = 10 * TARGET_BLOCKS_PER_YEAR; -pub(crate) const TARGET_BLOCKS_PER_HALVING: u64 = 210_000; pub(crate) const ONE_TERA_HASH: f64 = 1_000_000_000_000.0; #[derive(Traversable)] diff --git a/crates/brk_computer/src/blocks/weight/compute.rs b/crates/brk_computer/src/blocks/weight/compute.rs index 125a2293c..b7b95cacd 100644 --- a/crates/brk_computer/src/blocks/weight/compute.rs +++ b/crates/brk_computer/src/blocks/weight/compute.rs @@ -12,16 +12,12 @@ impl Vecs { starting_indexes: &Indexes, exit: &Exit, ) -> Result<()> { - self.fullness - .compute(starting_indexes.height, exit, |vec| { - vec.compute_transform( - starting_indexes.height, - &indexer.vecs.blocks.weight, - |(h, weight, ..)| (h, BasisPoints16::from(weight.fullness())), - exit, - )?; - Ok(()) - })?; + self.fullness.bps.compute_transform( + starting_indexes.height, + &indexer.vecs.blocks.weight, + |(h, weight, ..)| (h, BasisPoints16::from(weight.fullness())), + exit, + )?; Ok(()) } diff --git a/crates/brk_computer/src/blocks/weight/import.rs b/crates/brk_computer/src/blocks/weight/import.rs index 59d925227..aa20fb203 100644 --- a/crates/brk_computer/src/blocks/weight/import.rs +++ b/crates/brk_computer/src/blocks/weight/import.rs @@ -6,7 +6,7 @@ use super::Vecs; use crate::{ blocks::SizeVecs, indexes, - internal::{CachedWindowStarts, LazyPerBlockRolling, PercentPerBlockRollingAverage, VBytesToWeight}, + internal::{CachedWindowStarts, LazyPerBlockRolling, PercentVec, VBytesToWeight}, }; impl Vecs { @@ -25,13 +25,7 @@ impl Vecs { indexes, ); - let fullness = PercentPerBlockRollingAverage::forced_import( - db, - "block_fullness", - version, - indexes, - cached_starts, - )?; + let fullness = PercentVec::forced_import(db, "block_fullness", version)?; Ok(Self { weight, fullness }) } diff --git a/crates/brk_computer/src/blocks/weight/vecs.rs b/crates/brk_computer/src/blocks/weight/vecs.rs index 3782fe558..4a962c601 100644 --- a/crates/brk_computer/src/blocks/weight/vecs.rs +++ b/crates/brk_computer/src/blocks/weight/vecs.rs @@ -2,10 +2,10 @@ use brk_traversable::Traversable; use brk_types::{BasisPoints16, StoredU64, Weight}; use vecdb::{Rw, StorageMode}; -use crate::internal::{LazyPerBlockRolling, PercentPerBlockRollingAverage}; +use crate::internal::{LazyPerBlockRolling, PercentVec}; #[derive(Traversable)] pub struct Vecs { pub weight: LazyPerBlockRolling, - pub fullness: PercentPerBlockRollingAverage, + pub fullness: PercentVec, } diff --git a/crates/brk_computer/src/distribution/compute/block_loop.rs b/crates/brk_computer/src/distribution/compute/block_loop.rs index c6e990528..90a70858d 100644 --- a/crates/brk_computer/src/distribution/compute/block_loop.rs +++ b/crates/brk_computer/src/distribution/compute/block_loop.rs @@ -67,8 +67,8 @@ pub(crate) fn process_blocks( let height_to_first_txout_index = &indexer.vecs.outputs.first_txout_index; let height_to_first_txin_index = &indexer.vecs.inputs.first_txin_index; let height_to_tx_count = &transactions.count.total.base.height; - let height_to_output_count = &outputs.count.total.full.sum; - let height_to_input_count = &inputs.count.full.sum; + let height_to_output_count = &outputs.count.total.full.sum.height; + let height_to_input_count = &inputs.count.full.sum.height; let tx_index_to_output_count = &indexes.tx_index.output_count; let tx_index_to_input_count = &indexes.tx_index.input_count; diff --git a/crates/brk_computer/src/indicators/gini.rs b/crates/brk_computer/src/indicators/gini.rs index 1a9428937..712810250 100644 --- a/crates/brk_computer/src/indicators/gini.rs +++ b/crates/brk_computer/src/indicators/gini.rs @@ -35,12 +35,14 @@ pub(super) fn compute( gini.bps .height .validate_computed_version_or_reset(source_version)?; - gini.bps.height.truncate_if_needed_at( - gini.bps - .height - .len() - .min(starting_indexes.height.to_usize()), - )?; + + let min_len = gini + .bps + .height + .len() + .min(starting_indexes.height.to_usize()); + + gini.bps.height.truncate_if_needed_at(min_len)?; let total_heights = supply_vecs .iter() diff --git a/crates/brk_computer/src/internal/aggregate/distribution.rs b/crates/brk_computer/src/internal/aggregate/distribution.rs deleted file mode 100644 index d9bfb9f0b..000000000 --- a/crates/brk_computer/src/internal/aggregate/distribution.rs +++ /dev/null @@ -1,124 +0,0 @@ -use brk_error::Result; -use brk_traversable::Traversable; -use schemars::JsonSchema; -use vecdb::{ - CheckedSub, Database, EagerVec, Exit, ImportableVec, PcoVec, ReadableVec, Ro, Rw, StorageMode, - StoredVec, VecIndex, VecValue, Version, -}; - -use crate::internal::{ - ComputedVecValue, DistributionStats, - algo::{compute_aggregations, compute_aggregations_nblock_window}, -}; - -#[derive(Traversable)] -pub struct Distribution { - pub average: M::Stored>>, - pub min: M::Stored>>, - pub max: M::Stored>>, - pub pct10: M::Stored>>, - pub pct25: M::Stored>>, - pub median: M::Stored>>, - pub pct75: M::Stored>>, - pub pct90: M::Stored>>, -} - -impl Distribution { - pub(crate) fn forced_import(db: &Database, name: &str, version: Version) -> Result { - let s = DistributionStats::<()>::SUFFIXES; - Ok(Self { - average: EagerVec::forced_import(db, &format!("{name}_{}", s[0]), version)?, - min: EagerVec::forced_import(db, &format!("{name}_{}", s[1]), version)?, - max: EagerVec::forced_import(db, &format!("{name}_{}", s[2]), version)?, - pct10: EagerVec::forced_import(db, &format!("{name}_{}", s[3]), version)?, - pct25: EagerVec::forced_import(db, &format!("{name}_{}", s[4]), version)?, - median: EagerVec::forced_import(db, &format!("{name}_{}", s[5]), version)?, - pct75: EagerVec::forced_import(db, &format!("{name}_{}", s[6]), version)?, - pct90: EagerVec::forced_import(db, &format!("{name}_{}", s[7]), version)?, - }) - } - - /// Compute distribution stats, skipping first N items from all calculations. - /// - /// Use `skip_count: 1` to exclude coinbase transactions from fee/feerate stats. - pub(crate) fn compute_with_skip( - &mut self, - max_from: I, - source: &impl ReadableVec, - first_indexes: &impl ReadableVec, - count_indexes: &impl ReadableVec, - exit: &Exit, - skip_count: usize, - ) -> Result<()> - where - A: VecIndex + VecValue + brk_types::CheckedSub, - { - compute_aggregations( - max_from, - source, - first_indexes, - count_indexes, - exit, - skip_count, - None, // first - None, // last - Some(&mut self.min), - Some(&mut self.max), - Some(&mut self.average), - None, // sum - None, // cumulative - Some(&mut self.median), - Some(&mut self.pct10), - Some(&mut self.pct25), - Some(&mut self.pct75), - Some(&mut self.pct90), - ) - } - - /// Compute distribution stats from a fixed n-block rolling window. - /// - /// For each index `i`, aggregates all source items from blocks `max(0, i - n_blocks + 1)..=i`. - pub(crate) fn compute_from_nblocks( - &mut self, - max_from: I, - source: &(impl ReadableVec + Sized), - first_indexes: &impl ReadableVec, - count_indexes: &impl ReadableVec, - n_blocks: usize, - exit: &Exit, - ) -> Result<()> - where - T: CheckedSub, - A: VecIndex + VecValue + brk_types::CheckedSub, - { - compute_aggregations_nblock_window( - max_from, - source, - first_indexes, - count_indexes, - n_blocks, - exit, - &mut self.min, - &mut self.max, - &mut self.average, - &mut self.median, - &mut self.pct10, - &mut self.pct25, - &mut self.pct75, - &mut self.pct90, - ) - } - - pub fn read_only_clone(&self) -> Distribution { - Distribution { - average: StoredVec::read_only_clone(&self.average), - min: StoredVec::read_only_clone(&self.min), - max: StoredVec::read_only_clone(&self.max), - pct10: StoredVec::read_only_clone(&self.pct10), - pct25: StoredVec::read_only_clone(&self.pct25), - median: StoredVec::read_only_clone(&self.median), - pct75: StoredVec::read_only_clone(&self.pct75), - pct90: StoredVec::read_only_clone(&self.pct90), - } - } -} diff --git a/crates/brk_computer/src/internal/aggregate/distribution_full.rs b/crates/brk_computer/src/internal/aggregate/distribution_full.rs deleted file mode 100644 index a385e7e21..000000000 --- a/crates/brk_computer/src/internal/aggregate/distribution_full.rs +++ /dev/null @@ -1,75 +0,0 @@ -use brk_error::Result; -use brk_traversable::Traversable; -use schemars::JsonSchema; -use vecdb::{ - Database, EagerVec, Exit, ImportableVec, PcoVec, ReadableVec, Ro, Rw, StorageMode, StoredVec, - VecIndex, VecValue, Version, -}; - -use crate::internal::{ComputedVecValue, algo::compute_aggregations}; - -use super::Distribution; - -/// Full stats aggregate: sum + cumulative + distribution -#[derive(Traversable)] -pub struct DistributionFull { - pub sum: M::Stored>>, - pub cumulative: M::Stored>>, - #[traversable(flatten)] - pub distribution: Distribution, -} - -impl DistributionFull { - pub(crate) fn forced_import(db: &Database, name: &str, version: Version) -> Result { - Ok(Self { - distribution: Distribution::forced_import(db, name, version)?, - sum: EagerVec::forced_import(db, &format!("{name}_sum"), version)?, - cumulative: EagerVec::forced_import(db, &format!("{name}_cumulative"), version)?, - }) - } - - /// Compute all stats, skipping first N items from all calculations. - /// - /// Use `skip_count: 1` to exclude coinbase transactions from fee/feerate stats. - pub(crate) fn compute_with_skip( - &mut self, - max_from: I, - source: &impl ReadableVec, - first_indexes: &impl ReadableVec, - count_indexes: &impl ReadableVec, - exit: &Exit, - skip_count: usize, - ) -> Result<()> - where - A: VecIndex + VecValue + brk_types::CheckedSub, - { - compute_aggregations( - max_from, - source, - first_indexes, - count_indexes, - exit, - skip_count, - None, // first - None, // last - Some(&mut self.distribution.min), - Some(&mut self.distribution.max), - Some(&mut self.distribution.average), - Some(&mut self.sum), - Some(&mut self.cumulative), - Some(&mut self.distribution.median), - Some(&mut self.distribution.pct10), - Some(&mut self.distribution.pct25), - Some(&mut self.distribution.pct75), - Some(&mut self.distribution.pct90), - ) - } - - pub fn read_only_clone(&self) -> DistributionFull { - DistributionFull { - distribution: self.distribution.read_only_clone(), - sum: StoredVec::read_only_clone(&self.sum), - cumulative: StoredVec::read_only_clone(&self.cumulative), - } - } -} diff --git a/crates/brk_computer/src/internal/aggregate/mod.rs b/crates/brk_computer/src/internal/aggregate/mod.rs deleted file mode 100644 index c51f59bb7..000000000 --- a/crates/brk_computer/src/internal/aggregate/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod distribution; -mod distribution_full; -mod lazy_distribution; - -pub use distribution::*; -pub use distribution_full::*; -pub use lazy_distribution::*; diff --git a/crates/brk_computer/src/internal/containers/mod.rs b/crates/brk_computer/src/internal/containers/mod.rs index 89cb6a76a..6da8e451b 100644 --- a/crates/brk_computer/src/internal/containers/mod.rs +++ b/crates/brk_computer/src/internal/containers/mod.rs @@ -4,10 +4,12 @@ mod per_resolution; mod window_24h; mod windows; mod windows_from_1w; +mod percent; mod windows_to_1m; pub use constant::*; pub use distribution_stats::*; +pub use percent::*; pub use per_resolution::*; pub use window_24h::*; pub use windows::*; diff --git a/crates/brk_computer/src/internal/containers/percent.rs b/crates/brk_computer/src/internal/containers/percent.rs new file mode 100644 index 000000000..47b64e826 --- /dev/null +++ b/crates/brk_computer/src/internal/containers/percent.rs @@ -0,0 +1,8 @@ +use brk_traversable::Traversable; + +#[derive(Clone, Traversable)] +pub struct Percent { + pub bps: A, + pub ratio: B, + pub percent: C, +} diff --git a/crates/brk_computer/src/internal/mod.rs b/crates/brk_computer/src/internal/mod.rs index 16d13b1fc..e667d1af0 100644 --- a/crates/brk_computer/src/internal/mod.rs +++ b/crates/brk_computer/src/internal/mod.rs @@ -1,4 +1,3 @@ -mod aggregate; pub(crate) mod algo; mod amount; mod containers; @@ -9,7 +8,6 @@ mod indexes; mod traits; mod transform; -pub(crate) use aggregate::*; pub(crate) use amount::*; pub(crate) use containers::*; pub(crate) use per_block::*; diff --git a/crates/brk_computer/src/internal/per_block/computed/aggregated.rs b/crates/brk_computer/src/internal/per_block/computed/aggregated.rs index 91de22941..5a1c32f56 100644 --- a/crates/brk_computer/src/internal/per_block/computed/aggregated.rs +++ b/crates/brk_computer/src/internal/per_block/computed/aggregated.rs @@ -1,4 +1,4 @@ -//! PerBlockAggregated - DistributionFull (distribution + sum + cumulative) + RollingComplete. +//! PerBlockAggregated - PerBlockDistributionFull (distribution + sum + cumulative) + RollingComplete. //! //! For metrics aggregated per-block from finer-grained sources (e.g., per-tx data), //! where we want full per-block stats plus rolling window stats. @@ -11,7 +11,7 @@ use vecdb::{Database, Exit, Rw, StorageMode}; use crate::{ indexes, - internal::{CachedWindowStarts, DistributionFull, NumericValue, RollingComplete, WindowStarts}, + internal::{CachedWindowStarts, PerBlockDistributionFull, NumericValue, RollingComplete, WindowStarts}, }; #[derive(Traversable)] @@ -20,7 +20,7 @@ where T: NumericValue + JsonSchema, { #[traversable(flatten)] - pub full: DistributionFull, + pub full: PerBlockDistributionFull, pub rolling: RollingComplete, } @@ -35,26 +35,26 @@ where indexes: &indexes::Vecs, cached_starts: &CachedWindowStarts, ) -> Result { - let full = DistributionFull::forced_import(db, name, version)?; + let full = PerBlockDistributionFull::forced_import(db, name, version, indexes)?; let rolling = RollingComplete::forced_import( db, name, version, indexes, - &full.cumulative, + &full.cumulative.height, cached_starts, )?; Ok(Self { full, rolling }) } - /// Compute DistributionFull stats via closure, then rolling distribution from the per-block sum. + /// Compute PerBlockDistributionFull stats via closure, then rolling distribution from the per-block sum. pub(crate) fn compute( &mut self, max_from: Height, windows: &WindowStarts<'_>, exit: &Exit, - compute_full: impl FnOnce(&mut DistributionFull) -> Result<()>, + compute_full: impl FnOnce(&mut PerBlockDistributionFull) -> Result<()>, ) -> Result<()> where T: From + Default + Copy + Ord, @@ -62,7 +62,7 @@ where { compute_full(&mut self.full)?; self.rolling - .compute(max_from, windows, &self.full.sum, exit)?; + .compute(max_from, windows, &self.full.sum.height, exit)?; Ok(()) } } diff --git a/crates/brk_computer/src/internal/per_block/computed/distribution.rs b/crates/brk_computer/src/internal/per_block/computed/distribution.rs new file mode 100644 index 000000000..42f03e461 --- /dev/null +++ b/crates/brk_computer/src/internal/per_block/computed/distribution.rs @@ -0,0 +1,103 @@ +use brk_error::Result; +use brk_traversable::Traversable; +use brk_types::Height; +use derive_more::{Deref, DerefMut}; +use schemars::JsonSchema; +use vecdb::{ + CheckedSub, Database, Exit, ReadableVec, Rw, StorageMode, + VecIndex, VecValue, Version, +}; + +use crate::{ + indexes, + internal::{ + ComputedVecValue, DistributionStats, NumericValue, PerBlock, + algo::{compute_aggregations, compute_aggregations_nblock_window}, + }, +}; + +#[derive(Deref, DerefMut, Traversable)] +#[traversable(transparent)] +pub struct PerBlockDistribution( + pub DistributionStats>, +); + +impl PerBlockDistribution { + pub(crate) fn forced_import( + db: &Database, + name: &str, + version: Version, + indexes: &indexes::Vecs, + ) -> Result { + Ok(Self(DistributionStats::try_from_fn(|suffix| { + PerBlock::forced_import(db, &format!("{name}_{suffix}"), version, indexes) + })?)) + } + + pub(crate) fn compute_with_skip( + &mut self, + max_from: Height, + source: &impl ReadableVec, + first_indexes: &impl ReadableVec, + count_indexes: &impl ReadableVec, + exit: &Exit, + skip_count: usize, + ) -> Result<()> + where + A: VecIndex + VecValue + brk_types::CheckedSub, + { + let s = &mut self.0; + compute_aggregations( + max_from, + source, + first_indexes, + count_indexes, + exit, + skip_count, + None, + None, + Some(&mut s.min.height), + Some(&mut s.max.height), + Some(&mut s.average.height), + None, + None, + Some(&mut s.median.height), + Some(&mut s.pct10.height), + Some(&mut s.pct25.height), + Some(&mut s.pct75.height), + Some(&mut s.pct90.height), + ) + } + + pub(crate) fn compute_from_nblocks( + &mut self, + max_from: Height, + source: &(impl ReadableVec + Sized), + first_indexes: &impl ReadableVec, + count_indexes: &impl ReadableVec, + n_blocks: usize, + exit: &Exit, + ) -> Result<()> + where + T: CheckedSub, + A: VecIndex + VecValue + brk_types::CheckedSub, + { + let s = &mut self.0; + compute_aggregations_nblock_window( + max_from, + source, + first_indexes, + count_indexes, + n_blocks, + exit, + &mut s.min.height, + &mut s.max.height, + &mut s.average.height, + &mut s.median.height, + &mut s.pct10.height, + &mut s.pct25.height, + &mut s.pct75.height, + &mut s.pct90.height, + ) + } +} diff --git a/crates/brk_computer/src/internal/per_block/computed/distribution_full.rs b/crates/brk_computer/src/internal/per_block/computed/distribution_full.rs new file mode 100644 index 000000000..0f23151a1 --- /dev/null +++ b/crates/brk_computer/src/internal/per_block/computed/distribution_full.rs @@ -0,0 +1,73 @@ +use brk_error::Result; +use brk_traversable::Traversable; +use brk_types::Height; +use schemars::JsonSchema; +use vecdb::{ + Database, Exit, ReadableVec, Rw, StorageMode, + VecIndex, VecValue, Version, +}; + +use crate::{ + indexes, + internal::{ComputedVecValue, NumericValue, PerBlock, algo::compute_aggregations}, +}; + +use super::PerBlockDistribution; + +#[derive(Traversable)] +pub struct PerBlockDistributionFull { + pub sum: PerBlock, + pub cumulative: PerBlock, + #[traversable(flatten)] + pub distribution: PerBlockDistribution, +} + +impl PerBlockDistributionFull { + pub(crate) fn forced_import( + db: &Database, + name: &str, + version: Version, + indexes: &indexes::Vecs, + ) -> Result { + Ok(Self { + distribution: PerBlockDistribution::forced_import(db, name, version, indexes)?, + sum: PerBlock::forced_import(db, &format!("{name}_sum"), version, indexes)?, + cumulative: PerBlock::forced_import(db, &format!("{name}_cumulative"), version, indexes)?, + }) + } + + pub(crate) fn compute_with_skip( + &mut self, + max_from: Height, + source: &impl ReadableVec, + first_indexes: &impl ReadableVec, + count_indexes: &impl ReadableVec, + exit: &Exit, + skip_count: usize, + ) -> Result<()> + where + A: VecIndex + VecValue + brk_types::CheckedSub, + { + let d = &mut self.distribution.0; + compute_aggregations( + max_from, + source, + first_indexes, + count_indexes, + exit, + skip_count, + None, + None, + Some(&mut d.min.height), + Some(&mut d.max.height), + Some(&mut d.average.height), + Some(&mut self.sum.height), + Some(&mut self.cumulative.height), + Some(&mut d.median.height), + Some(&mut d.pct10.height), + Some(&mut d.pct25.height), + Some(&mut d.pct75.height), + Some(&mut d.pct90.height), + ) + } +} diff --git a/crates/brk_computer/src/internal/aggregate/lazy_distribution.rs b/crates/brk_computer/src/internal/per_block/computed/lazy_distribution.rs similarity index 73% rename from crates/brk_computer/src/internal/aggregate/lazy_distribution.rs rename to crates/brk_computer/src/internal/per_block/computed/lazy_distribution.rs index 0b40174a0..c80de5e49 100644 --- a/crates/brk_computer/src/internal/aggregate/lazy_distribution.rs +++ b/crates/brk_computer/src/internal/per_block/computed/lazy_distribution.rs @@ -3,10 +3,10 @@ use brk_types::{Height, Version}; use schemars::JsonSchema; use vecdb::{LazyVecFrom1, ReadableCloneableVec, UnaryTransform, VecIndex}; -use crate::internal::{ComputedVecValue, Distribution, DistributionStats}; +use crate::internal::{ComputedVecValue, PerBlockDistribution, DistributionStats}; -/// Lazy analog of `Distribution`: 8 `LazyVecFrom1` fields, -/// each derived by transforming the corresponding field of a source `Distribution`. +/// Lazy analog of `Distribution`: 8 `LazyVecFrom1` fields, +/// each derived by transforming the corresponding field of a source `PerBlockDistribution`. #[derive(Clone, Traversable)] pub struct LazyDistribution where @@ -27,54 +27,54 @@ where impl LazyDistribution where T: ComputedVecValue + JsonSchema + 'static, - S1T: ComputedVecValue + JsonSchema, + S1T: ComputedVecValue + PartialOrd + JsonSchema, { pub(crate) fn from_distribution>( name: &str, version: Version, - source: &Distribution, + source: &PerBlockDistribution, ) -> Self { let s = DistributionStats::<()>::SUFFIXES; Self { average: LazyVecFrom1::transformed::( &format!("{name}_{}", s[0]), version, - source.average.read_only_boxed_clone(), + source.average.height.read_only_boxed_clone(), ), min: LazyVecFrom1::transformed::( &format!("{name}_{}", s[1]), version, - source.min.read_only_boxed_clone(), + source.min.height.read_only_boxed_clone(), ), max: LazyVecFrom1::transformed::( &format!("{name}_{}", s[2]), version, - source.max.read_only_boxed_clone(), + source.max.height.read_only_boxed_clone(), ), pct10: LazyVecFrom1::transformed::( &format!("{name}_{}", s[3]), version, - source.pct10.read_only_boxed_clone(), + source.pct10.height.read_only_boxed_clone(), ), pct25: LazyVecFrom1::transformed::( &format!("{name}_{}", s[4]), version, - source.pct25.read_only_boxed_clone(), + source.pct25.height.read_only_boxed_clone(), ), median: LazyVecFrom1::transformed::( &format!("{name}_{}", s[5]), version, - source.median.read_only_boxed_clone(), + source.median.height.read_only_boxed_clone(), ), pct75: LazyVecFrom1::transformed::( &format!("{name}_{}", s[6]), version, - source.pct75.read_only_boxed_clone(), + source.pct75.height.read_only_boxed_clone(), ), pct90: LazyVecFrom1::transformed::( &format!("{name}_{}", s[7]), version, - source.pct90.read_only_boxed_clone(), + source.pct90.height.read_only_boxed_clone(), ), } } diff --git a/crates/brk_computer/src/internal/per_block/computed/mod.rs b/crates/brk_computer/src/internal/per_block/computed/mod.rs index 4ee0b7ccc..a9c486a2b 100644 --- a/crates/brk_computer/src/internal/per_block/computed/mod.rs +++ b/crates/brk_computer/src/internal/per_block/computed/mod.rs @@ -2,10 +2,13 @@ mod aggregated; mod base; mod cumulative; mod cumulative_sum; +mod distribution; +mod distribution_full; +mod full; +mod lazy_distribution; +mod lazy_rolling; mod resolutions; mod rolling; -mod lazy_rolling; -mod full; mod rolling_average; mod with_deltas; @@ -13,9 +16,12 @@ pub use aggregated::*; pub use base::*; pub use cumulative::*; pub use cumulative_sum::*; +pub use distribution::*; +pub use distribution_full::*; +pub use full::*; +pub use lazy_distribution::*; +pub use lazy_rolling::*; pub use resolutions::*; pub use rolling::*; -pub use lazy_rolling::*; -pub use full::*; pub use rolling_average::*; pub use with_deltas::*; diff --git a/crates/brk_computer/src/internal/per_block/lazy/base.rs b/crates/brk_computer/src/internal/per_block/lazy/base.rs index b26e96618..72a8618c9 100644 --- a/crates/brk_computer/src/internal/per_block/lazy/base.rs +++ b/crates/brk_computer/src/internal/per_block/lazy/base.rs @@ -2,11 +2,11 @@ use brk_traversable::Traversable; use brk_types::{Height, Version}; use derive_more::{Deref, DerefMut}; use schemars::JsonSchema; -use vecdb::{LazyVecFrom1, ReadableBoxedVec, ReadableCloneableVec, UnaryTransform}; +use vecdb::{LazyVecFrom1, ReadOnlyClone, ReadableBoxedVec, ReadableCloneableVec, UnaryTransform}; use crate::{ indexes, - internal::{PerBlock, ComputedVecValue, DerivedResolutions, NumericValue}, + internal::{ComputedVecValue, DerivedResolutions, NumericValue, PerBlock}, }; #[derive(Clone, Deref, DerefMut, Traversable)] #[traversable(merge)] @@ -38,7 +38,9 @@ where { Self { height: LazyVecFrom1::transformed::(name, version, height_source), - resolutions: Box::new(DerivedResolutions::from_computed::(name, version, source)), + resolutions: Box::new(DerivedResolutions::from_computed::( + name, version, source, + )), } } @@ -86,3 +88,14 @@ where } } } + +impl ReadOnlyClone for LazyPerBlock +where + T: ComputedVecValue + PartialOrd + JsonSchema, + S1T: ComputedVecValue, +{ + type ReadOnly = Self; + fn read_only_clone(&self) -> Self { + self.clone() + } +} diff --git a/crates/brk_computer/src/internal/per_block/percent/base.rs b/crates/brk_computer/src/internal/per_block/percent/base.rs index 72ea6f7bd..2a5f58bdd 100644 --- a/crates/brk_computer/src/internal/per_block/percent/base.rs +++ b/crates/brk_computer/src/internal/per_block/percent/base.rs @@ -1,16 +1,17 @@ use brk_error::Result; use brk_traversable::Traversable; use brk_types::{Height, StoredF32, Version}; +use derive_more::{Deref, DerefMut}; use vecdb::{ BinaryTransform, Database, Exit, ReadableCloneableVec, ReadableVec, Rw, StorageMode, VecValue, }; use crate::{ indexes, - internal::{BpsType, algo::ComputeDrawdown}, + internal::{BpsType, Percent, algo::ComputeDrawdown}, }; -use crate::internal::{PerBlock, LazyPerBlock}; +use crate::internal::{LazyPerBlock, PerBlock}; /// Basis-point storage with both ratio and percentage float views. /// @@ -18,12 +19,11 @@ use crate::internal::{PerBlock, LazyPerBlock}; /// exposes two lazy StoredF32 views: /// - `ratio`: bps / 10000 (e.g., 4523 bps -> 0.4523) /// - `percent`: bps / 100 (e.g., 4523 bps -> 45.23%) -#[derive(Traversable)] -pub struct PercentPerBlock { - pub bps: PerBlock, - pub ratio: LazyPerBlock, - pub percent: LazyPerBlock, -} +#[derive(Deref, DerefMut, Traversable)] +#[traversable(transparent)] +pub struct PercentPerBlock( + pub Percent, LazyPerBlock>, +); impl PercentPerBlock { pub(crate) fn forced_import( @@ -44,11 +44,11 @@ impl PercentPerBlock { let percent = LazyPerBlock::from_computed::(name, version, bps_clone, &bps); - Ok(Self { + Ok(Self(Percent { bps, ratio, percent, - }) + })) } pub(crate) fn compute_binary( diff --git a/crates/brk_computer/src/internal/per_block/percent/lazy.rs b/crates/brk_computer/src/internal/per_block/percent/lazy.rs index fae815648..a37fb3a6d 100644 --- a/crates/brk_computer/src/internal/per_block/percent/lazy.rs +++ b/crates/brk_computer/src/internal/per_block/percent/lazy.rs @@ -1,19 +1,19 @@ use brk_traversable::Traversable; use brk_types::{StoredF32, Version}; +use derive_more::{Deref, DerefMut}; use vecdb::{ReadableCloneableVec, UnaryTransform}; -use crate::internal::{BpsType, LazyPerBlock, PercentPerBlock}; +use crate::internal::{BpsType, LazyPerBlock, Percent, PercentPerBlock}; /// Fully lazy variant of `PercentPerBlock` — no stored vecs. /// /// BPS values are lazily derived from a source `PercentPerBlock` via a unary transform, /// and ratio/percent float views are chained from the lazy BPS. -#[derive(Clone, Traversable)] -pub struct LazyPercentPerBlock { - pub bps: LazyPerBlock, - pub ratio: LazyPerBlock, - pub percent: LazyPerBlock, -} +#[derive(Clone, Deref, DerefMut, Traversable)] +#[traversable(transparent)] +pub struct LazyPercentPerBlock( + pub Percent, LazyPerBlock>, +); impl LazyPercentPerBlock { /// Create from a stored `PercentPerBlock` source via a BPS-to-BPS unary transform. @@ -37,10 +37,10 @@ impl LazyPercentPerBlock { let percent = LazyPerBlock::from_lazy::(name, version, &bps); - Self { + Self(Percent { bps, ratio, percent, - } + }) } } diff --git a/crates/brk_computer/src/internal/per_block/percent/mod.rs b/crates/brk_computer/src/internal/per_block/percent/mod.rs index 32f1fe257..79a0d7c9d 100644 --- a/crates/brk_computer/src/internal/per_block/percent/mod.rs +++ b/crates/brk_computer/src/internal/per_block/percent/mod.rs @@ -1,11 +1,11 @@ mod base; mod lazy; mod lazy_windows; -mod rolling_average; +mod vec; mod windows; pub use base::*; pub use lazy::*; pub use lazy_windows::*; -pub use rolling_average::*; +pub use vec::*; pub use windows::*; diff --git a/crates/brk_computer/src/internal/per_block/percent/rolling_average.rs b/crates/brk_computer/src/internal/per_block/percent/rolling_average.rs deleted file mode 100644 index c1eff7670..000000000 --- a/crates/brk_computer/src/internal/per_block/percent/rolling_average.rs +++ /dev/null @@ -1,66 +0,0 @@ -use brk_error::Result; -use brk_traversable::Traversable; -use brk_types::{Height, StoredF32, Version}; -use vecdb::{Database, EagerVec, Exit, PcoVec, ReadableCloneableVec, Rw, StorageMode}; - -use crate::{ - indexes, - internal::{BpsType, CachedWindowStarts}, -}; - -use crate::internal::{PerBlockRollingAverage, LazyPerBlock}; - -/// Like PercentPerBlock but with rolling average stats on the bps data. -#[derive(Traversable)] -pub struct PercentPerBlockRollingAverage { - pub bps: PerBlockRollingAverage, - pub ratio: LazyPerBlock, - pub percent: LazyPerBlock, -} - -impl PercentPerBlockRollingAverage { - pub(crate) fn forced_import( - db: &Database, - name: &str, - version: Version, - indexes: &indexes::Vecs, - cached_starts: &CachedWindowStarts, - ) -> Result { - let bps = PerBlockRollingAverage::forced_import( - db, - &format!("{name}_bps"), - version, - indexes, - cached_starts, - )?; - - let ratio = LazyPerBlock::from_height_source::( - &format!("{name}_ratio"), - version, - bps.base.read_only_boxed_clone(), - indexes, - ); - - let percent = LazyPerBlock::from_height_source::( - name, - version, - bps.base.read_only_boxed_clone(), - indexes, - ); - - Ok(Self { - bps, - ratio, - percent, - }) - } - - pub(crate) fn compute( - &mut self, - max_from: Height, - exit: &Exit, - compute_height: impl FnOnce(&mut EagerVec>) -> Result<()>, - ) -> Result<()> { - self.bps.compute(max_from, exit, compute_height) - } -} diff --git a/crates/brk_computer/src/internal/per_block/percent/vec.rs b/crates/brk_computer/src/internal/per_block/percent/vec.rs new file mode 100644 index 000000000..2aa190f40 --- /dev/null +++ b/crates/brk_computer/src/internal/per_block/percent/vec.rs @@ -0,0 +1,43 @@ +use brk_traversable::Traversable; +use brk_types::{Height, StoredF32, Version}; +use derive_more::{Deref, DerefMut}; +use vecdb::{ + Database, EagerVec, ImportableVec, LazyVecFrom1, PcoVec, ReadableCloneableVec, Rw, StorageMode, +}; + +use crate::internal::{BpsType, Percent}; + +/// Lightweight percent container: BPS height vec + lazy ratio + lazy percent. +/// No resolutions, no rolling stats. +#[derive(Clone, Deref, DerefMut, Traversable)] +#[traversable(transparent)] +#[allow(clippy::type_complexity)] +pub struct PercentVec( + pub Percent>>, LazyVecFrom1>, +); + +impl PercentVec { + pub(crate) fn forced_import( + db: &Database, + name: &str, + version: Version, + ) -> brk_error::Result { + let bps: EagerVec> = + EagerVec::forced_import(db, &format!("{name}_bps"), version)?; + let bps_clone = bps.read_only_boxed_clone(); + + let ratio = LazyVecFrom1::transformed::( + &format!("{name}_ratio"), + version, + bps_clone.clone(), + ); + + let percent = LazyVecFrom1::transformed::(name, version, bps_clone); + + Ok(Self(Percent { + bps, + ratio, + percent, + })) + } +} diff --git a/crates/brk_computer/src/internal/per_tx/derived.rs b/crates/brk_computer/src/internal/per_tx/derived.rs index a3484bfb5..3546cbe3c 100644 --- a/crates/brk_computer/src/internal/per_tx/derived.rs +++ b/crates/brk_computer/src/internal/per_tx/derived.rs @@ -1,37 +1,36 @@ -//! TxDerivedDistribution - per-block + rolling window distribution stats from tx-level data. -//! -//! Computes true distribution stats (average, min, max, median, percentiles) by reading -//! actual tx values for each scope: current block, last 6 blocks. - use brk_error::Result; use brk_indexer::Indexer; use brk_traversable::Traversable; -use brk_types::{Height, Indexes, TxIndex}; +use brk_types::{Indexes, TxIndex}; use schemars::JsonSchema; use vecdb::{Database, Exit, ReadableVec, Rw, StorageMode, Version}; use crate::{ indexes, - internal::{ComputedVecValue, Distribution, NumericValue}, + internal::{ComputedVecValue, NumericValue, PerBlockDistribution}, }; -/// 6-block rolling window distribution with 8 distribution stat vecs. #[derive(Traversable)] pub struct BlockRollingDistribution where T: ComputedVecValue + PartialOrd + JsonSchema, { - pub _6b: Distribution, + pub _6b: PerBlockDistribution, } impl BlockRollingDistribution where T: NumericValue + JsonSchema, { - pub(crate) fn forced_import(db: &Database, name: &str, version: Version) -> Result { + pub(crate) fn forced_import( + db: &Database, + name: &str, + version: Version, + indexes: &indexes::Vecs, + ) -> Result { Ok(Self { - _6b: Distribution::forced_import(db, &format!("{name}_6b"), version)?, + _6b: PerBlockDistribution::forced_import(db, &format!("{name}_6b"), version, indexes)?, }) } } @@ -41,20 +40,28 @@ pub struct TxDerivedDistribution where T: ComputedVecValue + PartialOrd + JsonSchema, { - pub block: Distribution, + pub block: PerBlockDistribution, #[traversable(flatten)] - pub rolling: BlockRollingDistribution, + pub distribution: BlockRollingDistribution, } impl TxDerivedDistribution where T: NumericValue + JsonSchema, { - pub(crate) fn forced_import(db: &Database, name: &str, version: Version) -> Result { - let block = Distribution::forced_import(db, name, version)?; - let rolling = BlockRollingDistribution::forced_import(db, name, version)?; + pub(crate) fn forced_import( + db: &Database, + name: &str, + version: Version, + indexes: &indexes::Vecs, + ) -> Result { + let block = PerBlockDistribution::forced_import(db, name, version, indexes)?; + let distribution = BlockRollingDistribution::forced_import(db, name, version, indexes)?; - Ok(Self { block, rolling }) + Ok(Self { + block, + distribution, + }) } pub(crate) fn derive_from( @@ -72,10 +79,6 @@ where self.derive_from_with_skip(indexer, indexes, starting_indexes, tx_index_source, exit, 0) } - /// Derive from source, skipping first N transactions per block from per-block stats. - /// - /// Use `skip_count: 1` to exclude coinbase transactions from fee/feerate stats. - /// Rolling window distributions do NOT skip (negligible impact over many blocks). #[allow(clippy::too_many_arguments)] pub(crate) fn derive_from_with_skip( &mut self, @@ -90,7 +93,6 @@ where T: Copy + Ord + From + Default, f64: From, { - // Per-block distribution (supports skip for coinbase exclusion) self.block.compute_with_skip( starting_indexes.height, tx_index_source, @@ -100,8 +102,7 @@ where skip_count, )?; - // 6-block rolling: true distribution from all txs in last 6 blocks - self.rolling._6b.compute_from_nblocks( + self.distribution._6b.compute_from_nblocks( starting_indexes.height, tx_index_source, &indexer.vecs.transactions.first_tx_index, diff --git a/crates/brk_computer/src/internal/per_tx/distribution.rs b/crates/brk_computer/src/internal/per_tx/distribution.rs index 0ed3fc791..a003c52d5 100644 --- a/crates/brk_computer/src/internal/per_tx/distribution.rs +++ b/crates/brk_computer/src/internal/per_tx/distribution.rs @@ -29,9 +29,9 @@ impl PerTxDistribution where T: NumericValue + JsonSchema, { - pub(crate) fn forced_import(db: &Database, name: &str, version: Version) -> Result { + pub(crate) fn forced_import(db: &Database, name: &str, version: Version, indexes: &indexes::Vecs) -> Result { let tx_index = EagerVec::forced_import(db, name, version)?; - let distribution = TxDerivedDistribution::forced_import(db, name, version)?; + let distribution = TxDerivedDistribution::forced_import(db, name, version, indexes)?; Ok(Self { tx_index, distribution, diff --git a/crates/brk_computer/src/internal/per_tx/lazy_derived.rs b/crates/brk_computer/src/internal/per_tx/lazy_derived.rs index ea4196411..21de69edc 100644 --- a/crates/brk_computer/src/internal/per_tx/lazy_derived.rs +++ b/crates/brk_computer/src/internal/per_tx/lazy_derived.rs @@ -14,8 +14,6 @@ where pub _6b: LazyDistribution, } -/// Lazy analog of `TxDerivedDistribution`: per-block + 6-block rolling, -/// each derived by transforming the corresponding source distribution. #[derive(Clone, Traversable)] pub struct LazyTxDerivedDistribution where @@ -24,13 +22,13 @@ where { pub block: LazyDistribution, #[traversable(flatten)] - pub rolling: LazyBlockRollingDistribution, + pub distribution: LazyBlockRollingDistribution, } impl LazyTxDerivedDistribution where T: ComputedVecValue + JsonSchema + 'static, - S1T: ComputedVecValue + JsonSchema, + S1T: ComputedVecValue + PartialOrd + JsonSchema, { pub(crate) fn from_tx_derived>( name: &str, @@ -38,13 +36,16 @@ where source: &TxDerivedDistribution, ) -> Self { let block = LazyDistribution::from_distribution::(name, version, &source.block); - let rolling = LazyBlockRollingDistribution { + let distribution = LazyBlockRollingDistribution { _6b: LazyDistribution::from_distribution::( &format!("{name}_6b"), version, - &source.rolling._6b, + &source.distribution._6b, ), }; - Self { block, rolling } + Self { + block, + distribution, + } } } diff --git a/crates/brk_computer/src/internal/per_tx/lazy_distribution.rs b/crates/brk_computer/src/internal/per_tx/lazy_distribution.rs index 8214a89ac..fa724170c 100644 --- a/crates/brk_computer/src/internal/per_tx/lazy_distribution.rs +++ b/crates/brk_computer/src/internal/per_tx/lazy_distribution.rs @@ -32,9 +32,10 @@ where db: &Database, name: &str, version: Version, + indexes: &indexes::Vecs, tx_index: LazyVecFrom2, ) -> Result { - let distribution = TxDerivedDistribution::forced_import(db, name, version)?; + let distribution = TxDerivedDistribution::forced_import(db, name, version, indexes)?; Ok(Self { tx_index, distribution, diff --git a/crates/brk_computer/src/internal/transform/mod.rs b/crates/brk_computer/src/internal/transform/mod.rs index cfd5ad17c..cc2615590 100644 --- a/crates/brk_computer/src/internal/transform/mod.rs +++ b/crates/brk_computer/src/internal/transform/mod.rs @@ -28,4 +28,4 @@ pub use ratio::{ RatioCentsSignedDollarsBps32, RatioDiffCentsBps32, RatioDiffDollarsBps32, RatioDiffF32Bps32, RatioDollarsBp16, RatioDollarsBp32, RatioDollarsBps32, RatioSatsBp16, RatioU64Bp16, }; -pub use specialized::{BlockCountTarget, OhlcCentsToDollars, OhlcCentsToSats}; +pub use specialized::{BlockCountTarget24h, BlockCountTarget1w, BlockCountTarget1m, BlockCountTarget1y, OhlcCentsToDollars, OhlcCentsToSats}; diff --git a/crates/brk_computer/src/internal/transform/specialized.rs b/crates/brk_computer/src/internal/transform/specialized.rs index 8911b2452..b3efe88d0 100644 --- a/crates/brk_computer/src/internal/transform/specialized.rs +++ b/crates/brk_computer/src/internal/transform/specialized.rs @@ -1,132 +1,36 @@ use brk_types::{ - Close, Day1, Day3, Epoch, Halving, Height, High, Hour1, Hour4, Hour12, Low, - Minute10, Minute30, Month1, Month3, Month6, OHLCCents, OHLCDollars, OHLCSats, Open, StoredU64, - Week1, Year1, Year10, + Close, Day1, Day3, Epoch, Halving, Height, High, Hour1, Hour4, Hour12, Low, Minute10, Minute30, + Month1, Month3, Month6, OHLCCents, OHLCDollars, OHLCSats, Open, StoredU64, Week1, Year1, + Year10, }; use vecdb::UnaryTransform; use super::CentsUnsignedToSats; use crate::blocks::{ - TARGET_BLOCKS_PER_DAY, TARGET_BLOCKS_PER_DAY3, TARGET_BLOCKS_PER_DECADE, - TARGET_BLOCKS_PER_HALVING, TARGET_BLOCKS_PER_HOUR1, TARGET_BLOCKS_PER_HOUR4, - TARGET_BLOCKS_PER_HOUR12, TARGET_BLOCKS_PER_MINUTE10, TARGET_BLOCKS_PER_MINUTE30, - TARGET_BLOCKS_PER_MONTH, TARGET_BLOCKS_PER_QUARTER, TARGET_BLOCKS_PER_SEMESTER, - TARGET_BLOCKS_PER_WEEK, TARGET_BLOCKS_PER_YEAR, + TARGET_BLOCKS_PER_DAY, TARGET_BLOCKS_PER_MONTH, TARGET_BLOCKS_PER_WEEK, TARGET_BLOCKS_PER_YEAR, }; -pub struct BlockCountTarget; - -impl UnaryTransform for BlockCountTarget { - #[inline(always)] - fn apply(_: Height) -> StoredU64 { - StoredU64::from(TARGET_BLOCKS_PER_DAY) - } +macro_rules! const_block_target { + ($name:ident, $value:expr) => { + pub struct $name; + const_block_target!(@impl $name, $value, Height, Minute10, Minute30, Hour1, Hour4, Hour12, Day1, Day3, Week1, Month1, Month3, Month6, Year1, Year10, Halving, Epoch); + }; + (@impl $name:ident, $value:expr, $($idx:ty),*) => { + $( + impl UnaryTransform<$idx, StoredU64> for $name { + #[inline(always)] + fn apply(_: $idx) -> StoredU64 { + StoredU64::from($value) + } + } + )* + }; } -impl UnaryTransform for BlockCountTarget { - #[inline(always)] - fn apply(_: Minute10) -> StoredU64 { - StoredU64::from(TARGET_BLOCKS_PER_MINUTE10) - } -} - -impl UnaryTransform for BlockCountTarget { - #[inline(always)] - fn apply(_: Minute30) -> StoredU64 { - StoredU64::from(TARGET_BLOCKS_PER_MINUTE30) - } -} - -impl UnaryTransform for BlockCountTarget { - #[inline(always)] - fn apply(_: Hour1) -> StoredU64 { - StoredU64::from(TARGET_BLOCKS_PER_HOUR1) - } -} - -impl UnaryTransform for BlockCountTarget { - #[inline(always)] - fn apply(_: Hour4) -> StoredU64 { - StoredU64::from(TARGET_BLOCKS_PER_HOUR4) - } -} - -impl UnaryTransform for BlockCountTarget { - #[inline(always)] - fn apply(_: Hour12) -> StoredU64 { - StoredU64::from(TARGET_BLOCKS_PER_HOUR12) - } -} - -impl UnaryTransform for BlockCountTarget { - #[inline(always)] - fn apply(_: Day1) -> StoredU64 { - StoredU64::from(TARGET_BLOCKS_PER_DAY) - } -} - -impl UnaryTransform for BlockCountTarget { - #[inline(always)] - fn apply(_: Day3) -> StoredU64 { - StoredU64::from(TARGET_BLOCKS_PER_DAY3) - } -} - -impl UnaryTransform for BlockCountTarget { - #[inline(always)] - fn apply(_: Week1) -> StoredU64 { - StoredU64::from(TARGET_BLOCKS_PER_WEEK) - } -} - -impl UnaryTransform for BlockCountTarget { - #[inline(always)] - fn apply(_: Month1) -> StoredU64 { - StoredU64::from(TARGET_BLOCKS_PER_MONTH) - } -} - -impl UnaryTransform for BlockCountTarget { - #[inline(always)] - fn apply(_: Month3) -> StoredU64 { - StoredU64::from(TARGET_BLOCKS_PER_QUARTER) - } -} - -impl UnaryTransform for BlockCountTarget { - #[inline(always)] - fn apply(_: Month6) -> StoredU64 { - StoredU64::from(TARGET_BLOCKS_PER_SEMESTER) - } -} - -impl UnaryTransform for BlockCountTarget { - #[inline(always)] - fn apply(_: Year1) -> StoredU64 { - StoredU64::from(TARGET_BLOCKS_PER_YEAR) - } -} - -impl UnaryTransform for BlockCountTarget { - #[inline(always)] - fn apply(_: Year10) -> StoredU64 { - StoredU64::from(TARGET_BLOCKS_PER_DECADE) - } -} - -impl UnaryTransform for BlockCountTarget { - #[inline(always)] - fn apply(_: Halving) -> StoredU64 { - StoredU64::from(TARGET_BLOCKS_PER_HALVING) - } -} - -impl UnaryTransform for BlockCountTarget { - #[inline(always)] - fn apply(_: Epoch) -> StoredU64 { - StoredU64::from(2016u64) - } -} +const_block_target!(BlockCountTarget24h, TARGET_BLOCKS_PER_DAY); +const_block_target!(BlockCountTarget1w, TARGET_BLOCKS_PER_WEEK); +const_block_target!(BlockCountTarget1m, TARGET_BLOCKS_PER_MONTH); +const_block_target!(BlockCountTarget1y, TARGET_BLOCKS_PER_YEAR); pub struct OhlcCentsToDollars; diff --git a/crates/brk_computer/src/outputs/count/compute.rs b/crates/brk_computer/src/outputs/count/compute.rs index 0134fbb0b..b782b4cb0 100644 --- a/crates/brk_computer/src/outputs/count/compute.rs +++ b/crates/brk_computer/src/outputs/count/compute.rs @@ -33,8 +33,8 @@ impl Vecs { self.unspent.height.compute_transform3( starting_indexes.height, - &self.total.full.cumulative, - &inputs_count.full.cumulative, + &self.total.full.cumulative.height, + &inputs_count.full.cumulative.height, &scripts_count.op_return.cumulative.height, |(h, output_count, input_count, op_return_count, ..)| { let block_count = u64::from(h + 1_usize); diff --git a/crates/brk_computer/src/scripts/adoption.rs b/crates/brk_computer/src/scripts/adoption.rs index 1d8fb3530..b5feecc5d 100644 --- a/crates/brk_computer/src/scripts/adoption.rs +++ b/crates/brk_computer/src/scripts/adoption.rs @@ -39,14 +39,14 @@ impl Vecs { self.taproot.compute_binary::<_, _, RatioU64Bp16>( starting_indexes.height, &count.p2tr.base.height, - &outputs_count.total.full.sum, + &outputs_count.total.full.sum.height, exit, )?; self.segwit.compute_binary::<_, _, RatioU64Bp16>( starting_indexes.height, &count.segwit.base.height, - &outputs_count.total.full.sum, + &outputs_count.total.full.sum.height, exit, )?; diff --git a/crates/brk_computer/src/transactions/fees/import.rs b/crates/brk_computer/src/transactions/fees/import.rs index 28e3b843e..d0e806fd6 100644 --- a/crates/brk_computer/src/transactions/fees/import.rs +++ b/crates/brk_computer/src/transactions/fees/import.rs @@ -3,19 +3,19 @@ use brk_types::Version; use vecdb::{Database, EagerVec, ImportableVec}; use super::Vecs; -use crate::internal::PerTxDistribution; +use crate::{indexes, internal::PerTxDistribution}; /// Bump this when fee/feerate aggregation logic changes (e.g., skip coinbase). const VERSION: Version = Version::new(2); impl Vecs { - pub(crate) fn forced_import(db: &Database, version: Version) -> Result { + pub(crate) fn forced_import(db: &Database, version: Version, indexes: &indexes::Vecs) -> Result { let v = version + VERSION; Ok(Self { input_value: EagerVec::forced_import(db, "input_value", version)?, output_value: EagerVec::forced_import(db, "output_value", version)?, - fee: PerTxDistribution::forced_import(db, "fee", v)?, - fee_rate: PerTxDistribution::forced_import(db, "fee_rate", v)?, + fee: PerTxDistribution::forced_import(db, "fee", v, indexes)?, + fee_rate: PerTxDistribution::forced_import(db, "fee_rate", v, indexes)?, }) } } diff --git a/crates/brk_computer/src/transactions/import.rs b/crates/brk_computer/src/transactions/import.rs index 02f998fcb..1a50a06c4 100644 --- a/crates/brk_computer/src/transactions/import.rs +++ b/crates/brk_computer/src/transactions/import.rs @@ -23,8 +23,8 @@ impl Vecs { let version = parent_version; let count = CountVecs::forced_import(&db, version, indexer, indexes, cached_starts)?; - let size = SizeVecs::forced_import(&db, version, indexer)?; - let fees = FeesVecs::forced_import(&db, version)?; + let size = SizeVecs::forced_import(&db, version, indexer, indexes)?; + let fees = FeesVecs::forced_import(&db, version, indexes)?; let versions = VersionsVecs::forced_import(&db, version, indexes, cached_starts)?; let volume = VolumeVecs::forced_import(&db, version, indexes, cached_starts)?; diff --git a/crates/brk_computer/src/transactions/size/import.rs b/crates/brk_computer/src/transactions/size/import.rs index cf13bda7d..eef97a901 100644 --- a/crates/brk_computer/src/transactions/size/import.rs +++ b/crates/brk_computer/src/transactions/size/import.rs @@ -4,13 +4,14 @@ use brk_types::{TxIndex, VSize, Version, Weight}; use vecdb::{Database, LazyVecFrom2, ReadableCloneableVec}; use super::Vecs; -use crate::internal::{LazyPerTxDistribution, LazyPerTxDistributionTransformed, VSizeToWeight}; +use crate::{indexes, internal::{LazyPerTxDistribution, LazyPerTxDistributionTransformed, VSizeToWeight}}; impl Vecs { pub(crate) fn forced_import( db: &Database, version: Version, indexer: &Indexer, + indexes: &indexes::Vecs, ) -> Result { let tx_index_to_vsize = LazyVecFrom2::init( "tx_vsize", @@ -23,7 +24,7 @@ impl Vecs { ); let vsize = - LazyPerTxDistribution::forced_import(db, "tx_vsize", version, tx_index_to_vsize)?; + LazyPerTxDistribution::forced_import(db, "tx_vsize", version, indexes, tx_index_to_vsize)?; let tx_index_to_weight = LazyVecFrom2::init( "tx_weight", diff --git a/crates/brk_computer/src/transactions/volume/compute.rs b/crates/brk_computer/src/transactions/volume/compute.rs index 4f202b403..f1bf5d128 100644 --- a/crates/brk_computer/src/transactions/volume/compute.rs +++ b/crates/brk_computer/src/transactions/volume/compute.rs @@ -22,44 +22,21 @@ impl Vecs { starting_indexes: &Indexes, exit: &Exit, ) -> Result<()> { - // sent_sum and received_sum are independent — parallelize - let (r1, r2) = rayon::join( - || { - self.transfer_volume.compute( + self.transfer_volume.compute( + starting_indexes.height, + prices, + exit, + |sats_vec| { + Ok(sats_vec.compute_filtered_sum_from_indexes( starting_indexes.height, - prices, + &indexer.vecs.transactions.first_tx_index, + &indexes.height.tx_index_count, + &fees_vecs.input_value, + |sats| !sats.is_max(), exit, - |sats_vec| { - Ok(sats_vec.compute_filtered_sum_from_indexes( - starting_indexes.height, - &indexer.vecs.transactions.first_tx_index, - &indexes.height.tx_index_count, - &fees_vecs.input_value, - |sats| !sats.is_max(), - exit, - )?) - }, - ) + )?) }, - || { - self.output_volume.compute( - starting_indexes.height, - prices, - exit, - |sats_vec| { - Ok(sats_vec.compute_sum_from_indexes( - starting_indexes.height, - &indexer.vecs.transactions.first_tx_index, - &indexes.height.tx_index_count, - &fees_vecs.output_value, - exit, - )?) - }, - ) - }, - ); - r1?; - r2?; + )?; self.tx_per_sec .height @@ -73,7 +50,7 @@ impl Vecs { .height .compute_binary::<_, Timestamp, PerSec>( starting_indexes.height, - &inputs_count.full.sum, + &inputs_count.full.sum.height, &blocks.interval.base, exit, )?; @@ -81,7 +58,7 @@ impl Vecs { .height .compute_binary::<_, Timestamp, PerSec>( starting_indexes.height, - &outputs_count.total.full.sum, + &outputs_count.total.full.sum.height, &blocks.interval.base, exit, )?; diff --git a/crates/brk_computer/src/transactions/volume/import.rs b/crates/brk_computer/src/transactions/volume/import.rs index 926081556..6e312aa5f 100644 --- a/crates/brk_computer/src/transactions/volume/import.rs +++ b/crates/brk_computer/src/transactions/volume/import.rs @@ -24,13 +24,6 @@ impl Vecs { indexes, cached_starts, )?, - output_volume: AmountPerBlockCumulativeWithSums::forced_import( - db, - "output_volume", - version, - indexes, - cached_starts, - )?, tx_per_sec: PerBlock::forced_import(db, "tx_per_sec", version + v2, indexes)?, outputs_per_sec: PerBlock::forced_import( db, diff --git a/crates/brk_computer/src/transactions/volume/vecs.rs b/crates/brk_computer/src/transactions/volume/vecs.rs index 271ff7b16..982e95173 100644 --- a/crates/brk_computer/src/transactions/volume/vecs.rs +++ b/crates/brk_computer/src/transactions/volume/vecs.rs @@ -7,7 +7,6 @@ use crate::internal::{AmountPerBlockCumulativeWithSums, PerBlock}; #[derive(Traversable)] pub struct Vecs { pub transfer_volume: AmountPerBlockCumulativeWithSums, - pub output_volume: AmountPerBlockCumulativeWithSums, pub tx_per_sec: PerBlock, pub outputs_per_sec: PerBlock, pub inputs_per_sec: PerBlock, diff --git a/modules/brk-client/index.js b/modules/brk-client/index.js index e45632929..53f8a56b5 100644 --- a/modules/brk-client/index.js +++ b/modules/brk-client/index.js @@ -1797,17 +1797,17 @@ function create_10y1m1w1y2y3m3y4y5y6m6y8yPattern3(client, acc) { /** * @typedef {Object} AverageCumulativeMaxMedianMinPct10Pct25Pct75Pct90RollingSumPattern - * @property {SeriesPattern18} average - * @property {SeriesPattern18} cumulative - * @property {SeriesPattern18} max - * @property {SeriesPattern18} median - * @property {SeriesPattern18} min - * @property {SeriesPattern18} pct10 - * @property {SeriesPattern18} pct25 - * @property {SeriesPattern18} pct75 - * @property {SeriesPattern18} pct90 + * @property {SeriesPattern1} average + * @property {SeriesPattern1} cumulative + * @property {SeriesPattern1} max + * @property {SeriesPattern1} median + * @property {SeriesPattern1} min + * @property {SeriesPattern1} pct10 + * @property {SeriesPattern1} pct25 + * @property {SeriesPattern1} pct75 + * @property {SeriesPattern1} pct90 * @property {AverageMaxMedianMinPct10Pct25Pct75Pct90SumPattern} rolling - * @property {SeriesPattern18} sum + * @property {SeriesPattern1} sum */ /** @@ -1818,17 +1818,17 @@ function create_10y1m1w1y2y3m3y4y5y6m6y8yPattern3(client, acc) { */ function createAverageCumulativeMaxMedianMinPct10Pct25Pct75Pct90RollingSumPattern(client, acc) { return { - average: createSeriesPattern18(client, _m(acc, 'average')), - cumulative: createSeriesPattern18(client, _m(acc, 'cumulative')), - max: createSeriesPattern18(client, _m(acc, 'max')), - median: createSeriesPattern18(client, _m(acc, 'median')), - min: createSeriesPattern18(client, _m(acc, 'min')), - pct10: createSeriesPattern18(client, _m(acc, 'pct10')), - pct25: createSeriesPattern18(client, _m(acc, 'pct25')), - pct75: createSeriesPattern18(client, _m(acc, 'pct75')), - pct90: createSeriesPattern18(client, _m(acc, 'pct90')), + average: createSeriesPattern1(client, _m(acc, 'average')), + cumulative: createSeriesPattern1(client, _m(acc, 'cumulative')), + max: createSeriesPattern1(client, _m(acc, 'max')), + median: createSeriesPattern1(client, _m(acc, 'median')), + min: createSeriesPattern1(client, _m(acc, 'min')), + pct10: createSeriesPattern1(client, _m(acc, 'pct10')), + pct25: createSeriesPattern1(client, _m(acc, 'pct25')), + pct75: createSeriesPattern1(client, _m(acc, 'pct75')), + pct90: createSeriesPattern1(client, _m(acc, 'pct90')), rolling: createAverageMaxMedianMinPct10Pct25Pct75Pct90SumPattern(client, acc), - sum: createSeriesPattern18(client, _m(acc, 'sum')), + sum: createSeriesPattern1(client, _m(acc, 'sum')), }; } @@ -1987,6 +1987,37 @@ function createAverageMaxMedianMinPct10Pct25Pct75Pct90SumPattern(client, acc) { }; } +/** + * @typedef {Object} AverageMaxMedianMinPct10Pct25Pct75Pct90Pattern2 + * @property {SeriesPattern18} average + * @property {SeriesPattern18} max + * @property {SeriesPattern18} median + * @property {SeriesPattern18} min + * @property {SeriesPattern18} pct10 + * @property {SeriesPattern18} pct25 + * @property {SeriesPattern18} pct75 + * @property {SeriesPattern18} pct90 + */ + +/** + * Create a AverageMaxMedianMinPct10Pct25Pct75Pct90Pattern2 pattern node + * @param {BrkClientBase} client + * @param {string} acc - Accumulated series name + * @returns {AverageMaxMedianMinPct10Pct25Pct75Pct90Pattern2} + */ +function createAverageMaxMedianMinPct10Pct25Pct75Pct90Pattern2(client, acc) { + return { + average: createSeriesPattern18(client, _m(acc, 'average')), + max: createSeriesPattern18(client, _m(acc, 'max')), + median: createSeriesPattern18(client, _m(acc, 'median')), + min: createSeriesPattern18(client, _m(acc, 'min')), + pct10: createSeriesPattern18(client, _m(acc, 'pct10')), + pct25: createSeriesPattern18(client, _m(acc, 'pct25')), + pct75: createSeriesPattern18(client, _m(acc, 'pct75')), + pct90: createSeriesPattern18(client, _m(acc, 'pct90')), + }; +} + /** * @typedef {Object} BaseCapitulationCumulativeNegativeSumToValuePattern * @property {CentsUsdPattern2} base @@ -2014,14 +2045,14 @@ function createAverageMaxMedianMinPct10Pct25Pct75Pct90SumPattern(client, acc) { /** * @template T * @typedef {Object} AverageMaxMedianMinPct10Pct25Pct75Pct90Pattern - * @property {SeriesPattern18} average - * @property {SeriesPattern18} max - * @property {SeriesPattern18} median - * @property {SeriesPattern18} min - * @property {SeriesPattern18} pct10 - * @property {SeriesPattern18} pct25 - * @property {SeriesPattern18} pct75 - * @property {SeriesPattern18} pct90 + * @property {SeriesPattern1} average + * @property {SeriesPattern1} max + * @property {SeriesPattern1} median + * @property {SeriesPattern1} min + * @property {SeriesPattern1} pct10 + * @property {SeriesPattern1} pct25 + * @property {SeriesPattern1} pct75 + * @property {SeriesPattern1} pct90 */ /** @@ -2033,14 +2064,14 @@ function createAverageMaxMedianMinPct10Pct25Pct75Pct90SumPattern(client, acc) { */ function createAverageMaxMedianMinPct10Pct25Pct75Pct90Pattern(client, acc) { return { - average: createSeriesPattern18(client, _m(acc, 'average')), - max: createSeriesPattern18(client, _m(acc, 'max')), - median: createSeriesPattern18(client, _m(acc, 'median')), - min: createSeriesPattern18(client, _m(acc, 'min')), - pct10: createSeriesPattern18(client, _m(acc, 'pct10')), - pct25: createSeriesPattern18(client, _m(acc, 'pct25')), - pct75: createSeriesPattern18(client, _m(acc, 'pct75')), - pct90: createSeriesPattern18(client, _m(acc, 'pct90')), + average: createSeriesPattern1(client, _m(acc, 'average')), + max: createSeriesPattern1(client, _m(acc, 'max')), + median: createSeriesPattern1(client, _m(acc, 'median')), + min: createSeriesPattern1(client, _m(acc, 'min')), + pct10: createSeriesPattern1(client, _m(acc, 'pct10')), + pct25: createSeriesPattern1(client, _m(acc, 'pct25')), + pct75: createSeriesPattern1(client, _m(acc, 'pct75')), + pct90: createSeriesPattern1(client, _m(acc, 'pct90')), }; } @@ -3959,7 +3990,7 @@ function createUnspentPattern(client, acc) { /** * @typedef {Object} SeriesTree_Blocks_Count - * @property {SeriesPattern1} target + * @property {_1m1w1y24hPattern} target * @property {BaseCumulativeSumPattern2} total */ @@ -4012,9 +4043,9 @@ function createUnspentPattern(client, acc) { /** * @typedef {Object} SeriesTree_Blocks_Fullness - * @property {_1m1w1y24hBasePattern} bps - * @property {SeriesPattern1} ratio - * @property {SeriesPattern1} percent + * @property {SeriesPattern18} bps + * @property {SeriesPattern18} ratio + * @property {SeriesPattern18} percent */ /** @@ -4057,7 +4088,14 @@ function createUnspentPattern(client, acc) { /** * @typedef {Object} SeriesTree_Transactions_Size * @property {_6bBlockTxPattern} vsize - * @property {_6bBlockTxPattern} weight + * @property {SeriesTree_Transactions_Size_Weight} weight + */ + +/** + * @typedef {Object} SeriesTree_Transactions_Size_Weight + * @property {SeriesPattern19} txIndex + * @property {AverageMaxMedianMinPct10Pct25Pct75Pct90Pattern2} block + * @property {AverageMaxMedianMinPct10Pct25Pct75Pct90Pattern2} _6b */ /** @@ -4078,7 +4116,6 @@ function createUnspentPattern(client, acc) { /** * @typedef {Object} SeriesTree_Transactions_Volume * @property {BaseCumulativeSumPattern4} transferVolume - * @property {BaseCumulativeSumPattern4} outputVolume * @property {SeriesPattern1} txPerSec * @property {SeriesPattern1} outputsPerSec * @property {SeriesPattern1} inputsPerSec @@ -7492,7 +7529,7 @@ class BrkClient extends BrkClientBase { pct90: create_1m1w1y24hPattern(this, 'block_weight_pct90'), }, count: { - target: createSeriesPattern1(this, 'block_count_target'), + target: create_1m1w1y24hPattern(this, 'block_count_target'), total: createBaseCumulativeSumPattern2(this, 'block_count'), }, lookback: { @@ -7543,9 +7580,9 @@ class BrkClient extends BrkClientBase { interval: create_1m1w1y24hBasePattern(this, 'block_interval'), vbytes: createAverageBaseCumulativeMaxMedianMinPct10Pct25Pct75Pct90SumPattern2(this, 'block_vbytes'), fullness: { - bps: create_1m1w1y24hBasePattern(this, 'block_fullness_bps'), - ratio: createSeriesPattern1(this, 'block_fullness_ratio'), - percent: createSeriesPattern1(this, 'block_fullness'), + bps: createSeriesPattern18(this, 'block_fullness_bps'), + ratio: createSeriesPattern18(this, 'block_fullness_ratio'), + percent: createSeriesPattern18(this, 'block_fullness'), }, halving: { epoch: createSeriesPattern1(this, 'halving_epoch'), @@ -7572,7 +7609,11 @@ class BrkClient extends BrkClientBase { }, size: { vsize: create_6bBlockTxPattern(this, 'tx_vsize'), - weight: create_6bBlockTxPattern(this, 'tx_weight'), + weight: { + txIndex: createSeriesPattern19(this, 'tx_weight'), + block: createAverageMaxMedianMinPct10Pct25Pct75Pct90Pattern2(this, 'tx_weight'), + _6b: createAverageMaxMedianMinPct10Pct25Pct75Pct90Pattern2(this, 'tx_weight_6b'), + }, }, fees: { inputValue: createSeriesPattern19(this, 'input_value'), @@ -7587,7 +7628,6 @@ class BrkClient extends BrkClientBase { }, volume: { transferVolume: createBaseCumulativeSumPattern4(this, 'transfer_volume_bis'), - outputVolume: createBaseCumulativeSumPattern4(this, 'output_volume'), txPerSec: createSeriesPattern1(this, 'tx_per_sec'), outputsPerSec: createSeriesPattern1(this, 'outputs_per_sec'), inputsPerSec: createSeriesPattern1(this, 'inputs_per_sec'), diff --git a/packages/brk_client/brk_client/__init__.py b/packages/brk_client/brk_client/__init__.py index 70868911e..652b0cd0b 100644 --- a/packages/brk_client/brk_client/__init__.py +++ b/packages/brk_client/brk_client/__init__.py @@ -2226,17 +2226,17 @@ class AverageCumulativeMaxMedianMinPct10Pct25Pct75Pct90RollingSumPattern: def __init__(self, client: BrkClientBase, acc: str): """Create pattern node with accumulated series name.""" - self.average: SeriesPattern18[StoredU64] = SeriesPattern18(client, _m(acc, 'average')) - self.cumulative: SeriesPattern18[StoredU64] = SeriesPattern18(client, _m(acc, 'cumulative')) - self.max: SeriesPattern18[StoredU64] = SeriesPattern18(client, _m(acc, 'max')) - self.median: SeriesPattern18[StoredU64] = SeriesPattern18(client, _m(acc, 'median')) - self.min: SeriesPattern18[StoredU64] = SeriesPattern18(client, _m(acc, 'min')) - self.pct10: SeriesPattern18[StoredU64] = SeriesPattern18(client, _m(acc, 'pct10')) - self.pct25: SeriesPattern18[StoredU64] = SeriesPattern18(client, _m(acc, 'pct25')) - self.pct75: SeriesPattern18[StoredU64] = SeriesPattern18(client, _m(acc, 'pct75')) - self.pct90: SeriesPattern18[StoredU64] = SeriesPattern18(client, _m(acc, 'pct90')) + self.average: SeriesPattern1[StoredU64] = SeriesPattern1(client, _m(acc, 'average')) + self.cumulative: SeriesPattern1[StoredU64] = SeriesPattern1(client, _m(acc, 'cumulative')) + self.max: SeriesPattern1[StoredU64] = SeriesPattern1(client, _m(acc, 'max')) + self.median: SeriesPattern1[StoredU64] = SeriesPattern1(client, _m(acc, 'median')) + self.min: SeriesPattern1[StoredU64] = SeriesPattern1(client, _m(acc, 'min')) + self.pct10: SeriesPattern1[StoredU64] = SeriesPattern1(client, _m(acc, 'pct10')) + self.pct25: SeriesPattern1[StoredU64] = SeriesPattern1(client, _m(acc, 'pct25')) + self.pct75: SeriesPattern1[StoredU64] = SeriesPattern1(client, _m(acc, 'pct75')) + self.pct90: SeriesPattern1[StoredU64] = SeriesPattern1(client, _m(acc, 'pct90')) self.rolling: AverageMaxMedianMinPct10Pct25Pct75Pct90SumPattern = AverageMaxMedianMinPct10Pct25Pct75Pct90SumPattern(client, acc) - self.sum: SeriesPattern18[StoredU64] = SeriesPattern18(client, _m(acc, 'sum')) + self.sum: SeriesPattern1[StoredU64] = SeriesPattern1(client, _m(acc, 'sum')) class AverageBaseCumulativeMaxMedianMinPct10Pct25Pct75Pct90SumPattern2: """Pattern struct for repeated tree structure.""" @@ -2305,6 +2305,20 @@ class AverageMaxMedianMinPct10Pct25Pct75Pct90SumPattern: self.pct90: _1m1w1y24hPattern[StoredU64] = _1m1w1y24hPattern(client, _m(acc, 'pct90')) self.sum: _1m1w1y24hPattern[StoredU64] = _1m1w1y24hPattern(client, _m(acc, 'sum')) +class AverageMaxMedianMinPct10Pct25Pct75Pct90Pattern2: + """Pattern struct for repeated tree structure.""" + + def __init__(self, client: BrkClientBase, acc: str): + """Create pattern node with accumulated series name.""" + self.average: SeriesPattern18[Weight] = SeriesPattern18(client, _m(acc, 'average')) + self.max: SeriesPattern18[Weight] = SeriesPattern18(client, _m(acc, 'max')) + self.median: SeriesPattern18[Weight] = SeriesPattern18(client, _m(acc, 'median')) + self.min: SeriesPattern18[Weight] = SeriesPattern18(client, _m(acc, 'min')) + self.pct10: SeriesPattern18[Weight] = SeriesPattern18(client, _m(acc, 'pct10')) + self.pct25: SeriesPattern18[Weight] = SeriesPattern18(client, _m(acc, 'pct25')) + self.pct75: SeriesPattern18[Weight] = SeriesPattern18(client, _m(acc, 'pct75')) + self.pct90: SeriesPattern18[Weight] = SeriesPattern18(client, _m(acc, 'pct90')) + class BaseCapitulationCumulativeNegativeSumToValuePattern: """Pattern struct for repeated tree structure.""" pass @@ -2318,14 +2332,14 @@ class AverageMaxMedianMinPct10Pct25Pct75Pct90Pattern(Generic[T]): def __init__(self, client: BrkClientBase, acc: str): """Create pattern node with accumulated series name.""" - self.average: SeriesPattern18[T] = SeriesPattern18(client, _m(acc, 'average')) - self.max: SeriesPattern18[T] = SeriesPattern18(client, _m(acc, 'max')) - self.median: SeriesPattern18[T] = SeriesPattern18(client, _m(acc, 'median')) - self.min: SeriesPattern18[T] = SeriesPattern18(client, _m(acc, 'min')) - self.pct10: SeriesPattern18[T] = SeriesPattern18(client, _m(acc, 'pct10')) - self.pct25: SeriesPattern18[T] = SeriesPattern18(client, _m(acc, 'pct25')) - self.pct75: SeriesPattern18[T] = SeriesPattern18(client, _m(acc, 'pct75')) - self.pct90: SeriesPattern18[T] = SeriesPattern18(client, _m(acc, 'pct90')) + self.average: SeriesPattern1[T] = SeriesPattern1(client, _m(acc, 'average')) + self.max: SeriesPattern1[T] = SeriesPattern1(client, _m(acc, 'max')) + self.median: SeriesPattern1[T] = SeriesPattern1(client, _m(acc, 'median')) + self.min: SeriesPattern1[T] = SeriesPattern1(client, _m(acc, 'min')) + self.pct10: SeriesPattern1[T] = SeriesPattern1(client, _m(acc, 'pct10')) + self.pct25: SeriesPattern1[T] = SeriesPattern1(client, _m(acc, 'pct25')) + self.pct75: SeriesPattern1[T] = SeriesPattern1(client, _m(acc, 'pct75')) + self.pct90: SeriesPattern1[T] = SeriesPattern1(client, _m(acc, 'pct90')) class _10y2y3y4y5y6y8yPattern: """Pattern struct for repeated tree structure.""" @@ -3175,7 +3189,7 @@ class SeriesTree_Blocks_Count: """Series tree node.""" def __init__(self, client: BrkClientBase, base_path: str = ''): - self.target: SeriesPattern1[StoredU64] = SeriesPattern1(client, 'block_count_target') + self.target: _1m1w1y24hPattern[StoredU64] = _1m1w1y24hPattern(client, 'block_count_target') self.total: BaseCumulativeSumPattern2 = BaseCumulativeSumPattern2(client, 'block_count') class SeriesTree_Blocks_Lookback: @@ -3230,9 +3244,9 @@ class SeriesTree_Blocks_Fullness: """Series tree node.""" def __init__(self, client: BrkClientBase, base_path: str = ''): - self.bps: _1m1w1y24hBasePattern[BasisPoints16] = _1m1w1y24hBasePattern(client, 'block_fullness_bps') - self.ratio: SeriesPattern1[StoredF32] = SeriesPattern1(client, 'block_fullness_ratio') - self.percent: SeriesPattern1[StoredF32] = SeriesPattern1(client, 'block_fullness') + self.bps: SeriesPattern18[BasisPoints16] = SeriesPattern18(client, 'block_fullness_bps') + self.ratio: SeriesPattern18[StoredF32] = SeriesPattern18(client, 'block_fullness_ratio') + self.percent: SeriesPattern18[StoredF32] = SeriesPattern18(client, 'block_fullness') class SeriesTree_Blocks_Halving: """Series tree node.""" @@ -3280,12 +3294,20 @@ class SeriesTree_Transactions_Count: self.total: AverageBaseCumulativeMaxMedianMinPct10Pct25Pct75Pct90SumPattern2 = AverageBaseCumulativeMaxMedianMinPct10Pct25Pct75Pct90SumPattern2(client, 'tx_count') self.is_coinbase: SeriesPattern19[StoredBool] = SeriesPattern19(client, 'is_coinbase') +class SeriesTree_Transactions_Size_Weight: + """Series tree node.""" + + def __init__(self, client: BrkClientBase, base_path: str = ''): + self.tx_index: SeriesPattern19[Weight] = SeriesPattern19(client, 'tx_weight') + self.block: AverageMaxMedianMinPct10Pct25Pct75Pct90Pattern2 = AverageMaxMedianMinPct10Pct25Pct75Pct90Pattern2(client, 'tx_weight') + self._6b: AverageMaxMedianMinPct10Pct25Pct75Pct90Pattern2 = AverageMaxMedianMinPct10Pct25Pct75Pct90Pattern2(client, 'tx_weight_6b') + class SeriesTree_Transactions_Size: """Series tree node.""" def __init__(self, client: BrkClientBase, base_path: str = ''): self.vsize: _6bBlockTxPattern[VSize] = _6bBlockTxPattern(client, 'tx_vsize') - self.weight: _6bBlockTxPattern[Weight] = _6bBlockTxPattern(client, 'tx_weight') + self.weight: SeriesTree_Transactions_Size_Weight = SeriesTree_Transactions_Size_Weight(client) class SeriesTree_Transactions_Fees: """Series tree node.""" @@ -3309,7 +3331,6 @@ class SeriesTree_Transactions_Volume: def __init__(self, client: BrkClientBase, base_path: str = ''): self.transfer_volume: BaseCumulativeSumPattern4 = BaseCumulativeSumPattern4(client, 'transfer_volume_bis') - self.output_volume: BaseCumulativeSumPattern4 = BaseCumulativeSumPattern4(client, 'output_volume') self.tx_per_sec: SeriesPattern1[StoredF32] = SeriesPattern1(client, 'tx_per_sec') self.outputs_per_sec: SeriesPattern1[StoredF32] = SeriesPattern1(client, 'outputs_per_sec') self.inputs_per_sec: SeriesPattern1[StoredF32] = SeriesPattern1(client, 'inputs_per_sec') diff --git a/website/scripts/options/market.js b/website/scripts/options/market.js index 96751041a..e8cbc1a50 100644 --- a/website/scripts/options/market.js +++ b/website/scripts/options/market.js @@ -7,6 +7,7 @@ import { Unit } from "../utils/units.js"; import { priceLine, priceLines } from "./constants.js"; import { baseline, + deltaTree, histogram, line, price, @@ -522,58 +523,12 @@ export function createMarketSection() { }), ], }, - { - name: "Change", - tree: [ - { - name: "Compare", - title: "Market Cap Change", - bottom: ROLLING_WINDOWS.map((w) => - baseline({ - series: supply.marketCap.delta.absolute[w.key].usd, - name: w.name, - color: w.color, - unit: Unit.usd, - }), - ), - }, - ...ROLLING_WINDOWS.map((w) => ({ - name: w.name, - title: `Market Cap Change ${w.name}`, - bottom: [ - baseline({ - series: supply.marketCap.delta.absolute[w.key].usd, - name: w.name, - unit: Unit.usd, - }), - ], - })), - ], - }, - { - name: "Growth Rate", - tree: [ - { - name: "Compare", - title: "Market Cap Growth Rate", - bottom: ROLLING_WINDOWS.flatMap((w) => - percentRatio({ - pattern: supply.marketCap.delta.rate[w.key], - name: w.name, - color: w.color, - }), - ), - }, - ...ROLLING_WINDOWS.map((w) => ({ - name: w.name, - title: `Market Cap Growth Rate ${w.name}`, - bottom: percentRatioBaseline({ - pattern: supply.marketCap.delta.rate[w.key], - name: w.name, - }), - })), - ], - }, + ...deltaTree({ + delta: supply.marketCap.delta, + title: "Market Cap", + unit: Unit.usd, + extract: (v) => v.usd, + }), ], }, { @@ -591,58 +546,12 @@ export function createMarketSection() { }), ], }, - { - name: "Change", - tree: [ - { - name: "Compare", - title: "Realized Cap Change", - bottom: ROLLING_WINDOWS.map((w) => - baseline({ - series: cohorts.utxo.all.realized.cap.delta.absolute[w.key].usd, - name: w.name, - color: w.color, - unit: Unit.usd, - }), - ), - }, - ...ROLLING_WINDOWS.map((w) => ({ - name: w.name, - title: `Realized Cap Change ${w.name}`, - bottom: [ - baseline({ - series: cohorts.utxo.all.realized.cap.delta.absolute[w.key].usd, - name: w.name, - unit: Unit.usd, - }), - ], - })), - ], - }, - { - name: "Growth Rate", - tree: [ - { - name: "Compare", - title: "Realized Cap Growth Rate", - bottom: ROLLING_WINDOWS.flatMap((w) => - percentRatio({ - pattern: cohorts.utxo.all.realized.cap.delta.rate[w.key], - name: w.name, - color: w.color, - }), - ), - }, - ...ROLLING_WINDOWS.map((w) => ({ - name: w.name, - title: `Realized Cap Growth Rate ${w.name}`, - bottom: percentRatioBaseline({ - pattern: cohorts.utxo.all.realized.cap.delta.rate[w.key], - name: w.name, - }), - })), - ], - }, + ...deltaTree({ + delta: cohorts.utxo.all.realized.cap.delta, + title: "Realized Cap", + unit: Unit.usd, + extract: (v) => v.usd, + }), ], }, { diff --git a/website/scripts/options/network.js b/website/scripts/options/network.js index 06ca25d6f..859201a31 100644 --- a/website/scripts/options/network.js +++ b/website/scripts/options/network.js @@ -12,18 +12,18 @@ import { fromSupplyPattern, chartsFromFullPerBlock, chartsFromCount, + chartsFromCountEntries, chartsFromSumPerBlock, rollingWindowsTree, - distributionWindowsTree, ROLLING_WINDOWS, chartsFromBlockAnd6b, - fromStatsPattern, + simpleDeltaTree, percentRatio, percentRatioDots, rollingPercentRatioTree, } from "./series.js"; -import { satsBtcUsd, satsBtcUsdFrom } from "./shared.js"; +import { satsBtcUsd, satsBtcUsdFrom, satsBtcUsdFullTree } from "./shared.js"; /** * Create Network section @@ -512,202 +512,97 @@ export function createNetworkSection() { unit: Unit.count, }), }, + { + name: "Volume", + tree: satsBtcUsdFullTree({ + pattern: transactions.volume.transferVolume, + name: "base", + title: "Transaction Volume", + }), + }, { name: "Fee Rate", - title: "Fee Rate Distribution (6 Blocks)", - bottom: fromStatsPattern({ pattern: transactions.fees.feeRate._6b, unit: Unit.feeRate }), + tree: chartsFromBlockAnd6b({ + pattern: transactions.fees.feeRate, + title: "Transaction Fee Rate", + unit: Unit.feeRate, + }), }, { name: "Fee", - title: "Fee Distribution (6 Blocks)", - bottom: fromStatsPattern({ pattern: transactions.fees.fee._6b, unit: Unit.sats }), + tree: chartsFromBlockAnd6b({ + pattern: transactions.fees.fee, + title: "Transaction Fee", + unit: Unit.sats, + }), }, { - name: "Volume", - tree: [ - { - name: "Transferred", - title: "Transaction Volume", - bottom: [ - ...satsBtcUsd({ - pattern: transactions.volume.transferVolume.base, - name: "Sent", - }), - ...satsBtcUsd({ - pattern: transactions.volume.outputVolume.base, - name: "Received", - color: colors.entity.output, - }), - ], - }, - { - name: "Sent Rolling", - tree: [ - { - name: "Compare", - title: "Sent Volume Rolling", - bottom: ROLLING_WINDOWS.flatMap((w) => - satsBtcUsd({ - pattern: transactions.volume.transferVolume.sum[w.key], - name: w.name, - color: w.color, - }), - ), - }, - ...ROLLING_WINDOWS.map((w) => ({ - name: w.name, - title: `Sent Volume ${w.name}`, - bottom: satsBtcUsd({ - pattern: transactions.volume.transferVolume.sum[w.key], - name: w.name, - color: w.color, - }), - })), - ], - }, - { - name: "Sent Cumulative", - title: "Sent Volume (Total)", - bottom: satsBtcUsd({ - pattern: transactions.volume.transferVolume.cumulative, - name: "all-time", - }), - }, - { - name: "Received Rolling", - tree: [ - { - name: "Compare", - title: "Received Volume Rolling", - bottom: ROLLING_WINDOWS.flatMap((w) => - satsBtcUsd({ - pattern: transactions.volume.outputVolume.sum[w.key], - name: w.name, - color: w.color, - }), - ), - }, - ...ROLLING_WINDOWS.map((w) => ({ - name: w.name, - title: `Received Volume ${w.name}`, - bottom: satsBtcUsd({ - pattern: transactions.volume.outputVolume.sum[w.key], - name: w.name, - color: w.color, - }), - })), - ], - }, - { - name: "Received Cumulative", - title: "Received Volume (Total)", - bottom: satsBtcUsd({ - pattern: transactions.volume.outputVolume.cumulative, - name: "all-time", - }), - }, - ], + name: "Weight", + tree: chartsFromBlockAnd6b({ + pattern: transactions.size.weight, + title: "Transaction Weight", + unit: Unit.wu, + }), }, { - name: "Size", - tree: [ - { - name: "Weight", - tree: chartsFromBlockAnd6b({ - pattern: transactions.size.weight, - title: "Transaction Weight", - unit: Unit.wu, - }), - }, - { - name: "vSize", - tree: chartsFromBlockAnd6b({ - pattern: transactions.size.vsize, - title: "Transaction vSize", - unit: Unit.vb, - }), - }, - ], + name: "vSize", + tree: chartsFromBlockAnd6b({ + pattern: transactions.size.vsize, + title: "Transaction vSize", + unit: Unit.vb, + }), }, { name: "Versions", - tree: [ - { - name: "Base", - title: "Transaction Versions", - bottom: entries(transactions.versions).map( - ([v, data], i, arr) => - line({ - series: data.base, - name: v, - color: colors.at(i, arr.length), - unit: Unit.count, - }), - ), - }, - { - name: "Rolling", - tree: [ - { - name: "Compare", - title: "Transaction Versions Rolling", - bottom: entries(transactions.versions).flatMap( - ([v, data], i, arr) => - ROLLING_WINDOWS.map((w) => - line({ - series: data.sum[w.key], - name: `${v} ${w.name}`, - color: colors.at(i, arr.length), - unit: Unit.count, - }), - ), - ), - }, - ...ROLLING_WINDOWS.map((w) => ({ - name: w.name, - title: `Transaction Versions (${w.name})`, - bottom: entries(transactions.versions).map( - ([v, data], i, arr) => - line({ - series: data.sum[w.key], - name: v, - color: colors.at(i, arr.length), - unit: Unit.count, - }), - ), - })), - ], - }, - { - name: "Cumulative", - title: "Transaction Versions (Total)", - bottom: entries(transactions.versions).map( - ([v, data], i, arr) => - line({ - series: data.cumulative, - name: v, - color: colors.at(i, arr.length), - unit: Unit.count, - }), - ), - }, - ], + tree: chartsFromCountEntries({ + entries: entries(transactions.versions), + title: "Transaction Versions", + unit: Unit.count, + }), }, { name: "Velocity", - 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, - }), + 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, + }), + ], + }, ], }, ], @@ -721,37 +616,45 @@ export function createNetworkSection() { name: "Count", tree: [ { - name: "Base", - title: "Block Count", - bottom: [ - line({ - series: blocks.count.total.base, - name: "base", - unit: Unit.count, - }), - line({ - series: blocks.count.target, - name: "Target", - color: colors.gray, - unit: Unit.count, - options: { lineStyle: 4 }, - }), + name: "Sums", + tree: [ + { + name: "Compare", + title: "Block Count Rolling", + bottom: ROLLING_WINDOWS.map((w) => + line({ + series: blocks.count.total.sum[w.key], + name: w.name, + color: w.color, + unit: Unit.count, + }), + ), + }, + ...ROLLING_WINDOWS.map((w) => ({ + name: w.name, + title: `Block Count ${w.name}`, + bottom: [ + line({ + series: blocks.count.total.sum[w.key], + name: "Actual", + unit: Unit.count, + }), + line({ + series: blocks.count.target[w.key], + name: "Target", + color: colors.gray, + unit: Unit.count, + options: { lineStyle: 4 }, + }), + ], + })), ], }, - rollingWindowsTree({ - windows: blocks.count.total.sum, - title: "Block Count", - unit: Unit.count, - }), { name: "Cumulative", title: "Block Count (Total)", bottom: [ - line({ - series: blocks.count.total.cumulative, - name: "all-time", - unit: Unit.count, - }), + { series: blocks.count.total.cumulative, title: "all-time", unit: Unit.count }, ], }, ], @@ -760,7 +663,7 @@ export function createNetworkSection() { name: "Interval", tree: [ { - name: "Base", + name: "Per Block", title: "Block Interval", bottom: [ dots({ @@ -780,155 +683,34 @@ export function createNetworkSection() { rollingWindowsTree({ windows: blocks.interval, title: "Block Interval", + name: "Averages", unit: Unit.secs, }), ], }, { name: "Size", - tree: [ - { - name: "Base", - title: "Block Size", - bottom: [ - line({ - series: blocks.size.base, - name: "base", - unit: Unit.bytes, - }), - ], - }, - rollingWindowsTree({ - windows: blocks.size.sum, - title: "Block Size", - unit: Unit.bytes, - }), - distributionWindowsTree({ - pattern: blocks.size, - base: blocks.size.base, - title: "Block Size", - unit: Unit.bytes, - }), - { - name: "Cumulative", - title: "Block Size (Total)", - bottom: [ - line({ - series: blocks.size.cumulative, - name: "all-time", - unit: Unit.bytes, - }), - ], - }, - ], - }, - { - name: "Weight", - tree: [ - { - name: "Base", - title: "Block Weight", - bottom: [ - line({ - series: blocks.weight.base, - name: "base", - unit: Unit.wu, - }), - ], - }, - rollingWindowsTree({ - windows: blocks.weight.sum, - title: "Block Weight", - unit: Unit.wu, - }), - distributionWindowsTree({ - pattern: blocks.weight, - base: blocks.weight.base, - title: "Block Weight", - unit: Unit.wu, - }), - { - name: "Cumulative", - title: "Block Weight (Total)", - bottom: [ - line({ - series: blocks.weight.cumulative, - name: "all-time", - unit: Unit.wu, - }), - ], - }, - ], - }, - { - name: "vBytes", - tree: [ - { - name: "Base", - title: "Block vBytes", - bottom: [ - line({ - series: blocks.vbytes.base, - name: "base", - unit: Unit.vb, - }), - ], - }, - rollingWindowsTree({ - windows: blocks.vbytes.sum, - title: "Block vBytes", - unit: Unit.vb, - }), - distributionWindowsTree({ - pattern: blocks.vbytes, - base: blocks.vbytes.base, - title: "Block vBytes", - unit: Unit.vb, - }), - { - name: "Cumulative", - title: "Block vBytes (Total)", - bottom: [ - line({ - series: blocks.vbytes.cumulative, - name: "all-time", - unit: Unit.vb, - }), - ], - }, - ], - }, - { - name: "Fullness", - title: "Block Fullness", - bottom: percentRatioDots({ - pattern: blocks.fullness, - name: "base", + tree: chartsFromFullPerBlock({ + pattern: blocks.size, + title: "Block Size", + unit: Unit.bytes, }), }, { - name: "Difficulty", - tree: [ - { - name: "Base", - title: "Mining Difficulty", - bottom: [ - line({ - series: blocks.difficulty.value, - name: "Difficulty", - unit: Unit.count, - }), - ], - }, - { - name: "Adjustment", - title: "Difficulty Adjustment", - bottom: percentRatioDots({ - pattern: blocks.difficulty.adjustment, - name: "Adjustment", - }), - }, - ], + name: "Weight", + tree: chartsFromFullPerBlock({ + pattern: blocks.weight, + title: "Block Weight", + unit: Unit.wu, + }), + }, + { + name: "vBytes", + tree: chartsFromFullPerBlock({ + pattern: blocks.vbytes, + title: "Block vBytes", + unit: Unit.vb, + }), }, ], }, @@ -948,18 +730,11 @@ export function createNetworkSection() { }), ], }, - { - name: "30d Change", - title: "UTXO Count 30d Change", - bottom: [ - baseline({ - series: - cohorts.utxo.all.outputs.unspentCount.delta.absolute._1m, - name: "30d Change", - unit: Unit.count, - }), - ], - }, + ...simpleDeltaTree({ + delta: cohorts.utxo.all.outputs.unspentCount.delta, + title: "UTXO Count", + unit: Unit.count, + }), { name: "Flow", title: "UTXO Flow", diff --git a/website/scripts/options/series.js b/website/scripts/options/series.js index 0c133bf08..de9b34ffa 100644 --- a/website/scripts/options/series.js +++ b/website/scripts/options/series.js @@ -476,12 +476,13 @@ export function statsAtWindow(pattern, window) { * @param {{ _24h: AnySeriesPattern, _1w: AnySeriesPattern, _1m: AnySeriesPattern, _1y: AnySeriesPattern }} args.windows * @param {string} args.title * @param {Unit} args.unit + * @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, series = line }) { +export function rollingWindowsTree({ windows, title, unit, name = "Sums", series = line }) { return { - name: "Sums", + name, tree: [ { name: "Compare", @@ -523,17 +524,26 @@ export function rollingWindowsTree({ windows, title, unit, series = line }) { export function distributionWindowsTree({ pattern, base, title, unit }) { return { name: "Distributions", - tree: ROLLING_WINDOWS.map((w) => ({ - name: w.name, - title: `${title} Distribution (${w.name})`, - bottom: [ - ...(base ? [line({ series: base, name: "base", unit })] : []), - ...fromStatsPattern({ - pattern: statsAtWindow(pattern, w.key), - unit, - }), - ], - })), + tree: [ + { + name: "Compare", + title: `${title} Average`, + bottom: ROLLING_WINDOWS.map((w) => + line({ series: pattern.average[w.key], name: w.name, color: w.color, unit }), + ), + }, + ...ROLLING_WINDOWS.map((w) => ({ + name: w.name, + title: `${title} Distribution (${w.name})`, + bottom: [ + ...(base ? [line({ series: base, name: "base", unit })] : []), + ...fromStatsPattern({ + pattern: statsAtWindow(pattern, w.key), + unit, + }), + ], + })), + ], }; } @@ -651,10 +661,10 @@ export function percentRatioDots({ pattern, name, color, defaultActive }) { * @param {boolean} [args.defaultActive] * @returns {AnyFetchedSeriesBlueprint[]} */ -export function percentRatioBaseline({ pattern, name, color, defaultActive }) { +export function percentRatioBaseline({ pattern, name, defaultActive }) { return [ - baseline({ series: pattern.percent, name, color, defaultActive, unit: Unit.percentage }), - baseline({ series: pattern.ratio, name, color, defaultActive, unit: Unit.ratio }), + baseline({ series: pattern.percent, name, defaultActive, unit: Unit.percentage }), + baseline({ series: pattern.ratio, name, defaultActive, unit: Unit.ratio }), ]; } @@ -663,29 +673,86 @@ export function percentRatioBaseline({ pattern, name, color, defaultActive }) { * @param {Object} args * @param {{ _24h: { percent: AnySeriesPattern, ratio: AnySeriesPattern }, _1w: { percent: AnySeriesPattern, ratio: AnySeriesPattern }, _1m: { percent: AnySeriesPattern, ratio: AnySeriesPattern }, _1y: { percent: AnySeriesPattern, ratio: AnySeriesPattern } }} args.windows * @param {string} args.title - * @param {(args: {pattern: { percent: AnySeriesPattern, ratio: AnySeriesPattern }, name: string, color: Color}) => AnyFetchedSeriesBlueprint[]} [args.series] + * @param {string} [args.name] + * @param {(args: {pattern: { percent: AnySeriesPattern, ratio: AnySeriesPattern }, name: string, color?: Color}) => AnyFetchedSeriesBlueprint[]} [args.series] * @returns {PartialOptionsGroup} */ -export function rollingPercentRatioTree({ windows, title, series = percentRatio }) { +export function rollingPercentRatioTree({ windows, title, name = "Sums", series = percentRatio }) { return { - name: "Sums", + name, tree: [ { name: "Compare", title: `${title} Rolling`, bottom: ROLLING_WINDOWS.flatMap((w) => - series({ pattern: windows[w.key], name: w.name, color: w.color }), + percentRatio({ pattern: windows[w.key], name: w.name, color: w.color }), ), }, ...ROLLING_WINDOWS.map((w) => ({ name: w.name, title: `${title} ${w.name}`, - bottom: series({ pattern: windows[w.key], name: w.name, color: w.color }), + bottom: series({ pattern: windows[w.key], name: w.name }), })), ], }; } +/** + * Create Change + Growth Rate tree from a delta pattern (absolute + rate) + * @template T + * @param {Object} args + * @param {{ absolute: { _24h: T, _1w: T, _1m: T, _1y: T }, rate: { _24h: { percent: AnySeriesPattern, ratio: AnySeriesPattern }, _1w: { percent: AnySeriesPattern, ratio: AnySeriesPattern }, _1m: { percent: AnySeriesPattern, ratio: AnySeriesPattern }, _1y: { percent: AnySeriesPattern, ratio: AnySeriesPattern } } }} args.delta + * @param {string} args.title + * @param {Unit} args.unit + * @param {(v: T) => AnySeriesPattern} args.extract + * @returns {PartialOptionsTree} + */ +export function deltaTree({ delta, title, unit, extract }) { + return [ + { + name: "Change", + tree: [ + { + name: "Compare", + title: `${title} Change`, + bottom: ROLLING_WINDOWS.map((w) => + baseline({ + series: extract(delta.absolute[w.key]), + name: w.name, + color: w.color, + unit, + }), + ), + }, + ...ROLLING_WINDOWS.map((w) => ({ + name: w.name, + title: `${title} Change ${w.name}`, + bottom: [ + baseline({ + series: extract(delta.absolute[w.key]), + name: w.name, + unit, + }), + ], + })), + ], + }, + rollingPercentRatioTree({ windows: delta.rate, title: `${title} Growth Rate`, name: "Growth Rate", series: percentRatioBaseline }), + ]; +} + +/** + * deltaTree where absolute windows are directly AnySeriesPattern (no extract needed) + * @param {Object} args + * @param {{ absolute: { _24h: AnySeriesPattern, _1w: AnySeriesPattern, _1m: AnySeriesPattern, _1y: AnySeriesPattern }, rate: { _24h: { percent: AnySeriesPattern, ratio: AnySeriesPattern }, _1w: { percent: AnySeriesPattern, ratio: AnySeriesPattern }, _1m: { percent: AnySeriesPattern, ratio: AnySeriesPattern }, _1y: { percent: AnySeriesPattern, ratio: AnySeriesPattern } } }} args.delta + * @param {string} args.title + * @param {Unit} args.unit + * @returns {PartialOptionsTree} + */ +export function simpleDeltaTree({ delta, title, unit }) { + return deltaTree({ delta, title, unit, extract: (v) => v }); +} + // ============================================================================ // Chart-generating helpers (return PartialOptionsTree for folder structures) // ============================================================================ @@ -896,20 +963,40 @@ export const chartsFromSumPerBlock = (args) => export function chartsFromBlockAnd6b({ pattern, title, unit }) { return [ { - name: "Per Block", - title: `${title} per Block`, + name: "Block", + title: `${title} (Block)`, bottom: fromStatsPattern({ pattern: pattern.block, unit }), }, { - name: "Per 6 Blocks", - title: `${title} per 6 Blocks`, + name: "Hourly", + title: `${title} (Hourly)`, bottom: fromStatsPattern({ pattern: pattern._6b, unit }), }, ]; } /** - * Split pattern with rolling sum windows + cumulative into charts + * Sums + Cumulative charts (no Per Block) + * @param {Object} args + * @param {{ sum: { _24h: AnySeriesPattern, _1w: AnySeriesPattern, _1m: AnySeriesPattern, _1y: AnySeriesPattern }, cumulative: AnySeriesPattern }} args.pattern + * @param {string} args.title + * @param {Unit} args.unit + * @param {Color} [args.color] + * @returns {PartialOptionsTree} + */ +export function chartsFromSumsCumulative({ pattern, title, unit, color }) { + return [ + rollingWindowsTree({ windows: pattern.sum, title, unit }), + { + name: "Cumulative", + title: `${title} (Total)`, + bottom: [{ series: pattern.cumulative, title: "all-time", color, unit }], + }, + ]; +} + +/** + * Per Block + Sums + Cumulative charts * @param {Object} args * @param {CountPattern} args.pattern * @param {string} args.title @@ -920,15 +1007,47 @@ export function chartsFromBlockAnd6b({ pattern, title, unit }) { export function chartsFromCount({ pattern, title, unit, color }) { return [ { - name: "Base", + name: "Per Block", title, bottom: [{ series: pattern.base, title: "base", color, unit }], }, - rollingWindowsTree({ windows: pattern.sum, title, unit }), + ...chartsFromSumsCumulative({ pattern, title, unit, color }), + ]; +} + +/** + * Split multiple named entries (each with base/sum/cumulative) into Per Block/Sums/Cumulative charts + * @param {Object} args + * @param {Array<[string, CountPattern]>} args.entries + * @param {string} args.title + * @param {Unit} args.unit + * @returns {PartialOptionsTree} + */ +export function chartsFromCountEntries({ entries, title, unit }) { + return [ + { + name: "Per Block", + title, + bottom: entries.map(([name, data], i, arr) => + line({ series: data.base, name, color: colors.at(i, arr.length), unit }), + ), + }, + { + name: "Sums", + tree: ROLLING_WINDOWS.map((w) => ({ + name: w.name, + title: `${title} (${w.name})`, + bottom: entries.map(([name, data], i, arr) => + line({ series: data.sum[w.key], name, color: colors.at(i, arr.length), unit }), + ), + })), + }, { name: "Cumulative", title: `${title} (Total)`, - bottom: [{ series: pattern.cumulative, title: "all-time", color, unit }], + bottom: entries.map(([name, data], i, arr) => + line({ series: data.cumulative, name, color: colors.at(i, arr.length), unit }), + ), }, ]; } diff --git a/website/scripts/options/shared.js b/website/scripts/options/shared.js index 98bb62567..b2f209a57 100644 --- a/website/scripts/options/shared.js +++ b/website/scripts/options/shared.js @@ -227,16 +227,16 @@ export function satsBtcUsdRolling({ pattern, name, color, defaultActive }) { export function satsBtcUsdFullTree({ pattern, name, title, color }) { return [ { - name: "Sum", + name: "Per Block", title, bottom: satsBtcUsd({ pattern: pattern.base, name, color }), }, { - name: "Rolling", + name: "Sums", tree: [ { name: "Compare", - title: `${title} Rolling Sum`, + title: `${title} Rolling`, bottom: ROLLING_WINDOWS.flatMap((w) => satsBtcUsd({ pattern: pattern.sum[w.key], @@ -247,7 +247,7 @@ export function satsBtcUsdFullTree({ pattern, name, title, color }) { }, ...ROLLING_WINDOWS.map((w) => ({ name: w.name, - title: `${title} ${w.name} Rolling Sum`, + title: `${title} (${w.name})`, bottom: satsBtcUsd({ pattern: pattern.sum[w.key], name: w.name,