diff --git a/crates/brk_client/src/lib.rs b/crates/brk_client/src/lib.rs index c5346129e..ab34796c5 100644 --- a/crates/brk_client/src/lib.rs +++ b/crates/brk_client/src/lib.rs @@ -1397,6 +1397,44 @@ impl RatioPattern { } } +/// Pattern struct for repeated tree structure. +pub struct GrossInvestedInvestorNegNetSupplyUnrealizedPattern { + pub gross_pnl: CentsUsdPattern, + pub invested_capital_in_loss: CentsUsdPattern, + pub invested_capital_in_loss_raw: MetricPattern18, + pub invested_capital_in_profit: CentsUsdPattern, + pub invested_capital_in_profit_raw: MetricPattern18, + pub investor_cap_in_loss_raw: MetricPattern18, + pub investor_cap_in_profit_raw: MetricPattern18, + pub neg_unrealized_loss: MetricPattern1, + pub net_unrealized_pnl: CentsUsdPattern, + pub supply_in_loss: BtcCentsSatsUsdPattern, + pub supply_in_profit: BtcCentsSatsUsdPattern, + pub unrealized_loss: CentsUsdPattern, + pub unrealized_profit: CentsUsdPattern, +} + +impl GrossInvestedInvestorNegNetSupplyUnrealizedPattern { + /// Create a new pattern node with accumulated metric name. + pub fn new(client: Arc, acc: String) -> Self { + Self { + gross_pnl: CentsUsdPattern::new(client.clone(), _m(&acc, "unrealized_gross_pnl")), + invested_capital_in_loss: CentsUsdPattern::new(client.clone(), _m(&acc, "invested_capital_in_loss")), + invested_capital_in_loss_raw: MetricPattern18::new(client.clone(), _m(&acc, "invested_capital_in_loss_raw")), + invested_capital_in_profit: CentsUsdPattern::new(client.clone(), _m(&acc, "invested_capital_in_profit")), + invested_capital_in_profit_raw: MetricPattern18::new(client.clone(), _m(&acc, "invested_capital_in_profit_raw")), + investor_cap_in_loss_raw: MetricPattern18::new(client.clone(), _m(&acc, "investor_cap_in_loss_raw")), + investor_cap_in_profit_raw: MetricPattern18::new(client.clone(), _m(&acc, "investor_cap_in_profit_raw")), + neg_unrealized_loss: MetricPattern1::new(client.clone(), _m(&acc, "neg_unrealized_loss")), + net_unrealized_pnl: CentsUsdPattern::new(client.clone(), _m(&acc, "net_unrealized_pnl")), + supply_in_loss: BtcCentsSatsUsdPattern::new(client.clone(), _m(&acc, "supply_in_loss")), + supply_in_profit: BtcCentsSatsUsdPattern::new(client.clone(), _m(&acc, "supply_in_profit")), + unrealized_loss: CentsUsdPattern::new(client.clone(), _m(&acc, "unrealized_loss")), + unrealized_profit: CentsUsdPattern::new(client.clone(), _m(&acc, "unrealized_profit")), + } + } +} + /// Pattern struct for repeated tree structure. pub struct _10y1m1w1y2y3m3y4y5y6m6y8yPattern2 { pub _10y: BpsPercentRatioPattern, @@ -1639,30 +1677,30 @@ impl AverageGainsLossesRsiStochPattern { /// Pattern struct for repeated tree structure. pub struct AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern { - pub all: ChangeCountPattern, - pub p2a: ChangeCountPattern, - pub p2pk33: ChangeCountPattern, - pub p2pk65: ChangeCountPattern, - pub p2pkh: ChangeCountPattern, - pub p2sh: ChangeCountPattern, - pub p2tr: ChangeCountPattern, - pub p2wpkh: ChangeCountPattern, - pub p2wsh: ChangeCountPattern, + pub all: MetricPattern1, + pub p2a: MetricPattern1, + pub p2pk33: MetricPattern1, + pub p2pk65: MetricPattern1, + pub p2pkh: MetricPattern1, + pub p2sh: MetricPattern1, + pub p2tr: MetricPattern1, + pub p2wpkh: MetricPattern1, + pub p2wsh: MetricPattern1, } impl AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern { /// Create a new pattern node with accumulated metric name. pub fn new(client: Arc, acc: String) -> Self { Self { - all: ChangeCountPattern::new(client.clone(), acc.clone()), - p2a: ChangeCountPattern::new(client.clone(), _p("p2a", &acc)), - p2pk33: ChangeCountPattern::new(client.clone(), _p("p2pk33", &acc)), - p2pk65: ChangeCountPattern::new(client.clone(), _p("p2pk65", &acc)), - p2pkh: ChangeCountPattern::new(client.clone(), _p("p2pkh", &acc)), - p2sh: ChangeCountPattern::new(client.clone(), _p("p2sh", &acc)), - p2tr: ChangeCountPattern::new(client.clone(), _p("p2tr", &acc)), - p2wpkh: ChangeCountPattern::new(client.clone(), _p("p2wpkh", &acc)), - p2wsh: ChangeCountPattern::new(client.clone(), _p("p2wsh", &acc)), + all: MetricPattern1::new(client.clone(), acc.clone()), + p2a: MetricPattern1::new(client.clone(), _p("p2a", &acc)), + p2pk33: MetricPattern1::new(client.clone(), _p("p2pk33", &acc)), + p2pk65: MetricPattern1::new(client.clone(), _p("p2pk65", &acc)), + p2pkh: MetricPattern1::new(client.clone(), _p("p2pkh", &acc)), + p2sh: MetricPattern1::new(client.clone(), _p("p2sh", &acc)), + p2tr: MetricPattern1::new(client.clone(), _p("p2tr", &acc)), + p2wpkh: MetricPattern1::new(client.clone(), _p("p2wpkh", &acc)), + p2wsh: MetricPattern1::new(client.clone(), _p("p2wsh", &acc)), } } } @@ -1697,36 +1735,6 @@ impl AverageMaxMedianMinPct10Pct25Pct75Pct90SumPattern2 { } } -/// Pattern struct for repeated tree structure. -pub struct AverageHeightMaxMedianMinPct10Pct25Pct75Pct90Pattern { - pub average: _1m1w1y24hPattern, - pub height: MetricPattern18, - pub max: _1m1w1y24hPattern, - pub median: _1m1w1y24hPattern, - pub min: _1m1w1y24hPattern, - pub pct10: _1m1w1y24hPattern, - pub pct25: _1m1w1y24hPattern, - pub pct75: _1m1w1y24hPattern, - pub pct90: _1m1w1y24hPattern, -} - -impl AverageHeightMaxMedianMinPct10Pct25Pct75Pct90Pattern { - /// Create a new pattern node with accumulated metric name. - pub fn new(client: Arc, acc: String) -> Self { - Self { - average: _1m1w1y24hPattern::new(client.clone(), _m(&acc, "average")), - height: MetricPattern18::new(client.clone(), acc.clone()), - max: _1m1w1y24hPattern::new(client.clone(), _m(&acc, "max")), - median: _1m1w1y24hPattern::new(client.clone(), _m(&acc, "median")), - min: _1m1w1y24hPattern::new(client.clone(), _m(&acc, "min")), - pct10: _1m1w1y24hPattern::new(client.clone(), _m(&acc, "p10")), - pct25: _1m1w1y24hPattern::new(client.clone(), _m(&acc, "p25")), - pct75: _1m1w1y24hPattern::new(client.clone(), _m(&acc, "p75")), - pct90: _1m1w1y24hPattern::new(client.clone(), _m(&acc, "p90")), - } - } -} - /// Pattern struct for repeated tree structure. pub struct AverageMaxMedianMinPct10Pct25Pct75Pct90SumPattern { pub average: _1m1w1y24hPattern, @@ -1875,7 +1883,7 @@ pub struct ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern { pub realized: MvrvNegNetRealizedSentSoprValuePattern, pub relative: InvestedNetNuplSupplyUnrealizedPattern, pub supply: ChangeHalvedTotalPattern, - pub unrealized: GreedGrossInvestedInvestorNegNetPainSupplyUnrealizedPattern, + pub unrealized: GrossInvestedInvestorNegNetSupplyUnrealizedPattern, } impl ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern { @@ -1888,7 +1896,7 @@ impl ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern { realized: MvrvNegNetRealizedSentSoprValuePattern::new(client.clone(), acc.clone()), relative: InvestedNetNuplSupplyUnrealizedPattern::new(client.clone(), acc.clone()), supply: ChangeHalvedTotalPattern::new(client.clone(), _m(&acc, "supply")), - unrealized: GreedGrossInvestedInvestorNegNetPainSupplyUnrealizedPattern::new(client.clone(), acc.clone()), + unrealized: GrossInvestedInvestorNegNetSupplyUnrealizedPattern::new(client.clone(), acc.clone()), } } } @@ -2277,24 +2285,6 @@ impl BaseCumulativeSumPattern { } } -/// Pattern struct for repeated tree structure. -pub struct BpsPercentRatioPattern3 { - pub bps: AverageHeightMaxMedianMinPct10Pct25Pct75Pct90Pattern, - pub percent: MetricPattern1, - pub ratio: MetricPattern1, -} - -impl BpsPercentRatioPattern3 { - /// Create a new pattern node with accumulated metric name. - pub fn new(client: Arc, acc: String) -> Self { - Self { - bps: AverageHeightMaxMedianMinPct10Pct25Pct75Pct90Pattern::new(client.clone(), _m(&acc, "bps")), - percent: MetricPattern1::new(client.clone(), acc.clone()), - ratio: MetricPattern1::new(client.clone(), _m(&acc, "ratio")), - } - } -} - /// Pattern struct for repeated tree structure. pub struct BpsPercentRatioPattern { pub bps: MetricPattern1, @@ -2518,17 +2508,17 @@ impl CentsUsdPattern { } /// Pattern struct for repeated tree structure. -pub struct ChangeCountPattern { - pub change_1m: MetricPattern1, - pub count: MetricPattern1, +pub struct ChangeRatePattern { + pub change: _1m1w1y24hPattern, + pub rate: _1m1w1y24hPattern2, } -impl ChangeCountPattern { +impl ChangeRatePattern { /// Create a new pattern node with accumulated metric name. pub fn new(client: Arc, acc: String) -> Self { Self { - change_1m: MetricPattern1::new(client.clone(), _m(&acc, "change_1m")), - count: MetricPattern1::new(client.clone(), acc.clone()), + change: _1m1w1y24hPattern::new(client.clone(), _m(&acc, "change")), + rate: _1m1w1y24hPattern2::new(client.clone(), _m(&acc, "rate")), } } } @@ -5409,9 +5399,9 @@ pub struct MetricsTree_Distribution { pub addr_count: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern, pub empty_addr_count: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern, pub address_activity: MetricsTree_Distribution_AddressActivity, - pub total_addr_count: MetricsTree_Distribution_TotalAddrCount, + pub total_addr_count: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern, pub new_addr_count: MetricsTree_Distribution_NewAddrCount, - pub growth_rate: MetricsTree_Distribution_GrowthRate, + pub delta: MetricsTree_Distribution_Delta, pub fundedaddressindex: MetricPattern34, pub emptyaddressindex: MetricPattern35, } @@ -5427,9 +5417,9 @@ impl MetricsTree_Distribution { addr_count: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern::new(client.clone(), "addr_count".to_string()), empty_addr_count: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern::new(client.clone(), "empty_addr_count".to_string()), address_activity: MetricsTree_Distribution_AddressActivity::new(client.clone(), format!("{base_path}_address_activity")), - total_addr_count: MetricsTree_Distribution_TotalAddrCount::new(client.clone(), format!("{base_path}_total_addr_count")), + total_addr_count: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern::new(client.clone(), "total_addr_count".to_string()), new_addr_count: MetricsTree_Distribution_NewAddrCount::new(client.clone(), format!("{base_path}_new_addr_count")), - growth_rate: MetricsTree_Distribution_GrowthRate::new(client.clone(), format!("{base_path}_growth_rate")), + delta: MetricsTree_Distribution_Delta::new(client.clone(), format!("{base_path}_delta")), fundedaddressindex: MetricPattern34::new(client.clone(), "fundedaddressindex".to_string()), emptyaddressindex: MetricPattern35::new(client.clone(), "emptyaddressindex".to_string()), } @@ -6194,35 +6184,6 @@ impl MetricsTree_Distribution_AddressActivity { } } -/// Metrics tree node. -pub struct MetricsTree_Distribution_TotalAddrCount { - pub all: MetricPattern1, - pub p2pk65: MetricPattern1, - pub p2pk33: MetricPattern1, - pub p2pkh: MetricPattern1, - pub p2sh: MetricPattern1, - pub p2wpkh: MetricPattern1, - pub p2wsh: MetricPattern1, - pub p2tr: MetricPattern1, - pub p2a: MetricPattern1, -} - -impl MetricsTree_Distribution_TotalAddrCount { - pub fn new(client: Arc, base_path: String) -> Self { - Self { - all: MetricPattern1::new(client.clone(), "total_addr_count".to_string()), - p2pk65: MetricPattern1::new(client.clone(), "p2pk65_total_addr_count".to_string()), - p2pk33: MetricPattern1::new(client.clone(), "p2pk33_total_addr_count".to_string()), - p2pkh: MetricPattern1::new(client.clone(), "p2pkh_total_addr_count".to_string()), - p2sh: MetricPattern1::new(client.clone(), "p2sh_total_addr_count".to_string()), - p2wpkh: MetricPattern1::new(client.clone(), "p2wpkh_total_addr_count".to_string()), - p2wsh: MetricPattern1::new(client.clone(), "p2wsh_total_addr_count".to_string()), - p2tr: MetricPattern1::new(client.clone(), "p2tr_total_addr_count".to_string()), - p2a: MetricPattern1::new(client.clone(), "p2a_total_addr_count".to_string()), - } - } -} - /// Metrics tree node. pub struct MetricsTree_Distribution_NewAddrCount { pub all: AverageCumulativeHeightMaxMedianMinPct10Pct25Pct75Pct90SumPattern, @@ -6253,30 +6214,30 @@ impl MetricsTree_Distribution_NewAddrCount { } /// Metrics tree node. -pub struct MetricsTree_Distribution_GrowthRate { - pub all: BpsPercentRatioPattern3, - pub p2pk65: BpsPercentRatioPattern3, - pub p2pk33: BpsPercentRatioPattern3, - pub p2pkh: BpsPercentRatioPattern3, - pub p2sh: BpsPercentRatioPattern3, - pub p2wpkh: BpsPercentRatioPattern3, - pub p2wsh: BpsPercentRatioPattern3, - pub p2tr: BpsPercentRatioPattern3, - pub p2a: BpsPercentRatioPattern3, +pub struct MetricsTree_Distribution_Delta { + pub all: ChangeRatePattern, + pub p2pk65: ChangeRatePattern, + pub p2pk33: ChangeRatePattern, + pub p2pkh: ChangeRatePattern, + pub p2sh: ChangeRatePattern, + pub p2wpkh: ChangeRatePattern, + pub p2wsh: ChangeRatePattern, + pub p2tr: ChangeRatePattern, + pub p2a: ChangeRatePattern, } -impl MetricsTree_Distribution_GrowthRate { +impl MetricsTree_Distribution_Delta { pub fn new(client: Arc, base_path: String) -> Self { Self { - all: BpsPercentRatioPattern3::new(client.clone(), "growth_rate".to_string()), - p2pk65: BpsPercentRatioPattern3::new(client.clone(), "p2pk65_growth_rate".to_string()), - p2pk33: BpsPercentRatioPattern3::new(client.clone(), "p2pk33_growth_rate".to_string()), - p2pkh: BpsPercentRatioPattern3::new(client.clone(), "p2pkh_growth_rate".to_string()), - p2sh: BpsPercentRatioPattern3::new(client.clone(), "p2sh_growth_rate".to_string()), - p2wpkh: BpsPercentRatioPattern3::new(client.clone(), "p2wpkh_growth_rate".to_string()), - p2wsh: BpsPercentRatioPattern3::new(client.clone(), "p2wsh_growth_rate".to_string()), - p2tr: BpsPercentRatioPattern3::new(client.clone(), "p2tr_growth_rate".to_string()), - p2a: BpsPercentRatioPattern3::new(client.clone(), "p2a_growth_rate".to_string()), + all: ChangeRatePattern::new(client.clone(), "addr_count".to_string()), + p2pk65: ChangeRatePattern::new(client.clone(), "p2pk65_addr_count".to_string()), + p2pk33: ChangeRatePattern::new(client.clone(), "p2pk33_addr_count".to_string()), + p2pkh: ChangeRatePattern::new(client.clone(), "p2pkh_addr_count".to_string()), + p2sh: ChangeRatePattern::new(client.clone(), "p2sh_addr_count".to_string()), + p2wpkh: ChangeRatePattern::new(client.clone(), "p2wpkh_addr_count".to_string()), + p2wsh: ChangeRatePattern::new(client.clone(), "p2wsh_addr_count".to_string()), + p2tr: ChangeRatePattern::new(client.clone(), "p2tr_addr_count".to_string()), + p2a: ChangeRatePattern::new(client.clone(), "p2a_addr_count".to_string()), } } } diff --git a/crates/brk_computer/src/distribution/address/address_count.rs b/crates/brk_computer/src/distribution/address/address_count.rs index ceb88d680..3fa8a6f6c 100644 --- a/crates/brk_computer/src/distribution/address/address_count.rs +++ b/crates/brk_computer/src/distribution/address/address_count.rs @@ -1,7 +1,7 @@ use brk_cohort::ByAddressType; use brk_error::Result; use brk_traversable::Traversable; -use brk_types::{Height, Indexes, StoredF64, StoredU64, Version}; +use brk_types::{Height, Indexes, StoredU64, Version}; use derive_more::{Deref, DerefMut}; use rayon::prelude::*; use vecdb::{ @@ -9,15 +9,12 @@ use vecdb::{ WritableVec, }; -use crate::{blocks, indexes, internal::ComputedFromHeight}; +use crate::{indexes, internal::ComputedFromHeight}; -/// Address count with 1m change metric for a single type. -#[derive(Traversable)] -pub struct AddrCountVecs { - #[traversable(flatten)] - pub count: ComputedFromHeight, - pub change_1m: ComputedFromHeight, -} +#[derive(Deref, DerefMut, Traversable)] +pub struct AddrCountVecs( + #[traversable(flatten)] pub ComputedFromHeight, +); impl AddrCountVecs { pub(crate) fn forced_import( @@ -26,31 +23,9 @@ impl AddrCountVecs { version: Version, indexes: &indexes::Vecs, ) -> Result { - Ok(Self { - count: ComputedFromHeight::forced_import(db, name, version, indexes)?, - change_1m: ComputedFromHeight::forced_import( - db, - &format!("{name}_change_1m"), - version, - indexes, - )?, - }) - } - - pub(crate) fn compute_rest( - &mut self, - blocks: &blocks::Vecs, - starting_indexes: &Indexes, - exit: &Exit, - ) -> Result<()> { - self.change_1m.height.compute_rolling_change( - starting_indexes.height, - &blocks.count.height_1m_ago, - &self.count.height, - exit, - )?; - - Ok(()) + Ok(Self(ComputedFromHeight::forced_import( + db, name, version, indexes, + )?)) } } @@ -72,56 +47,48 @@ impl From<(&AddressTypeToAddrCountVecs, Height)> for AddressTypeToAddressCount { Self(ByAddressType { p2pk65: groups .p2pk65 - .count .height .collect_one(prev_height) .unwrap() .into(), p2pk33: groups .p2pk33 - .count .height .collect_one(prev_height) .unwrap() .into(), p2pkh: groups .p2pkh - .count .height .collect_one(prev_height) .unwrap() .into(), p2sh: groups .p2sh - .count .height .collect_one(prev_height) .unwrap() .into(), p2wpkh: groups .p2wpkh - .count .height .collect_one(prev_height) .unwrap() .into(), p2wsh: groups .p2wsh - .count .height .collect_one(prev_height) .unwrap() .into(), p2tr: groups .p2tr - .count .height .collect_one(prev_height) .unwrap() .into(), p2a: groups .p2a - .count .height .collect_one(prev_height) .unwrap() @@ -133,7 +100,7 @@ impl From<(&AddressTypeToAddrCountVecs, Height)> for AddressTypeToAddressCount { } } -/// Address count per address type, with height + derived indexes + 1m change. +/// Address count per address type, with height + derived indexes. #[derive(Deref, DerefMut, Traversable)] pub struct AddressTypeToAddrCountVecs(ByAddressType>); @@ -159,7 +126,7 @@ impl AddressTypeToAddrCountVecs { } pub(crate) fn min_stateful_height(&self) -> usize { - self.0.values().map(|v| v.count.height.len()).min().unwrap() + self.0.values().map(|v| v.height.len()).min().unwrap() } pub(crate) fn par_iter_height_mut( @@ -167,7 +134,7 @@ impl AddressTypeToAddrCountVecs { ) -> impl ParallelIterator { self.0 .par_values_mut() - .map(|v| &mut v.count.height as &mut dyn AnyStoredVec) + .map(|v| &mut v.height as &mut dyn AnyStoredVec) } pub(crate) fn truncate_push_height( @@ -176,7 +143,7 @@ impl AddressTypeToAddrCountVecs { addr_counts: &AddressTypeToAddressCount, ) -> Result<()> { for (vecs, &count) in self.0.values_mut().zip(addr_counts.values()) { - vecs.count.height.truncate_push(height, count.into())?; + vecs.height.truncate_push(height, count.into())?; } Ok(()) } @@ -184,25 +151,13 @@ impl AddressTypeToAddrCountVecs { pub(crate) fn reset_height(&mut self) -> Result<()> { use vecdb::WritableVec; for v in self.0.values_mut() { - v.count.height.reset()?; - } - Ok(()) - } - - pub(crate) fn compute_rest( - &mut self, - blocks: &blocks::Vecs, - starting_indexes: &Indexes, - exit: &Exit, - ) -> Result<()> { - for v in self.0.values_mut() { - v.compute_rest(blocks, starting_indexes, exit)?; + v.height.reset()?; } Ok(()) } pub(crate) fn by_height(&self) -> Vec<&EagerVec>> { - self.0.values().map(|v| &v.count.height).collect() + self.0.values().map(|v| &v.height).collect() } } @@ -227,22 +182,18 @@ impl AddrCountsVecs { } pub(crate) fn min_stateful_height(&self) -> usize { - self.all - .count - .height - .len() - .min(self.by_addresstype.min_stateful_height()) + self.all.height.len().min(self.by_addresstype.min_stateful_height()) } pub(crate) fn par_iter_height_mut( &mut self, ) -> impl ParallelIterator { - rayon::iter::once(&mut self.all.count.height as &mut dyn AnyStoredVec) + rayon::iter::once(&mut self.all.height as &mut dyn AnyStoredVec) .chain(self.by_addresstype.par_iter_height_mut()) } pub(crate) fn reset_height(&mut self) -> Result<()> { - self.all.count.height.reset()?; + self.all.height.reset()?; self.by_addresstype.reset_height()?; Ok(()) } @@ -253,7 +204,7 @@ impl AddrCountsVecs { total: u64, addr_counts: &AddressTypeToAddressCount, ) -> Result<()> { - self.all.count.height.truncate_push(height, total.into())?; + self.all.height.truncate_push(height, total.into())?; self.by_addresstype .truncate_push_height(height, addr_counts)?; Ok(()) @@ -261,26 +212,13 @@ impl AddrCountsVecs { pub(crate) fn compute_rest( &mut self, - blocks: &blocks::Vecs, starting_indexes: &Indexes, exit: &Exit, ) -> Result<()> { - self.by_addresstype - .compute_rest(blocks, starting_indexes, exit)?; - let sources = self.by_addresstype.by_height(); self.all - .count .height .compute_sum_of_others(starting_indexes.height, &sources, exit)?; - - self.all.change_1m.height.compute_rolling_change( - starting_indexes.height, - &blocks.count.height_1m_ago, - &self.all.count.height, - exit, - )?; - Ok(()) } } diff --git a/crates/brk_computer/src/distribution/address/delta.rs b/crates/brk_computer/src/distribution/address/delta.rs new file mode 100644 index 000000000..028f4330a --- /dev/null +++ b/crates/brk_computer/src/distribution/address/delta.rs @@ -0,0 +1,61 @@ +use brk_cohort::ByAddressType; +use brk_error::Result; +use brk_traversable::Traversable; +use brk_types::{Height, StoredI64, StoredU64, Version}; +use vecdb::{Database, Exit, Rw, StorageMode}; + +use crate::{ + indexes, + internal::{WindowStarts, RollingDelta}, +}; + +use super::AddrCountsVecs; + +#[derive(Traversable)] +pub struct DeltaVecs { + pub all: RollingDelta, + #[traversable(flatten)] + pub by_addresstype: ByAddressType>, +} + +impl DeltaVecs { + pub(crate) fn forced_import( + db: &Database, + version: Version, + indexes: &indexes::Vecs, + ) -> Result { + let version = version + Version::ONE; + + let all = RollingDelta::forced_import(db, "addr_count", version, indexes)?; + + let by_addresstype = ByAddressType::new_with_name(|name| { + RollingDelta::forced_import(db, &format!("{name}_addr_count"), version, indexes) + })?; + + Ok(Self { + all, + by_addresstype, + }) + } + + pub(crate) fn compute( + &mut self, + max_from: Height, + windows: &WindowStarts<'_>, + addr_count: &AddrCountsVecs, + exit: &Exit, + ) -> Result<()> { + self.all + .compute(max_from, windows, &addr_count.all.height, exit)?; + + for ((_, growth), (_, addr)) in self + .by_addresstype + .iter_mut() + .zip(addr_count.by_addresstype.iter()) + { + growth.compute(max_from, windows, &addr.height, exit)?; + } + + Ok(()) + } +} diff --git a/crates/brk_computer/src/distribution/address/growth_rate.rs b/crates/brk_computer/src/distribution/address/growth_rate.rs deleted file mode 100644 index f049895f2..000000000 --- a/crates/brk_computer/src/distribution/address/growth_rate.rs +++ /dev/null @@ -1,99 +0,0 @@ -use brk_cohort::ByAddressType; -use brk_error::Result; -use brk_traversable::Traversable; -use brk_types::{BasisPoints16, Height, StoredU64, Version}; -use vecdb::{Database, EagerVec, Exit, PcoVec, ReadableVec, Rw, StorageMode}; - -use crate::{ - indexes, - internal::{PercentFromHeightDistribution, WindowStarts}, -}; - -use super::{AddrCountsVecs, NewAddrCountVecs}; - -/// Growth rate: new_addr_count / addr_count (global + per-type) -#[derive(Traversable)] -pub struct GrowthRateVecs { - pub all: PercentFromHeightDistribution, - #[traversable(flatten)] - pub by_addresstype: ByAddressType>, -} - -impl GrowthRateVecs { - pub(crate) fn forced_import( - db: &Database, - version: Version, - indexes: &indexes::Vecs, - ) -> Result { - let all = - PercentFromHeightDistribution::forced_import(db, "growth_rate", version, indexes)?; - - let by_addresstype = ByAddressType::new_with_name(|name| { - PercentFromHeightDistribution::forced_import( - db, - &format!("{name}_growth_rate"), - version, - indexes, - ) - })?; - - Ok(Self { - all, - by_addresstype, - }) - } - - pub(crate) fn compute( - &mut self, - max_from: Height, - windows: &WindowStarts<'_>, - new_addr_count: &NewAddrCountVecs, - addr_count: &AddrCountsVecs, - exit: &Exit, - ) -> Result<()> { - self.all.compute(max_from, windows, exit, |target| { - compute_ratio( - target, - max_from, - &new_addr_count.all.height, - &addr_count.all.count.height, - exit, - ) - })?; - - for ((_, growth), ((_, new), (_, addr))) in self.by_addresstype.iter_mut().zip( - new_addr_count - .by_addresstype - .iter() - .zip(addr_count.by_addresstype.iter()), - ) { - growth.compute(max_from, windows, exit, |target| { - compute_ratio(target, max_from, &new.height, &addr.count.height, exit) - })?; - } - - Ok(()) - } -} - -fn compute_ratio( - target: &mut EagerVec>, - max_from: Height, - numerator: &impl ReadableVec, - denominator: &impl ReadableVec, - exit: &Exit, -) -> Result<()> { - target.compute_transform2( - max_from, - numerator, - denominator, - |(h, num, den, ..)| { - let n = *num as f64; - let d = *den as f64; - let ratio = if d == 0.0 { 0.0 } else { n / d }; - (h, BasisPoints16::from(ratio)) - }, - exit, - )?; - Ok(()) -} diff --git a/crates/brk_computer/src/distribution/address/mod.rs b/crates/brk_computer/src/distribution/address/mod.rs index b9c9972a9..1e43ae339 100644 --- a/crates/brk_computer/src/distribution/address/mod.rs +++ b/crates/brk_computer/src/distribution/address/mod.rs @@ -1,7 +1,7 @@ mod activity; mod address_count; mod data; -mod growth_rate; +mod delta; mod indexes; mod new_addr_count; mod total_addr_count; @@ -10,7 +10,7 @@ mod type_map; pub use activity::{AddressActivityVecs, AddressTypeToActivityCounts}; pub use address_count::{AddrCountsVecs, AddressTypeToAddressCount}; pub use data::AddressesDataVecs; -pub use growth_rate::GrowthRateVecs; +pub use delta::DeltaVecs; pub use indexes::AnyAddressIndexesVecs; pub use new_addr_count::NewAddrCountVecs; pub use total_addr_count::TotalAddrCountVecs; diff --git a/crates/brk_computer/src/distribution/address/total_addr_count.rs b/crates/brk_computer/src/distribution/address/total_addr_count.rs index cfc4d30fa..d6c7c9c89 100644 --- a/crates/brk_computer/src/distribution/address/total_addr_count.rs +++ b/crates/brk_computer/src/distribution/address/total_addr_count.rs @@ -50,8 +50,8 @@ impl TotalAddrCountVecs { ) -> Result<()> { self.all.height.compute_add( max_from, - &addr_count.all.count.height, - &empty_addr_count.all.count.height, + &addr_count.all.height, + &empty_addr_count.all.height, exit, )?; @@ -63,7 +63,7 @@ impl TotalAddrCountVecs { ) { total .height - .compute_add(max_from, &addr.count.height, &empty.count.height, exit)?; + .compute_add(max_from, &addr.height, &empty.height, exit)?; } Ok(()) diff --git a/crates/brk_computer/src/distribution/cohorts/utxo/groups.rs b/crates/brk_computer/src/distribution/cohorts/utxo/groups.rs index 2167ed55d..1963871f7 100644 --- a/crates/brk_computer/src/distribution/cohorts/utxo/groups.rs +++ b/crates/brk_computer/src/distribution/cohorts/utxo/groups.rs @@ -378,46 +378,6 @@ impl UTXOCohorts { .try_for_each(|v| v.compute_rest_part1(blocks, prices, starting_indexes, exit))?; } - // 2. Compute net_sentiment.height for aggregate cohorts (weighted average). - // Separate cohorts already computed net_sentiment in step 1 (inside compute_rest_part1). - // Note: min_age, max_age, epoch, class are Core tier — no net_sentiment. - // Note: ge_amount, lt_amount, amount_range are Minimal tier — no net_sentiment. - { - let Self { - all, - sth, - lth, - age_range, - .. - } = self; - - let ar = &*age_range; - let si = starting_indexes; - - let tasks: Vec Result<()> + Send + '_>> = vec![ - Box::new(|| { - let sources = filter_sources_from(ar.iter(), None); - all.metrics - .compute_net_sentiment_from_others(si, &sources, exit) - }), - Box::new(|| { - let sources = filter_sources_from(ar.iter(), Some(sth.metrics.filter())); - sth.metrics - .compute_net_sentiment_from_others(si, &sources, exit) - }), - Box::new(|| { - let sources = filter_sources_from(ar.iter(), Some(lth.metrics.filter())); - lth.metrics - .compute_net_sentiment_from_others(si, &sources, exit) - }), - ]; - - tasks - .into_par_iter() - .map(|f| f()) - .collect::>>()?; - } - Ok(()) } diff --git a/crates/brk_computer/src/distribution/metrics/activity/full.rs b/crates/brk_computer/src/distribution/metrics/activity/full.rs index db58e09c3..e4ee15f53 100644 --- a/crates/brk_computer/src/distribution/metrics/activity/full.rs +++ b/crates/brk_computer/src/distribution/metrics/activity/full.rs @@ -4,7 +4,7 @@ use brk_types::{Bitcoin, Height, Indexes, Sats, StoredF64, Version}; use derive_more::{Deref, DerefMut}; use vecdb::{AnyStoredVec, AnyVec, Exit, Rw, StorageMode, WritableVec}; -use crate::internal::ComputedFromHeightCumulativeSum; +use crate::internal::{ComputedFromHeightCumulative, ComputedFromHeightCumulativeSum}; use crate::{blocks, distribution::metrics::ImportConfig, prices}; @@ -17,7 +17,7 @@ pub struct ActivityFull { #[traversable(flatten)] pub base: ActivityBase, - pub coinblocks_destroyed: ComputedFromHeightCumulativeSum, + pub coinblocks_destroyed: ComputedFromHeightCumulative, pub coindays_destroyed: ComputedFromHeightCumulativeSum, } @@ -94,11 +94,10 @@ impl ActivityFull { self.base .compute_rest_part1(blocks, prices, starting_indexes, exit)?; - let window_starts = blocks.count.window_starts(); - self.coinblocks_destroyed - .compute_rest(starting_indexes.height, &window_starts, exit)?; + .compute_rest(starting_indexes.height, exit)?; + let window_starts = blocks.count.window_starts(); self.coindays_destroyed .compute_rest(starting_indexes.height, &window_starts, exit)?; diff --git a/crates/brk_computer/src/distribution/metrics/cohort/all.rs b/crates/brk_computer/src/distribution/metrics/cohort/all.rs index 1ed0045a7..c2a86f3b1 100644 --- a/crates/brk_computer/src/distribution/metrics/cohort/all.rs +++ b/crates/brk_computer/src/distribution/metrics/cohort/all.rs @@ -35,6 +35,7 @@ pub struct AllCohortMetrics { impl CohortMetricsBase for AllCohortMetrics { type RealizedVecs = RealizedFull; + type UnrealizedVecs = UnrealizedFull; type CostBasisVecs = CostBasisWithExtended; impl_cohort_accessors!(); diff --git a/crates/brk_computer/src/distribution/metrics/cohort/basic.rs b/crates/brk_computer/src/distribution/metrics/cohort/basic.rs index 66a417cc1..564506357 100644 --- a/crates/brk_computer/src/distribution/metrics/cohort/basic.rs +++ b/crates/brk_computer/src/distribution/metrics/cohort/basic.rs @@ -8,7 +8,7 @@ use crate::{blocks, prices}; use crate::distribution::metrics::{ ActivityFull, CohortMetricsBase, CostBasisBase, ImportConfig, OutputsMetrics, RealizedBase, - RelativeWithRelToAll, SupplyMetrics, UnrealizedFull, + RelativeWithRelToAll, SupplyMetrics, UnrealizedBase, }; /// Basic cohort metrics: no extensions, with relative (rel_to_all). @@ -22,12 +22,13 @@ pub struct BasicCohortMetrics { pub activity: Box>, pub realized: Box>, pub cost_basis: Box>, - pub unrealized: Box>, + pub unrealized: Box>, pub relative: Box>, } impl CohortMetricsBase for BasicCohortMetrics { type RealizedVecs = RealizedBase; + type UnrealizedVecs = UnrealizedBase; type CostBasisVecs = CostBasisBase; impl_cohort_accessors!(); @@ -47,7 +48,7 @@ impl CohortMetricsBase for BasicCohortMetrics { impl BasicCohortMetrics { pub(crate) fn forced_import(cfg: &ImportConfig) -> Result { let supply = SupplyMetrics::forced_import(cfg)?; - let unrealized = UnrealizedFull::forced_import(cfg)?; + let unrealized = UnrealizedBase::forced_import(cfg)?; let realized = RealizedBase::forced_import(cfg)?; let relative = RelativeWithRelToAll::forced_import(cfg)?; diff --git a/crates/brk_computer/src/distribution/metrics/cohort/core.rs b/crates/brk_computer/src/distribution/metrics/cohort/core.rs index 8f935460d..2b7cc3be9 100644 --- a/crates/brk_computer/src/distribution/metrics/cohort/core.rs +++ b/crates/brk_computer/src/distribution/metrics/cohort/core.rs @@ -8,7 +8,7 @@ use crate::{blocks, prices}; use crate::distribution::metrics::{ ActivityBase, CohortMetricsBase, RealizedBase, ImportConfig, OutputsMetrics, - RelativeBaseWithRelToAll, SupplyMetrics, UnrealizedBase, + RelativeBaseWithRelToAll, SupplyMetrics, UnrealizedCore, }; #[derive(Traversable)] @@ -19,7 +19,7 @@ pub struct CoreCohortMetrics { pub outputs: Box>, pub activity: Box>, pub realized: Box>, - pub unrealized: Box>, + pub unrealized: Box>, pub relative: Box>, } @@ -31,7 +31,7 @@ impl CoreCohortMetrics { outputs: Box::new(OutputsMetrics::forced_import(cfg)?), activity: Box::new(ActivityBase::forced_import(cfg)?), realized: Box::new(RealizedBase::forced_import(cfg)?), - unrealized: Box::new(UnrealizedBase::forced_import(cfg)?), + unrealized: Box::new(UnrealizedCore::forced_import(cfg)?), relative: Box::new(RelativeBaseWithRelToAll::forced_import(cfg)?), }) } @@ -90,7 +90,7 @@ impl CoreCohortMetrics { )?; self.unrealized.compute_from_stateful( starting_indexes, - &others.iter().map(|v| &v.unrealized_full().base).collect::>(), + &others.iter().map(|v| &v.unrealized_base().core).collect::>(), exit, )?; diff --git a/crates/brk_computer/src/distribution/metrics/cohort/extended.rs b/crates/brk_computer/src/distribution/metrics/cohort/extended.rs index 3582d1018..d99808372 100644 --- a/crates/brk_computer/src/distribution/metrics/cohort/extended.rs +++ b/crates/brk_computer/src/distribution/metrics/cohort/extended.rs @@ -33,6 +33,7 @@ pub struct ExtendedCohortMetrics { impl CohortMetricsBase for ExtendedCohortMetrics { type RealizedVecs = RealizedFull; + type UnrealizedVecs = UnrealizedFull; type CostBasisVecs = CostBasisWithExtended; impl_cohort_accessors!(); diff --git a/crates/brk_computer/src/distribution/metrics/cohort/extended_adjusted.rs b/crates/brk_computer/src/distribution/metrics/cohort/extended_adjusted.rs index cf752afac..249ec3121 100644 --- a/crates/brk_computer/src/distribution/metrics/cohort/extended_adjusted.rs +++ b/crates/brk_computer/src/distribution/metrics/cohort/extended_adjusted.rs @@ -8,6 +8,7 @@ use crate::{blocks, prices}; use crate::distribution::metrics::{ CohortMetricsBase, CostBasisWithExtended, ImportConfig, RealizedAdjusted, RealizedFull, + UnrealizedFull, }; use super::ExtendedCohortMetrics; @@ -27,6 +28,7 @@ pub struct ExtendedAdjustedCohortMetrics { impl CohortMetricsBase for ExtendedAdjustedCohortMetrics { type RealizedVecs = RealizedFull; + type UnrealizedVecs = UnrealizedFull; type CostBasisVecs = CostBasisWithExtended; impl_cohort_accessors!(); diff --git a/crates/brk_computer/src/distribution/metrics/mod.rs b/crates/brk_computer/src/distribution/metrics/mod.rs index 87ae46fdf..98473bf71 100644 --- a/crates/brk_computer/src/distribution/metrics/mod.rs +++ b/crates/brk_computer/src/distribution/metrics/mod.rs @@ -27,8 +27,8 @@ macro_rules! impl_cohort_accessors { fn activity_mut(&mut self) -> &mut $crate::distribution::metrics::ActivityFull { &mut self.activity } fn realized(&self) -> &Self::RealizedVecs { &self.realized } fn realized_mut(&mut self) -> &mut Self::RealizedVecs { &mut self.realized } - fn unrealized_full(&self) -> &$crate::distribution::metrics::UnrealizedFull { &self.unrealized } - fn unrealized_full_mut(&mut self) -> &mut $crate::distribution::metrics::UnrealizedFull { &mut self.unrealized } + fn unrealized(&self) -> &Self::UnrealizedVecs { &self.unrealized } + fn unrealized_mut(&mut self) -> &mut Self::UnrealizedVecs { &mut self.unrealized } fn cost_basis(&self) -> &Self::CostBasisVecs { &self.cost_basis } fn cost_basis_mut(&mut self) -> &mut Self::CostBasisVecs { &mut self.cost_basis } }; @@ -58,7 +58,7 @@ pub use relative::{ RelativeBaseWithRelToAll, RelativeForAll, RelativeWithExtended, RelativeWithRelToAll, }; pub use supply::SupplyMetrics; -pub use unrealized::{UnrealizedBase, UnrealizedFull}; +pub use unrealized::{UnrealizedBase, UnrealizedCore, UnrealizedFull, UnrealizedLike}; use brk_cohort::Filter; use brk_error::Result; @@ -92,6 +92,7 @@ impl CohortMetricsState for AllCohortMetrics { pub trait CohortMetricsBase: CohortMetricsState + Send + Sync { type RealizedVecs: RealizedLike; + type UnrealizedVecs: UnrealizedLike; type CostBasisVecs: CostBasisLike; fn filter(&self) -> &Filter; @@ -103,8 +104,8 @@ pub trait CohortMetricsBase: CohortMetricsState + Send fn activity_mut(&mut self) -> &mut ActivityFull; fn realized(&self) -> &Self::RealizedVecs; fn realized_mut(&mut self) -> &mut Self::RealizedVecs; - fn unrealized_full(&self) -> &UnrealizedFull; - fn unrealized_full_mut(&mut self) -> &mut UnrealizedFull; + fn unrealized(&self) -> &Self::UnrealizedVecs; + fn unrealized_mut(&mut self) -> &mut Self::UnrealizedVecs; fn cost_basis(&self) -> &Self::CostBasisVecs; fn cost_basis_mut(&mut self) -> &mut Self::CostBasisVecs; @@ -112,6 +113,10 @@ pub trait CohortMetricsBase: CohortMetricsState + Send fn realized_base(&self) -> &RealizedBase { self.realized().as_base() } fn realized_base_mut(&mut self) -> &mut RealizedBase { self.realized_mut().as_base_mut() } + /// Convenience: access unrealized as `&UnrealizedBase` (via `UnrealizedLike::as_base`). + fn unrealized_base(&self) -> &UnrealizedBase { self.unrealized().as_base() } + fn unrealized_base_mut(&mut self) -> &mut UnrealizedBase { self.unrealized_mut().as_base_mut() } + /// Convenience: access cost basis as `&CostBasisBase` (via `CostBasisLike::as_base`). fn cost_basis_base(&self) -> &CostBasisBase { self.cost_basis().as_base() } fn cost_basis_base_mut(&mut self) -> &mut CostBasisBase { self.cost_basis_mut().as_base_mut() } @@ -134,7 +139,7 @@ pub trait CohortMetricsBase: CohortMetricsState + Send self.cost_basis_base_mut() .truncate_push_minmax(height, state)?; let unrealized_state = state.compute_unrealized_state(height_price); - self.unrealized_full_mut() + self.unrealized_mut() .truncate_push(height, &unrealized_state)?; Ok(()) } @@ -160,7 +165,7 @@ pub trait CohortMetricsBase: CohortMetricsState + Send .min(self.outputs().min_len()) .min(self.activity().min_len()) .min(self.realized().min_stateful_height_len()) - .min(self.unrealized_full().min_stateful_height_len()) + .min(self.unrealized().min_stateful_height_len()) .min(self.cost_basis_base().min_stateful_height_len()) } @@ -180,31 +185,6 @@ pub trait CohortMetricsBase: CohortMetricsState + Send Ok(()) } - /// Compute net_sentiment.height as capital-weighted average of component cohorts. - fn compute_net_sentiment_from_others( - &mut self, - starting_indexes: &Indexes, - others: &[&T], - exit: &Exit, - ) -> Result<()> { - let weights: Vec<_> = others - .iter() - .map(|o| &o.realized_base().realized_cap.height) - .collect(); - let values: Vec<_> = others - .iter() - .map(|o| &o.unrealized_full().net_sentiment.cents.height) - .collect(); - - self.unrealized_full_mut() - .net_sentiment - .cents - .height - .compute_weighted_average_of_others(starting_indexes.height, &weights, &values, exit)?; - - Ok(()) - } - /// First phase of computed metrics (indexes from height). fn compute_rest_part1( &mut self, @@ -234,19 +214,18 @@ pub trait CohortMetricsBase: CohortMetricsState + Send self.realized_mut() .compute_rest_part1(starting_indexes, exit)?; - self.unrealized_full_mut() + self.unrealized_mut() .compute_rest(prices, starting_indexes, exit)?; Ok(()) } - /// Compute net_sentiment.height for separate cohorts (greed - pain). fn compute_net_sentiment_height( &mut self, starting_indexes: &Indexes, exit: &Exit, ) -> Result<()> { - self.unrealized_full_mut() + self.unrealized_mut() .compute_net_sentiment_height(starting_indexes, exit)?; Ok(()) } @@ -278,9 +257,9 @@ pub trait CohortMetricsBase: CohortMetricsState + Send &others.iter().map(|v| v.realized_base()).collect::>(), exit, )?; - self.unrealized_full_mut().compute_from_stateful( + self.unrealized_base_mut().compute_from_stateful( starting_indexes, - &others.iter().map(|v| v.unrealized_full()).collect::>(), + &others.iter().map(|v| v.unrealized_base()).collect::>(), exit, )?; self.cost_basis_base_mut().compute_from_stateful( diff --git a/crates/brk_computer/src/distribution/metrics/realized/base.rs b/crates/brk_computer/src/distribution/metrics/realized/base.rs index aeb11e931..20dc89584 100644 --- a/crates/brk_computer/src/distribution/metrics/realized/base.rs +++ b/crates/brk_computer/src/distribution/metrics/realized/base.rs @@ -10,9 +10,8 @@ use crate::{ blocks, distribution::state::RealizedOps, internal::{ - ComputedFromHeight, ComputedFromHeightCumulative, - LazyFromHeight, NegCentsUnsignedToDollars, RatioCents64, - RollingWindows, ValueFromHeightCumulative, + ByUnit, ComputedFromHeight, ComputedFromHeightCumulative, LazyFromHeight, + NegCentsUnsignedToDollars, RatioCents64, RollingWindows, SatsToCents, }, prices, }; diff --git a/crates/brk_computer/src/distribution/metrics/relative/base.rs b/crates/brk_computer/src/distribution/metrics/relative/base.rs index 86f57ec6d..198d3083b 100644 --- a/crates/brk_computer/src/distribution/metrics/relative/base.rs +++ b/crates/brk_computer/src/distribution/metrics/relative/base.rs @@ -5,7 +5,7 @@ use vecdb::{Exit, ReadableVec, Rw, StorageMode}; use crate::internal::{PercentFromHeight, RatioSatsBp16}; -use crate::distribution::metrics::{ImportConfig, UnrealizedBase}; +use crate::distribution::metrics::{ImportConfig, UnrealizedCore}; /// Relative metrics for the Complete tier. #[derive(Traversable)] @@ -29,7 +29,7 @@ impl RelativeBase { pub(crate) fn compute( &mut self, max_from: Height, - unrealized: &UnrealizedBase, + unrealized: &UnrealizedCore, supply_total_sats: &impl ReadableVec, exit: &Exit, ) -> Result<()> { diff --git a/crates/brk_computer/src/distribution/metrics/relative/extended_own_market_cap.rs b/crates/brk_computer/src/distribution/metrics/relative/extended_own_market_cap.rs index b2ecbc596..56b85ba67 100644 --- a/crates/brk_computer/src/distribution/metrics/relative/extended_own_market_cap.rs +++ b/crates/brk_computer/src/distribution/metrics/relative/extended_own_market_cap.rs @@ -5,7 +5,7 @@ use vecdb::{Exit, ReadableVec, Rw, StorageMode}; use crate::internal::{PercentFromHeight, RatioDollarsBp16, RatioDollarsBp32, RatioDollarsBps32}; -use crate::distribution::metrics::{ImportConfig, UnrealizedFull}; +use crate::distribution::metrics::{ImportConfig, UnrealizedBase}; /// Extended relative metrics for own market cap (extended && rel_to_all). #[derive(Traversable)] @@ -32,7 +32,7 @@ impl RelativeExtendedOwnMarketCap { pub(crate) fn compute( &mut self, max_from: Height, - unrealized: &UnrealizedFull, + unrealized: &UnrealizedBase, own_market_cap: &impl ReadableVec, exit: &Exit, ) -> Result<()> { diff --git a/crates/brk_computer/src/distribution/metrics/relative/extended_own_pnl.rs b/crates/brk_computer/src/distribution/metrics/relative/extended_own_pnl.rs index a289de4d1..900549ece 100644 --- a/crates/brk_computer/src/distribution/metrics/relative/extended_own_pnl.rs +++ b/crates/brk_computer/src/distribution/metrics/relative/extended_own_pnl.rs @@ -5,7 +5,7 @@ use vecdb::{Exit, Rw, StorageMode}; use crate::internal::{PercentFromHeight, RatioDollarsBp16, RatioDollarsBps32}; -use crate::distribution::metrics::{ImportConfig, UnrealizedFull}; +use crate::distribution::metrics::{ImportConfig, UnrealizedBase}; /// Extended relative metrics for own total unrealized PnL (extended only). #[derive(Traversable)] @@ -32,7 +32,7 @@ impl RelativeExtendedOwnPnl { pub(crate) fn compute( &mut self, max_from: Height, - unrealized: &UnrealizedFull, + unrealized: &UnrealizedBase, exit: &Exit, ) -> Result<()> { self.unrealized_profit_rel_to_own_gross_pnl diff --git a/crates/brk_computer/src/distribution/metrics/relative/for_all.rs b/crates/brk_computer/src/distribution/metrics/relative/for_all.rs index b7f7426df..f2e462d22 100644 --- a/crates/brk_computer/src/distribution/metrics/relative/for_all.rs +++ b/crates/brk_computer/src/distribution/metrics/relative/for_all.rs @@ -4,7 +4,7 @@ use brk_types::{Dollars, Height, Sats}; use derive_more::{Deref, DerefMut}; use vecdb::{Exit, ReadableVec, Rw, StorageMode}; -use crate::distribution::metrics::{ImportConfig, RealizedBase, UnrealizedFull}; +use crate::distribution::metrics::{ImportConfig, RealizedBase, UnrealizedBase}; use super::{RelativeFull, RelativeExtendedOwnPnl}; @@ -30,7 +30,7 @@ impl RelativeForAll { pub(crate) fn compute( &mut self, max_from: Height, - unrealized: &UnrealizedFull, + unrealized: &UnrealizedBase, realized: &RealizedBase, supply_total_sats: &impl ReadableVec, market_cap: &impl ReadableVec, diff --git a/crates/brk_computer/src/distribution/metrics/relative/full.rs b/crates/brk_computer/src/distribution/metrics/relative/full.rs index 08a256041..01b6f929f 100644 --- a/crates/brk_computer/src/distribution/metrics/relative/full.rs +++ b/crates/brk_computer/src/distribution/metrics/relative/full.rs @@ -10,7 +10,7 @@ use crate::internal::{ Bps32ToFloat, LazyFromHeight, PercentFromHeight, RatioDollarsBp16, RatioDollarsBps32, }; -use crate::distribution::metrics::{ImportConfig, RealizedBase, UnrealizedFull}; +use crate::distribution::metrics::{ImportConfig, RealizedBase, UnrealizedBase}; use super::RelativeBase; @@ -73,7 +73,7 @@ impl RelativeFull { pub(crate) fn compute( &mut self, max_from: Height, - unrealized: &UnrealizedFull, + unrealized: &UnrealizedBase, realized: &RealizedBase, supply_total_sats: &impl ReadableVec, market_cap: &impl ReadableVec, @@ -81,7 +81,7 @@ impl RelativeFull { ) -> Result<()> { self.base.compute( max_from, - &unrealized.base, + &unrealized.core, supply_total_sats, exit, )?; diff --git a/crates/brk_computer/src/distribution/metrics/relative/with_extended.rs b/crates/brk_computer/src/distribution/metrics/relative/with_extended.rs index 7f629b88b..730d376ee 100644 --- a/crates/brk_computer/src/distribution/metrics/relative/with_extended.rs +++ b/crates/brk_computer/src/distribution/metrics/relative/with_extended.rs @@ -4,7 +4,7 @@ use brk_types::{Dollars, Height, Sats}; use derive_more::{Deref, DerefMut}; use vecdb::{Exit, ReadableVec, Rw, StorageMode}; -use crate::distribution::metrics::{ImportConfig, RealizedBase, UnrealizedFull}; +use crate::distribution::metrics::{ImportConfig, RealizedBase, UnrealizedBase}; use super::{RelativeFull, RelativeExtendedOwnMarketCap, RelativeExtendedOwnPnl, RelativeToAll}; @@ -38,7 +38,7 @@ impl RelativeWithExtended { pub(crate) fn compute( &mut self, max_from: Height, - unrealized: &UnrealizedFull, + unrealized: &UnrealizedBase, realized: &RealizedBase, supply_total_sats: &impl ReadableVec, market_cap: &impl ReadableVec, diff --git a/crates/brk_computer/src/distribution/metrics/relative/with_rel_to_all.rs b/crates/brk_computer/src/distribution/metrics/relative/with_rel_to_all.rs index 7e0fbd07f..1ea4a864c 100644 --- a/crates/brk_computer/src/distribution/metrics/relative/with_rel_to_all.rs +++ b/crates/brk_computer/src/distribution/metrics/relative/with_rel_to_all.rs @@ -4,7 +4,7 @@ use brk_types::{Dollars, Height, Sats}; use derive_more::{Deref, DerefMut}; use vecdb::{Exit, ReadableVec, Rw, StorageMode}; -use crate::distribution::metrics::{ImportConfig, RealizedBase, UnrealizedFull}; +use crate::distribution::metrics::{ImportConfig, RealizedBase, UnrealizedBase}; use super::{RelativeFull, RelativeToAll}; @@ -32,7 +32,7 @@ impl RelativeWithRelToAll { pub(crate) fn compute( &mut self, max_from: Height, - unrealized: &UnrealizedFull, + unrealized: &UnrealizedBase, realized: &RealizedBase, supply_total_sats: &impl ReadableVec, market_cap: &impl ReadableVec, diff --git a/crates/brk_computer/src/distribution/metrics/relative/with_rel_to_all_base.rs b/crates/brk_computer/src/distribution/metrics/relative/with_rel_to_all_base.rs index fbc6db65c..84fd30a1b 100644 --- a/crates/brk_computer/src/distribution/metrics/relative/with_rel_to_all_base.rs +++ b/crates/brk_computer/src/distribution/metrics/relative/with_rel_to_all_base.rs @@ -4,7 +4,7 @@ use brk_types::{Height, Sats}; use derive_more::{Deref, DerefMut}; use vecdb::{Exit, ReadableVec, Rw, StorageMode}; -use crate::distribution::metrics::{ImportConfig, UnrealizedBase}; +use crate::distribution::metrics::{ImportConfig, UnrealizedCore}; use super::{RelativeBase, RelativeToAll}; @@ -31,7 +31,7 @@ impl RelativeBaseWithRelToAll { pub(crate) fn compute( &mut self, max_from: Height, - unrealized: &UnrealizedBase, + unrealized: &UnrealizedCore, supply_total_sats: &impl ReadableVec, all_supply_sats: &impl ReadableVec, exit: &Exit, diff --git a/crates/brk_computer/src/distribution/metrics/unrealized/base.rs b/crates/brk_computer/src/distribution/metrics/unrealized/base.rs index cc318314b..fccb843a5 100644 --- a/crates/brk_computer/src/distribution/metrics/unrealized/base.rs +++ b/crates/brk_computer/src/distribution/metrics/unrealized/base.rs @@ -1,73 +1,75 @@ use brk_error::Result; use brk_traversable::Traversable; -use brk_types::{Cents, CentsSigned, Height, Indexes, Version}; -use vecdb::{AnyStoredVec, AnyVec, Exit, ReadableCloneableVec, Rw, StorageMode, WritableVec}; +use brk_types::{Cents, CentsSats, CentsSquaredSats, Height, Indexes, Version}; +use derive_more::{Deref, DerefMut}; +use vecdb::{AnyStoredVec, AnyVec, BytesVec, Exit, ReadableVec, Rw, StorageMode, WritableVec}; use crate::{ distribution::state::UnrealizedState, - internal::{ - CentsSubtractToCentsSigned, FiatFromHeight, LazyFromHeight, NegCentsUnsignedToDollars, - ValueFromHeight, - }, + internal::FiatFromHeight, + prices, }; -use brk_types::Dollars; - use crate::distribution::metrics::ImportConfig; -/// Unrealized metrics for the Complete tier (~6 fields). -/// -/// Excludes source-only fields (invested_capital, raw BytesVecs) -/// and extended-only fields (pain_index, greed_index, net_sentiment). -#[derive(Traversable)] +use super::UnrealizedCore; + +#[derive(Deref, DerefMut, Traversable)] pub struct UnrealizedBase { - pub supply_in_profit: ValueFromHeight, - pub supply_in_loss: ValueFromHeight, + #[deref] + #[deref_mut] + #[traversable(flatten)] + pub core: UnrealizedCore, - pub unrealized_profit: FiatFromHeight, - pub unrealized_loss: FiatFromHeight, + pub gross_pnl: FiatFromHeight, - pub neg_unrealized_loss: LazyFromHeight, + pub invested_capital_in_profit: FiatFromHeight, + pub invested_capital_in_loss: FiatFromHeight, - pub net_unrealized_pnl: FiatFromHeight, + pub invested_capital_in_profit_raw: M::Stored>, + pub invested_capital_in_loss_raw: M::Stored>, + pub investor_cap_in_profit_raw: M::Stored>, + pub investor_cap_in_loss_raw: M::Stored>, } impl UnrealizedBase { pub(crate) fn forced_import(cfg: &ImportConfig) -> Result { let v0 = Version::ZERO; - let supply_in_profit = cfg.import("supply_in_profit", v0)?; - let supply_in_loss = cfg.import("supply_in_loss", v0)?; - let unrealized_profit = cfg.import("unrealized_profit", v0)?; - let unrealized_loss: FiatFromHeight = cfg.import("unrealized_loss", v0)?; + let core = UnrealizedCore::forced_import(cfg)?; - let neg_unrealized_loss = LazyFromHeight::from_computed::( - &cfg.name("neg_unrealized_loss"), - cfg.version, - unrealized_loss.cents.height.read_only_boxed_clone(), - &unrealized_loss.cents, - ); + let gross_pnl = cfg.import("unrealized_gross_pnl", v0)?; - let net_unrealized_pnl = cfg.import("net_unrealized_pnl", v0)?; + let invested_capital_in_profit = cfg.import("invested_capital_in_profit", v0)?; + let invested_capital_in_loss = cfg.import("invested_capital_in_loss", v0)?; + + let invested_capital_in_profit_raw = + cfg.import("invested_capital_in_profit_raw", v0)?; + let invested_capital_in_loss_raw = cfg.import("invested_capital_in_loss_raw", v0)?; + let investor_cap_in_profit_raw = cfg.import("investor_cap_in_profit_raw", v0)?; + let investor_cap_in_loss_raw = cfg.import("investor_cap_in_loss_raw", v0)?; Ok(Self { - supply_in_profit, - supply_in_loss, - unrealized_profit, - unrealized_loss, - neg_unrealized_loss, - net_unrealized_pnl, + core, + gross_pnl, + invested_capital_in_profit, + invested_capital_in_loss, + invested_capital_in_profit_raw, + invested_capital_in_loss_raw, + investor_cap_in_profit_raw, + investor_cap_in_loss_raw, }) } pub(crate) fn min_stateful_height_len(&self) -> usize { - self.supply_in_profit - .sats - .height - .len() - .min(self.supply_in_loss.sats.height.len()) - .min(self.unrealized_profit.cents.height.len()) - .min(self.unrealized_loss.cents.height.len()) + self.core + .min_stateful_height_len() + .min(self.invested_capital_in_profit.cents.height.len()) + .min(self.invested_capital_in_loss.cents.height.len()) + .min(self.invested_capital_in_profit_raw.len()) + .min(self.invested_capital_in_loss_raw.len()) + .min(self.investor_cap_in_profit_raw.len()) + .min(self.investor_cap_in_loss_raw.len()) } pub(crate) fn truncate_push( @@ -75,35 +77,46 @@ impl UnrealizedBase { height: Height, height_state: &UnrealizedState, ) -> Result<()> { - self.supply_in_profit - .sats - .height - .truncate_push(height, height_state.supply_in_profit)?; - self.supply_in_loss - .sats - .height - .truncate_push(height, height_state.supply_in_loss)?; - self.unrealized_profit + self.core.truncate_push(height, height_state)?; + + self.invested_capital_in_profit .cents .height - .truncate_push(height, height_state.unrealized_profit)?; - self.unrealized_loss + .truncate_push(height, height_state.invested_capital_in_profit)?; + self.invested_capital_in_loss .cents .height - .truncate_push(height, height_state.unrealized_loss)?; + .truncate_push(height, height_state.invested_capital_in_loss)?; + + self.invested_capital_in_profit_raw.truncate_push( + height, + CentsSats::new(height_state.invested_capital_in_profit_raw), + )?; + self.invested_capital_in_loss_raw.truncate_push( + height, + CentsSats::new(height_state.invested_capital_in_loss_raw), + )?; + self.investor_cap_in_profit_raw.truncate_push( + height, + CentsSquaredSats::new(height_state.investor_cap_in_profit_raw), + )?; + self.investor_cap_in_loss_raw.truncate_push( + height, + CentsSquaredSats::new(height_state.investor_cap_in_loss_raw), + )?; Ok(()) } pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> { - vec![ - &mut self.supply_in_profit.base.sats.height as &mut dyn AnyStoredVec, - &mut self.supply_in_profit.base.cents.height as &mut dyn AnyStoredVec, - &mut self.supply_in_loss.base.sats.height as &mut dyn AnyStoredVec, - &mut self.supply_in_loss.base.cents.height as &mut dyn AnyStoredVec, - &mut self.unrealized_profit.cents.height, - &mut self.unrealized_loss.cents.height, - ] + let mut vecs = self.core.collect_vecs_mut(); + vecs.push(&mut self.invested_capital_in_profit.cents.height as &mut dyn AnyStoredVec); + vecs.push(&mut self.invested_capital_in_loss.cents.height as &mut dyn AnyStoredVec); + vecs.push(&mut self.invested_capital_in_profit_raw as &mut dyn AnyStoredVec); + vecs.push(&mut self.invested_capital_in_loss_raw as &mut dyn AnyStoredVec); + vecs.push(&mut self.investor_cap_in_profit_raw as &mut dyn AnyStoredVec); + vecs.push(&mut self.investor_cap_in_loss_raw as &mut dyn AnyStoredVec); + vecs } pub(crate) fn compute_from_stateful( @@ -112,29 +125,89 @@ impl UnrealizedBase { others: &[&Self], exit: &Exit, ) -> Result<()> { - sum_others!(self, starting_indexes, others, exit; supply_in_profit.sats.height); - sum_others!(self, starting_indexes, others, exit; supply_in_loss.sats.height); - sum_others!(self, starting_indexes, others, exit; unrealized_profit.cents.height); - sum_others!(self, starting_indexes, others, exit; unrealized_loss.cents.height); + let core_refs: Vec<&UnrealizedCore> = + others.iter().map(|o| &o.core).collect(); + self.core + .compute_from_stateful(starting_indexes, &core_refs, exit)?; + + sum_others!(self, starting_indexes, others, exit; invested_capital_in_profit.cents.height); + sum_others!(self, starting_indexes, others, exit; invested_capital_in_loss.cents.height); + + let start = self + .invested_capital_in_profit_raw + .len() + .min(self.invested_capital_in_loss_raw.len()) + .min(self.investor_cap_in_profit_raw.len()) + .min(self.investor_cap_in_loss_raw.len()); + let end = others + .iter() + .map(|o| o.invested_capital_in_profit_raw.len()) + .min() + .unwrap_or(0); + + let invested_profit_ranges: Vec> = others + .iter() + .map(|o| { + o.invested_capital_in_profit_raw + .collect_range_at(start, end) + }) + .collect(); + let invested_loss_ranges: Vec> = others + .iter() + .map(|o| o.invested_capital_in_loss_raw.collect_range_at(start, end)) + .collect(); + let investor_profit_ranges: Vec> = others + .iter() + .map(|o| o.investor_cap_in_profit_raw.collect_range_at(start, end)) + .collect(); + let investor_loss_ranges: Vec> = others + .iter() + .map(|o| o.investor_cap_in_loss_raw.collect_range_at(start, end)) + .collect(); + + for i in start..end { + let height = Height::from(i); + let local_i = i - start; + + let mut sum_invested_profit = CentsSats::ZERO; + let mut sum_invested_loss = CentsSats::ZERO; + let mut sum_investor_profit = CentsSquaredSats::ZERO; + let mut sum_investor_loss = CentsSquaredSats::ZERO; + + for idx in 0..others.len() { + sum_invested_profit += invested_profit_ranges[idx][local_i]; + sum_invested_loss += invested_loss_ranges[idx][local_i]; + sum_investor_profit += investor_profit_ranges[idx][local_i]; + sum_investor_loss += investor_loss_ranges[idx][local_i]; + } + + self.invested_capital_in_profit_raw + .truncate_push(height, sum_invested_profit)?; + self.invested_capital_in_loss_raw + .truncate_push(height, sum_invested_loss)?; + self.investor_cap_in_profit_raw + .truncate_push(height, sum_investor_profit)?; + self.investor_cap_in_loss_raw + .truncate_push(height, sum_investor_loss)?; + } Ok(()) } - /// Compute derived metrics from stored values. pub(crate) fn compute_rest( &mut self, + _prices: &prices::Vecs, starting_indexes: &Indexes, exit: &Exit, ) -> Result<()> { - self.net_unrealized_pnl - .cents - .height - .compute_binary::( - starting_indexes.height, - &self.unrealized_profit.cents.height, - &self.unrealized_loss.cents.height, - exit, - )?; + self.core.compute_rest(starting_indexes, exit)?; + + self.gross_pnl.cents.height.compute_add( + starting_indexes.height, + &self.core.unrealized_profit.cents.height, + &self.core.unrealized_loss.cents.height, + exit, + )?; Ok(()) } diff --git a/crates/brk_computer/src/distribution/metrics/unrealized/core.rs b/crates/brk_computer/src/distribution/metrics/unrealized/core.rs new file mode 100644 index 000000000..75b84df59 --- /dev/null +++ b/crates/brk_computer/src/distribution/metrics/unrealized/core.rs @@ -0,0 +1,137 @@ +use brk_error::Result; +use brk_traversable::Traversable; +use brk_types::{Cents, CentsSigned, Height, Indexes, Version}; +use vecdb::{AnyStoredVec, AnyVec, Exit, ReadableCloneableVec, Rw, StorageMode, WritableVec}; + +use crate::{ + distribution::state::UnrealizedState, + internal::{ + CentsSubtractToCentsSigned, FiatFromHeight, LazyFromHeight, NegCentsUnsignedToDollars, + ValueFromHeight, + }, +}; + +use brk_types::Dollars; + +use crate::distribution::metrics::ImportConfig; + +#[derive(Traversable)] +pub struct UnrealizedCore { + pub supply_in_profit: ValueFromHeight, + pub supply_in_loss: ValueFromHeight, + + pub unrealized_profit: FiatFromHeight, + pub unrealized_loss: FiatFromHeight, + + pub neg_unrealized_loss: LazyFromHeight, + + pub net_unrealized_pnl: FiatFromHeight, +} + +impl UnrealizedCore { + pub(crate) fn forced_import(cfg: &ImportConfig) -> Result { + let v0 = Version::ZERO; + let supply_in_profit = cfg.import("supply_in_profit", v0)?; + let supply_in_loss = cfg.import("supply_in_loss", v0)?; + + let unrealized_profit = cfg.import("unrealized_profit", v0)?; + let unrealized_loss: FiatFromHeight = cfg.import("unrealized_loss", v0)?; + + let neg_unrealized_loss = LazyFromHeight::from_computed::( + &cfg.name("neg_unrealized_loss"), + cfg.version, + unrealized_loss.cents.height.read_only_boxed_clone(), + &unrealized_loss.cents, + ); + + let net_unrealized_pnl = cfg.import("net_unrealized_pnl", v0)?; + + Ok(Self { + supply_in_profit, + supply_in_loss, + unrealized_profit, + unrealized_loss, + neg_unrealized_loss, + net_unrealized_pnl, + }) + } + + pub(crate) fn min_stateful_height_len(&self) -> usize { + self.supply_in_profit + .sats + .height + .len() + .min(self.supply_in_loss.sats.height.len()) + .min(self.unrealized_profit.cents.height.len()) + .min(self.unrealized_loss.cents.height.len()) + } + + pub(crate) fn truncate_push( + &mut self, + height: Height, + height_state: &UnrealizedState, + ) -> Result<()> { + self.supply_in_profit + .sats + .height + .truncate_push(height, height_state.supply_in_profit)?; + self.supply_in_loss + .sats + .height + .truncate_push(height, height_state.supply_in_loss)?; + self.unrealized_profit + .cents + .height + .truncate_push(height, height_state.unrealized_profit)?; + self.unrealized_loss + .cents + .height + .truncate_push(height, height_state.unrealized_loss)?; + + Ok(()) + } + + pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> { + vec![ + &mut self.supply_in_profit.base.sats.height as &mut dyn AnyStoredVec, + &mut self.supply_in_profit.base.cents.height as &mut dyn AnyStoredVec, + &mut self.supply_in_loss.base.sats.height as &mut dyn AnyStoredVec, + &mut self.supply_in_loss.base.cents.height as &mut dyn AnyStoredVec, + &mut self.unrealized_profit.cents.height, + &mut self.unrealized_loss.cents.height, + ] + } + + pub(crate) fn compute_from_stateful( + &mut self, + starting_indexes: &Indexes, + others: &[&Self], + exit: &Exit, + ) -> Result<()> { + sum_others!(self, starting_indexes, others, exit; supply_in_profit.sats.height); + sum_others!(self, starting_indexes, others, exit; supply_in_loss.sats.height); + sum_others!(self, starting_indexes, others, exit; unrealized_profit.cents.height); + sum_others!(self, starting_indexes, others, exit; unrealized_loss.cents.height); + + Ok(()) + } + + /// Compute derived metrics from stored values. + pub(crate) fn compute_rest( + &mut self, + starting_indexes: &Indexes, + exit: &Exit, + ) -> Result<()> { + self.net_unrealized_pnl + .cents + .height + .compute_binary::( + starting_indexes.height, + &self.unrealized_profit.cents.height, + &self.unrealized_loss.cents.height, + exit, + )?; + + Ok(()) + } +} diff --git a/crates/brk_computer/src/distribution/metrics/unrealized/full.rs b/crates/brk_computer/src/distribution/metrics/unrealized/full.rs index eff8391d3..649c73193 100644 --- a/crates/brk_computer/src/distribution/metrics/unrealized/full.rs +++ b/crates/brk_computer/src/distribution/metrics/unrealized/full.rs @@ -1,42 +1,23 @@ use brk_error::Result; use brk_traversable::Traversable; -use brk_types::{Cents, CentsSats, CentsSigned, CentsSquaredSats, Height, Indexes, Version}; +use brk_types::{Cents, CentsSigned, Indexes, Version}; use derive_more::{Deref, DerefMut}; -use vecdb::{AnyStoredVec, AnyVec, BytesVec, Exit, ReadableVec, Rw, StorageMode, WritableVec}; +use vecdb::{Exit, Rw, StorageMode}; -use crate::{ - distribution::state::UnrealizedState, - internal::{CentsSubtractToCentsSigned, FiatFromHeight}, - prices, -}; +use crate::internal::{CentsSubtractToCentsSigned, FiatFromHeight}; +use crate::prices; use crate::distribution::metrics::ImportConfig; use super::UnrealizedBase; -/// Full unrealized metrics (Source/Extended tier). -/// -/// Contains all Complete-tier fields (via Deref to UnrealizedBase) plus: -/// - Source-only: invested_capital, raw BytesVecs -/// - Extended-only: pain_index, greed_index, net_sentiment #[derive(Deref, DerefMut, Traversable)] pub struct UnrealizedFull { #[deref] #[deref_mut] #[traversable(flatten)] - pub base: UnrealizedBase, + pub inner: UnrealizedBase, - pub gross_pnl: FiatFromHeight, - - pub invested_capital_in_profit: FiatFromHeight, - pub invested_capital_in_loss: FiatFromHeight, - - pub invested_capital_in_profit_raw: M::Stored>, - pub invested_capital_in_loss_raw: M::Stored>, - pub investor_cap_in_profit_raw: M::Stored>, - pub investor_cap_in_loss_raw: M::Stored>, - - // --- Extended-only fields --- pub pain_index: FiatFromHeight, pub greed_index: FiatFromHeight, pub net_sentiment: FiatFromHeight, @@ -44,198 +25,32 @@ pub struct UnrealizedFull { impl UnrealizedFull { pub(crate) fn forced_import(cfg: &ImportConfig) -> Result { - let v0 = Version::ZERO; + let inner = UnrealizedBase::forced_import(cfg)?; - let base = UnrealizedBase::forced_import(cfg)?; - - let gross_pnl = cfg.import("unrealized_gross_pnl", v0)?; - - let invested_capital_in_profit = cfg.import("invested_capital_in_profit", v0)?; - let invested_capital_in_loss = cfg.import("invested_capital_in_loss", v0)?; - - let invested_capital_in_profit_raw = - cfg.import("invested_capital_in_profit_raw", v0)?; - let invested_capital_in_loss_raw = cfg.import("invested_capital_in_loss_raw", v0)?; - let investor_cap_in_profit_raw = cfg.import("investor_cap_in_profit_raw", v0)?; - let investor_cap_in_loss_raw = cfg.import("investor_cap_in_loss_raw", v0)?; - - let pain_index = cfg.import("pain_index", v0)?; - let greed_index = cfg.import("greed_index", v0)?; + let pain_index = cfg.import("pain_index", Version::ZERO)?; + let greed_index = cfg.import("greed_index", Version::ZERO)?; let net_sentiment = cfg.import("net_sentiment", Version::ONE)?; Ok(Self { - base, - gross_pnl, - invested_capital_in_profit, - invested_capital_in_loss, - invested_capital_in_profit_raw, - invested_capital_in_loss_raw, - investor_cap_in_profit_raw, - investor_cap_in_loss_raw, + inner, pain_index, greed_index, net_sentiment, }) } - pub(crate) fn min_stateful_height_len(&self) -> usize { - self.base - .min_stateful_height_len() - .min(self.invested_capital_in_profit.cents.height.len()) - .min(self.invested_capital_in_loss.cents.height.len()) - .min(self.invested_capital_in_profit_raw.len()) - .min(self.invested_capital_in_loss_raw.len()) - .min(self.investor_cap_in_profit_raw.len()) - .min(self.investor_cap_in_loss_raw.len()) - } - - pub(crate) fn truncate_push( - &mut self, - height: Height, - height_state: &UnrealizedState, - ) -> Result<()> { - self.base.truncate_push(height, height_state)?; - - self.invested_capital_in_profit - .cents - .height - .truncate_push(height, height_state.invested_capital_in_profit)?; - self.invested_capital_in_loss - .cents - .height - .truncate_push(height, height_state.invested_capital_in_loss)?; - - self.invested_capital_in_profit_raw.truncate_push( - height, - CentsSats::new(height_state.invested_capital_in_profit_raw), - )?; - self.invested_capital_in_loss_raw.truncate_push( - height, - CentsSats::new(height_state.invested_capital_in_loss_raw), - )?; - self.investor_cap_in_profit_raw.truncate_push( - height, - CentsSquaredSats::new(height_state.investor_cap_in_profit_raw), - )?; - self.investor_cap_in_loss_raw.truncate_push( - height, - CentsSquaredSats::new(height_state.investor_cap_in_loss_raw), - )?; - - Ok(()) - } - - pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> { - let mut vecs = self.base.collect_vecs_mut(); - vecs.push(&mut self.invested_capital_in_profit.cents.height as &mut dyn AnyStoredVec); - vecs.push(&mut self.invested_capital_in_loss.cents.height as &mut dyn AnyStoredVec); - vecs.push(&mut self.invested_capital_in_profit_raw as &mut dyn AnyStoredVec); - vecs.push(&mut self.invested_capital_in_loss_raw as &mut dyn AnyStoredVec); - vecs.push(&mut self.investor_cap_in_profit_raw as &mut dyn AnyStoredVec); - vecs.push(&mut self.investor_cap_in_loss_raw as &mut dyn AnyStoredVec); - vecs - } - - pub(crate) fn compute_from_stateful( - &mut self, - starting_indexes: &Indexes, - others: &[&Self], - exit: &Exit, - ) -> Result<()> { - // Delegate Complete-tier aggregation - let base_refs: Vec<&UnrealizedBase> = - others.iter().map(|o| &o.base).collect(); - self.base - .compute_from_stateful(starting_indexes, &base_refs, exit)?; - - // Source-only: invested_capital - sum_others!(self, starting_indexes, others, exit; invested_capital_in_profit.cents.height); - sum_others!(self, starting_indexes, others, exit; invested_capital_in_loss.cents.height); - - // Source-only: raw BytesVec aggregation - let start = self - .invested_capital_in_profit_raw - .len() - .min(self.invested_capital_in_loss_raw.len()) - .min(self.investor_cap_in_profit_raw.len()) - .min(self.investor_cap_in_loss_raw.len()); - let end = others - .iter() - .map(|o| o.invested_capital_in_profit_raw.len()) - .min() - .unwrap_or(0); - - // Pre-collect all cohort data to avoid per-element BytesVec reads in nested loop - let invested_profit_ranges: Vec> = others - .iter() - .map(|o| { - o.invested_capital_in_profit_raw - .collect_range_at(start, end) - }) - .collect(); - let invested_loss_ranges: Vec> = others - .iter() - .map(|o| o.invested_capital_in_loss_raw.collect_range_at(start, end)) - .collect(); - let investor_profit_ranges: Vec> = others - .iter() - .map(|o| o.investor_cap_in_profit_raw.collect_range_at(start, end)) - .collect(); - let investor_loss_ranges: Vec> = others - .iter() - .map(|o| o.investor_cap_in_loss_raw.collect_range_at(start, end)) - .collect(); - - for i in start..end { - let height = Height::from(i); - let local_i = i - start; - - let mut sum_invested_profit = CentsSats::ZERO; - let mut sum_invested_loss = CentsSats::ZERO; - let mut sum_investor_profit = CentsSquaredSats::ZERO; - let mut sum_investor_loss = CentsSquaredSats::ZERO; - - for idx in 0..others.len() { - sum_invested_profit += invested_profit_ranges[idx][local_i]; - sum_invested_loss += invested_loss_ranges[idx][local_i]; - sum_investor_profit += investor_profit_ranges[idx][local_i]; - sum_investor_loss += investor_loss_ranges[idx][local_i]; - } - - self.invested_capital_in_profit_raw - .truncate_push(height, sum_invested_profit)?; - self.invested_capital_in_loss_raw - .truncate_push(height, sum_invested_loss)?; - self.investor_cap_in_profit_raw - .truncate_push(height, sum_investor_profit)?; - self.investor_cap_in_loss_raw - .truncate_push(height, sum_investor_loss)?; - } - - Ok(()) - } - - /// Compute derived metrics from stored values + price. pub(crate) fn compute_rest( &mut self, prices: &prices::Vecs, starting_indexes: &Indexes, exit: &Exit, ) -> Result<()> { - self.base.compute_rest(starting_indexes, exit)?; + self.inner.compute_rest(prices, starting_indexes, exit)?; - self.gross_pnl.cents.height.compute_add( - starting_indexes.height, - &self.base.unrealized_profit.cents.height, - &self.base.unrealized_loss.cents.height, - exit, - )?; - - // Pain index (investor_price_of_losers - spot) self.pain_index.cents.height.compute_transform3( starting_indexes.height, - &self.investor_cap_in_loss_raw, - &self.invested_capital_in_loss_raw, + &self.inner.investor_cap_in_loss_raw, + &self.inner.invested_capital_in_loss_raw, &prices.price.cents.height, |(h, investor_cap, invested_cap, spot, ..)| { if invested_cap.inner() == 0 { @@ -248,11 +63,10 @@ impl UnrealizedFull { exit, )?; - // Extended-only: Greed index (spot - investor_price_of_winners) self.greed_index.cents.height.compute_transform3( starting_indexes.height, - &self.investor_cap_in_profit_raw, - &self.invested_capital_in_profit_raw, + &self.inner.investor_cap_in_profit_raw, + &self.inner.invested_capital_in_profit_raw, &prices.price.cents.height, |(h, investor_cap, invested_cap, spot, ..)| { if invested_cap.inner() == 0 { @@ -268,7 +82,6 @@ impl UnrealizedFull { Ok(()) } - /// Compute net_sentiment.height for separate cohorts (greed - pain). pub(crate) fn compute_net_sentiment_height( &mut self, starting_indexes: &Indexes, diff --git a/crates/brk_computer/src/distribution/metrics/unrealized/mod.rs b/crates/brk_computer/src/distribution/metrics/unrealized/mod.rs index fef954c35..993022431 100644 --- a/crates/brk_computer/src/distribution/metrics/unrealized/mod.rs +++ b/crates/brk_computer/src/distribution/metrics/unrealized/mod.rs @@ -1,5 +1,52 @@ mod base; +mod core; mod full; pub use base::UnrealizedBase; +pub use self::core::UnrealizedCore; pub use full::UnrealizedFull; + +use brk_error::Result; +use brk_types::{Height, Indexes}; +use vecdb::Exit; + +use crate::{distribution::state::UnrealizedState, prices}; + +pub trait UnrealizedLike: Send + Sync { + fn as_base(&self) -> &UnrealizedBase; + fn as_base_mut(&mut self) -> &mut UnrealizedBase; + fn min_stateful_height_len(&self) -> usize; + fn truncate_push(&mut self, height: Height, state: &UnrealizedState) -> Result<()>; + fn compute_rest(&mut self, prices: &prices::Vecs, starting_indexes: &Indexes, exit: &Exit) -> Result<()>; + fn compute_net_sentiment_height(&mut self, starting_indexes: &Indexes, exit: &Exit) -> Result<()>; +} + +impl UnrealizedLike for UnrealizedBase { + fn as_base(&self) -> &UnrealizedBase { self } + fn as_base_mut(&mut self) -> &mut UnrealizedBase { self } + fn min_stateful_height_len(&self) -> usize { self.min_stateful_height_len() } + fn truncate_push(&mut self, height: Height, state: &UnrealizedState) -> Result<()> { + self.truncate_push(height, state) + } + fn compute_rest(&mut self, prices: &prices::Vecs, starting_indexes: &Indexes, exit: &Exit) -> Result<()> { + self.compute_rest(prices, starting_indexes, exit) + } + fn compute_net_sentiment_height(&mut self, _starting_indexes: &Indexes, _exit: &Exit) -> Result<()> { + Ok(()) + } +} + +impl UnrealizedLike for UnrealizedFull { + fn as_base(&self) -> &UnrealizedBase { &self.inner } + fn as_base_mut(&mut self) -> &mut UnrealizedBase { &mut self.inner } + fn min_stateful_height_len(&self) -> usize { self.inner.min_stateful_height_len() } + fn truncate_push(&mut self, height: Height, state: &UnrealizedState) -> Result<()> { + self.inner.truncate_push(height, state) + } + fn compute_rest(&mut self, prices: &prices::Vecs, starting_indexes: &Indexes, exit: &Exit) -> Result<()> { + self.compute_rest(prices, starting_indexes, exit) + } + fn compute_net_sentiment_height(&mut self, starting_indexes: &Indexes, exit: &Exit) -> Result<()> { + self.compute_net_sentiment_height(starting_indexes, exit) + } +} diff --git a/crates/brk_computer/src/distribution/vecs.rs b/crates/brk_computer/src/distribution/vecs.rs index 89693af2d..7b962a6b0 100644 --- a/crates/brk_computer/src/distribution/vecs.rs +++ b/crates/brk_computer/src/distribution/vecs.rs @@ -30,7 +30,7 @@ use crate::{ use super::{ AddressCohorts, AddressesDataVecs, AnyAddressIndexesVecs, RangeMap, UTXOCohorts, address::{ - AddrCountsVecs, AddressActivityVecs, GrowthRateVecs, NewAddrCountVecs, TotalAddrCountVecs, + AddrCountsVecs, AddressActivityVecs, DeltaVecs, NewAddrCountVecs, TotalAddrCountVecs, }, compute::aggregates, }; @@ -57,8 +57,8 @@ pub struct Vecs { pub total_addr_count: TotalAddrCountVecs, /// New addresses per block (delta of total) - stored height + cumulative + rolling, global + per-type pub new_addr_count: NewAddrCountVecs, - /// Growth rate (new / addr_count) - stored ratio with distribution stats, global + per-type - pub growth_rate: GrowthRateVecs, + /// Windowed change + growth rate for addr_count, global + per-type + pub delta: DeltaVecs, pub fundedaddressindex: LazyVecFrom1, @@ -141,7 +141,7 @@ impl Vecs { let new_addr_count = NewAddrCountVecs::forced_import(&db, version, indexes)?; // Growth rate: new / addr_count (global + per-type) - let growth_rate = GrowthRateVecs::forced_import(&db, version, indexes)?; + let delta = DeltaVecs::forced_import(&db, version, indexes)?; let this = Self { supply_state: BytesVec::forced_import_with( @@ -154,7 +154,7 @@ impl Vecs { address_activity, total_addr_count, new_addr_count, - growth_rate, + delta, utxo_cohorts, address_cohorts, @@ -400,11 +400,11 @@ impl Vecs { exit, )?; - // 6b. Compute address count day1 vecs (by addresstype + all) + // 6b. Compute address count sum (by addresstype → all) self.addr_count - .compute_rest(blocks, starting_indexes, exit)?; + .compute_rest(starting_indexes, exit)?; self.empty_addr_count - .compute_rest(blocks, starting_indexes, exit)?; + .compute_rest(starting_indexes, exit)?; // 6c. Compute total_addr_count = addr_count + empty_addr_count self.total_addr_count.compute( @@ -425,11 +425,9 @@ impl Vecs { exit, )?; - // 6e. Compute growth_rate = new_addr_count / addr_count - self.growth_rate.compute( + self.delta.compute( starting_indexes.height, &window_starts, - &self.new_addr_count, &self.addr_count, exit, )?; diff --git a/crates/brk_computer/src/internal/from_height/computed/delta.rs b/crates/brk_computer/src/internal/from_height/computed/delta.rs new file mode 100644 index 000000000..242e21bd8 --- /dev/null +++ b/crates/brk_computer/src/internal/from_height/computed/delta.rs @@ -0,0 +1,107 @@ +//! RollingDelta - raw change + growth rate (%) across 4 time windows. +//! +//! For a monotonic source (e.g., cumulative address count): +//! - `change._24h` = count_now - count_24h_ago +//! - `rate._24h` = (count_now - count_24h_ago) / count_24h_ago in BPS + +use brk_error::Result; +use brk_traversable::Traversable; +use brk_types::{BasisPoints16, Height, Version}; +use schemars::JsonSchema; +use vecdb::{Database, Exit, ReadableVec, Rw, StorageMode}; + +use crate::{ + indexes, + internal::{NumericValue, PercentRollingWindows, RollingWindows, WindowStarts}, +}; + +#[derive(Traversable)] +pub struct RollingDelta +where + S: NumericValue + JsonSchema, + C: NumericValue + JsonSchema, +{ + pub change: RollingWindows, + pub rate: PercentRollingWindows, + _phantom: std::marker::PhantomData, +} + +impl RollingDelta +where + S: NumericValue + JsonSchema, + C: NumericValue + JsonSchema, +{ + pub(crate) fn forced_import( + db: &Database, + name: &str, + version: Version, + indexes: &indexes::Vecs, + ) -> Result { + Ok(Self { + change: RollingWindows::forced_import( + db, + &format!("{name}_change"), + version, + indexes, + )?, + rate: PercentRollingWindows::forced_import( + db, + &format!("{name}_rate"), + version, + indexes, + )?, + _phantom: std::marker::PhantomData, + }) + } + + pub(crate) fn compute( + &mut self, + max_from: Height, + windows: &WindowStarts<'_>, + source: &impl ReadableVec, + exit: &Exit, + ) -> Result<()> + where + S: Default, + { + // Step 1: change = current - ago + for (change_w, starts) in self.change.0.as_mut_array().into_iter().zip(windows.as_array()) + { + change_w.height.compute_transform( + max_from, + *starts, + |(h, ago_h, ..)| { + let current: f64 = source.collect_one(h).unwrap_or_default().into(); + let ago: f64 = source.collect_one(ago_h).unwrap_or_default().into(); + (h, C::from(current - ago)) + }, + exit, + )?; + } + + // Step 2: rate = change / ago = change / (current - change) + for (growth_w, change_w) in self + .rate + .0 + .as_mut_array() + .into_iter() + .zip(self.change.0.as_array()) + { + growth_w.bps.height.compute_transform2( + max_from, + source, + &change_w.height, + |(h, current, change, ..)| { + let current_f: f64 = current.into(); + let change_f: f64 = change.into(); + let ago = current_f - change_f; + let rate = if ago == 0.0 { 0.0 } else { change_f / ago }; + (h, BasisPoints16::from(rate)) + }, + exit, + )?; + } + + Ok(()) + } +} diff --git a/crates/brk_computer/src/internal/from_height/computed/distribution.rs b/crates/brk_computer/src/internal/from_height/computed/distribution.rs deleted file mode 100644 index 83695e68c..000000000 --- a/crates/brk_computer/src/internal/from_height/computed/distribution.rs +++ /dev/null @@ -1,75 +0,0 @@ -//! ComputedFromHeight using Distribution aggregation (no sum/cumulative). -//! -//! Stored height data + LazyAggVec index views + rolling distribution windows. -//! Use for block-based metrics where sum/cumulative would be misleading -//! (e.g., activity counts that can't be deduplicated across blocks). - -use brk_error::Result; - -use brk_traversable::Traversable; -use brk_types::{Height, Version}; -use schemars::JsonSchema; -use vecdb::{Database, EagerVec, Exit, ImportableVec, PcoVec, Rw, StorageMode}; - -use crate::indexes; - -use crate::internal::{ComputedVecValue, NumericValue, RollingDistribution, WindowStarts}; - -#[derive(Traversable)] -pub struct ComputedFromHeightDistribution -where - T: ComputedVecValue + PartialOrd + JsonSchema, -{ - pub height: M::Stored>>, - #[traversable(flatten)] - pub rolling: RollingDistribution, -} - -impl ComputedFromHeightDistribution -where - T: NumericValue + JsonSchema, -{ - pub(crate) fn forced_import( - db: &Database, - name: &str, - version: Version, - indexes: &indexes::Vecs, - ) -> Result { - let height: EagerVec> = EagerVec::forced_import(db, name, version)?; - let rolling = RollingDistribution::forced_import(db, name, version, indexes)?; - - Ok(Self { height, rolling }) - } - - /// Compute height data via closure, then rolling distribution. - pub(crate) fn compute( - &mut self, - max_from: Height, - windows: &WindowStarts<'_>, - exit: &Exit, - compute_height: impl FnOnce(&mut EagerVec>) -> Result<()>, - ) -> Result<()> - where - T: Copy + Ord + From + Default, - f64: From, - { - compute_height(&mut self.height)?; - self.compute_rest(max_from, windows, exit) - } - - /// Compute rolling distribution from already-populated height data. - pub(crate) fn compute_rest( - &mut self, - max_from: Height, - windows: &WindowStarts<'_>, - exit: &Exit, - ) -> Result<()> - where - T: Copy + Ord + From + Default, - f64: From, - { - self.rolling - .compute_distribution(max_from, windows, &self.height, exit)?; - Ok(()) - } -} diff --git a/crates/brk_computer/src/internal/from_height/computed/mod.rs b/crates/brk_computer/src/internal/from_height/computed/mod.rs index 9e957e923..d71bd7517 100644 --- a/crates/brk_computer/src/internal/from_height/computed/mod.rs +++ b/crates/brk_computer/src/internal/from_height/computed/mod.rs @@ -1,13 +1,13 @@ mod aggregated; mod cumulative; mod cumulative_sum; -mod distribution; mod full; +mod delta; mod rolling_average; pub use aggregated::*; pub use cumulative::*; pub use cumulative_sum::*; -pub use distribution::*; pub use full::*; +pub use delta::*; pub use rolling_average::*; diff --git a/crates/brk_computer/src/internal/from_height/computed/rolling_average.rs b/crates/brk_computer/src/internal/from_height/computed/rolling_average.rs index 9f821093c..f7e472a51 100644 --- a/crates/brk_computer/src/internal/from_height/computed/rolling_average.rs +++ b/crates/brk_computer/src/internal/from_height/computed/rolling_average.rs @@ -37,7 +37,7 @@ where ) -> Result { let height: EagerVec> = EagerVec::forced_import(db, name, version)?; let average = - RollingWindows::forced_import(db, &format!("{name}_average"), version, indexes)?; + RollingWindows::forced_import(db, &format!("{name}_average"), version + Version::ONE, indexes)?; Ok(Self { height, average }) } diff --git a/crates/brk_computer/src/internal/from_height/percent/distribution.rs b/crates/brk_computer/src/internal/from_height/percent/distribution.rs deleted file mode 100644 index e440fcfb4..000000000 --- a/crates/brk_computer/src/internal/from_height/percent/distribution.rs +++ /dev/null @@ -1,69 +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, WindowStarts}, -}; - -use crate::internal::{ComputedFromHeightDistribution, LazyFromHeight}; - -/// Like PercentFromHeight but with rolling distribution stats on the bps data. -#[derive(Traversable)] -pub struct PercentFromHeightDistribution { - pub bps: ComputedFromHeightDistribution, - pub ratio: LazyFromHeight, - pub percent: LazyFromHeight, -} - -impl PercentFromHeightDistribution { - pub(crate) fn forced_import( - db: &Database, - name: &str, - version: Version, - indexes: &indexes::Vecs, - ) -> Result { - let bps = ComputedFromHeightDistribution::forced_import( - db, - &format!("{name}_bps"), - version, - indexes, - )?; - - let ratio = LazyFromHeight::from_height_source::( - &format!("{name}_ratio"), - version, - bps.height.read_only_boxed_clone(), - indexes, - ); - - let percent = LazyFromHeight::from_height_source::( - name, - version, - bps.height.read_only_boxed_clone(), - indexes, - ); - - Ok(Self { - bps, - ratio, - percent, - }) - } - - pub(crate) fn compute( - &mut self, - max_from: Height, - windows: &WindowStarts<'_>, - exit: &Exit, - compute_height: impl FnOnce(&mut EagerVec>) -> Result<()>, - ) -> Result<()> - where - B: Copy + Ord + From + Default, - f64: From, - { - self.bps.compute(max_from, windows, exit, compute_height) - } -} diff --git a/crates/brk_computer/src/internal/from_height/percent/mod.rs b/crates/brk_computer/src/internal/from_height/percent/mod.rs index 4ac92d769..9abd34603 100644 --- a/crates/brk_computer/src/internal/from_height/percent/mod.rs +++ b/crates/brk_computer/src/internal/from_height/percent/mod.rs @@ -1,7 +1,5 @@ mod base; -mod distribution; mod rolling_average; pub use base::*; -pub use distribution::*; pub use rolling_average::*; diff --git a/crates/brk_types/src/anyaddressindex.rs b/crates/brk_types/src/anyaddressindex.rs index 64c45746a..3ea7ea1ef 100644 --- a/crates/brk_types/src/anyaddressindex.rs +++ b/crates/brk_types/src/anyaddressindex.rs @@ -63,8 +63,9 @@ impl fmt::Display for AnyAddressIndex { impl Formattable for AnyAddressIndex { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = itoa::Buffer::new(); + f.push_str(buf.format((self.0).0)); + Ok(()) } } diff --git a/crates/brk_types/src/basis_points_16.rs b/crates/brk_types/src/basis_points_16.rs index 28d001ca7..8dbd55c00 100644 --- a/crates/brk_types/src/basis_points_16.rs +++ b/crates/brk_types/src/basis_points_16.rs @@ -165,7 +165,8 @@ impl std::fmt::Display for BasisPoints16 { impl Formattable for BasisPoints16 { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = itoa::Buffer::new(); + f.push_str(buf.format(self.0)); + Ok(()) } } diff --git a/crates/brk_types/src/basis_points_32.rs b/crates/brk_types/src/basis_points_32.rs index 93bf0b266..bed6c5921 100644 --- a/crates/brk_types/src/basis_points_32.rs +++ b/crates/brk_types/src/basis_points_32.rs @@ -138,7 +138,8 @@ impl std::fmt::Display for BasisPoints32 { impl Formattable for BasisPoints32 { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = itoa::Buffer::new(); + f.push_str(buf.format(self.0)); + Ok(()) } } diff --git a/crates/brk_types/src/basis_points_signed_16.rs b/crates/brk_types/src/basis_points_signed_16.rs index 02008cf0b..65aaf06ec 100644 --- a/crates/brk_types/src/basis_points_signed_16.rs +++ b/crates/brk_types/src/basis_points_signed_16.rs @@ -161,7 +161,8 @@ impl std::fmt::Display for BasisPointsSigned16 { impl Formattable for BasisPointsSigned16 { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = itoa::Buffer::new(); + f.push_str(buf.format(self.0)); + Ok(()) } } diff --git a/crates/brk_types/src/basis_points_signed_32.rs b/crates/brk_types/src/basis_points_signed_32.rs index f7f05d625..12117b279 100644 --- a/crates/brk_types/src/basis_points_signed_32.rs +++ b/crates/brk_types/src/basis_points_signed_32.rs @@ -167,7 +167,8 @@ impl std::fmt::Display for BasisPointsSigned32 { impl Formattable for BasisPointsSigned32 { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = itoa::Buffer::new(); + f.push_str(buf.format(self.0)); + Ok(()) } } diff --git a/crates/brk_types/src/bitcoin.rs b/crates/brk_types/src/bitcoin.rs index eb6bc2853..59965be5f 100644 --- a/crates/brk_types/src/bitcoin.rs +++ b/crates/brk_types/src/bitcoin.rs @@ -144,7 +144,8 @@ impl std::fmt::Display for Bitcoin { impl Formattable for Bitcoin { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = ryu::Buffer::new(); + f.push_str(buf.format(self.0)); + Ok(()) } } diff --git a/crates/brk_types/src/blkposition.rs b/crates/brk_types/src/blkposition.rs index dfd89a24b..3f9658708 100644 --- a/crates/brk_types/src/blkposition.rs +++ b/crates/brk_types/src/blkposition.rs @@ -40,7 +40,8 @@ impl std::fmt::Display for BlkPosition { impl Formattable for BlkPosition { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = itoa::Buffer::new(); + f.push_str(buf.format(self.0)); + Ok(()) } } diff --git a/crates/brk_types/src/cents.rs b/crates/brk_types/src/cents.rs index 4e3527d89..c37684346 100644 --- a/crates/brk_types/src/cents.rs +++ b/crates/brk_types/src/cents.rs @@ -283,7 +283,8 @@ impl std::fmt::Display for Cents { impl Formattable for Cents { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = itoa::Buffer::new(); + f.push_str(buf.format(self.0)); + Ok(()) } } diff --git a/crates/brk_types/src/cents_squared_sats.rs b/crates/brk_types/src/cents_squared_sats.rs index 4b3c7404d..007036f3c 100644 --- a/crates/brk_types/src/cents_squared_sats.rs +++ b/crates/brk_types/src/cents_squared_sats.rs @@ -110,8 +110,9 @@ impl Div for CentsSquaredSats { impl Formattable for CentsSquaredSats { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = itoa::Buffer::new(); + f.push_str(buf.format(self.0)); + Ok(()) } } diff --git a/crates/brk_types/src/day1.rs b/crates/brk_types/src/day1.rs index 3e44d75a4..19587d2fd 100644 --- a/crates/brk_types/src/day1.rs +++ b/crates/brk_types/src/day1.rs @@ -217,7 +217,8 @@ impl fmt::Display for Day1 { impl Formattable for Day1 { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = itoa::Buffer::new(); + f.push_str(buf.format(self.0)); + Ok(()) } } diff --git a/crates/brk_types/src/day3.rs b/crates/brk_types/src/day3.rs index 78585a5fb..658d92254 100644 --- a/crates/brk_types/src/day3.rs +++ b/crates/brk_types/src/day3.rs @@ -82,7 +82,8 @@ impl std::fmt::Display for Day3 { impl Formattable for Day3 { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = itoa::Buffer::new(); + f.push_str(buf.format(self.0)); + Ok(()) } } diff --git a/crates/brk_types/src/dollars.rs b/crates/brk_types/src/dollars.rs index 1f26af46e..2832b7a8d 100644 --- a/crates/brk_types/src/dollars.rs +++ b/crates/brk_types/src/dollars.rs @@ -435,7 +435,8 @@ impl std::fmt::Display for Dollars { impl Formattable for Dollars { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = ryu::Buffer::new(); + f.push_str(buf.format(self.0)); + Ok(()) } } diff --git a/crates/brk_types/src/emptyaddressindex.rs b/crates/brk_types/src/emptyaddressindex.rs index 91b19d18f..234509467 100644 --- a/crates/brk_types/src/emptyaddressindex.rs +++ b/crates/brk_types/src/emptyaddressindex.rs @@ -83,7 +83,8 @@ impl std::fmt::Display for EmptyAddressIndex { impl Formattable for EmptyAddressIndex { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = itoa::Buffer::new(); + f.push_str(buf.format((self.0).0)); + Ok(()) } } diff --git a/crates/brk_types/src/feerate.rs b/crates/brk_types/src/feerate.rs index 6a28aaf8e..51c73b4d3 100644 --- a/crates/brk_types/src/feerate.rs +++ b/crates/brk_types/src/feerate.rs @@ -123,7 +123,8 @@ impl std::fmt::Display for FeeRate { impl Formattable for FeeRate { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = ryu::Buffer::new(); + f.push_str(buf.format(self.0)); + Ok(()) } } diff --git a/crates/brk_types/src/height.rs b/crates/brk_types/src/height.rs index 2f5342ba1..b8887f172 100644 --- a/crates/brk_types/src/height.rs +++ b/crates/brk_types/src/height.rs @@ -287,8 +287,9 @@ impl std::fmt::Display for Height { impl Formattable for Height { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = itoa::Buffer::new(); + f.push_str(buf.format(self.0)); + Ok(()) } } diff --git a/crates/brk_types/src/hour12.rs b/crates/brk_types/src/hour12.rs index 8117ade76..9e110afde 100644 --- a/crates/brk_types/src/hour12.rs +++ b/crates/brk_types/src/hour12.rs @@ -82,7 +82,8 @@ impl std::fmt::Display for Hour12 { impl Formattable for Hour12 { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = itoa::Buffer::new(); + f.push_str(buf.format(self.0)); + Ok(()) } } diff --git a/crates/brk_types/src/lib.rs b/crates/brk_types/src/lib.rs index bc2a160b9..a42fbd54d 100644 --- a/crates/brk_types/src/lib.rs +++ b/crates/brk_types/src/lib.rs @@ -151,6 +151,7 @@ mod stored_bool; mod stored_f32; mod stored_f64; mod stored_i16; +mod stored_i64; mod stored_i8; mod stored_string; mod stored_u16; @@ -343,6 +344,7 @@ pub use stored_f32::*; pub use stored_f64::*; pub use stored_i8::*; pub use stored_i16::*; +pub use stored_i64::*; pub use stored_string::*; pub use stored_u8::*; pub use stored_u16::*; diff --git a/crates/brk_types/src/minute30.rs b/crates/brk_types/src/minute30.rs index 27a23a470..fd3fe6fd1 100644 --- a/crates/brk_types/src/minute30.rs +++ b/crates/brk_types/src/minute30.rs @@ -82,7 +82,8 @@ impl std::fmt::Display for Minute30 { impl Formattable for Minute30 { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = itoa::Buffer::new(); + f.push_str(buf.format(self.0)); + Ok(()) } } diff --git a/crates/brk_types/src/month1.rs b/crates/brk_types/src/month1.rs index 35c4e98ac..df6fc6290 100644 --- a/crates/brk_types/src/month1.rs +++ b/crates/brk_types/src/month1.rs @@ -136,7 +136,8 @@ impl std::fmt::Display for Month1 { impl Formattable for Month1 { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = itoa::Buffer::new(); + f.push_str(buf.format(self.0)); + Ok(()) } } diff --git a/crates/brk_types/src/month3.rs b/crates/brk_types/src/month3.rs index 6f8a6c446..e2be587df 100644 --- a/crates/brk_types/src/month3.rs +++ b/crates/brk_types/src/month3.rs @@ -130,7 +130,8 @@ impl std::fmt::Display for Month3 { impl Formattable for Month3 { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = itoa::Buffer::new(); + f.push_str(buf.format(self.0)); + Ok(()) } } diff --git a/crates/brk_types/src/month6.rs b/crates/brk_types/src/month6.rs index 2ab1169d9..ba9268788 100644 --- a/crates/brk_types/src/month6.rs +++ b/crates/brk_types/src/month6.rs @@ -122,7 +122,8 @@ impl std::fmt::Display for Month6 { impl Formattable for Month6 { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = itoa::Buffer::new(); + f.push_str(buf.format(self.0)); + Ok(()) } } diff --git a/crates/brk_types/src/opreturnindex.rs b/crates/brk_types/src/opreturnindex.rs index c41caa865..7a81cd2db 100644 --- a/crates/brk_types/src/opreturnindex.rs +++ b/crates/brk_types/src/opreturnindex.rs @@ -85,7 +85,8 @@ impl std::fmt::Display for OpReturnIndex { impl Formattable for OpReturnIndex { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = itoa::Buffer::new(); + f.push_str(buf.format((self.0).0)); + Ok(()) } } diff --git a/crates/brk_types/src/p2msoutputindex.rs b/crates/brk_types/src/p2msoutputindex.rs index 95321c234..231458923 100644 --- a/crates/brk_types/src/p2msoutputindex.rs +++ b/crates/brk_types/src/p2msoutputindex.rs @@ -85,7 +85,8 @@ impl std::fmt::Display for P2MSOutputIndex { impl Formattable for P2MSOutputIndex { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = itoa::Buffer::new(); + f.push_str(buf.format((self.0).0)); + Ok(()) } } diff --git a/crates/brk_types/src/p2pk33addressindex.rs b/crates/brk_types/src/p2pk33addressindex.rs index 4fab1db16..014586486 100644 --- a/crates/brk_types/src/p2pk33addressindex.rs +++ b/crates/brk_types/src/p2pk33addressindex.rs @@ -106,7 +106,8 @@ impl std::fmt::Display for P2PK33AddressIndex { impl Formattable for P2PK33AddressIndex { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = itoa::Buffer::new(); + f.push_str(buf.format((self.0).0)); + Ok(()) } } diff --git a/crates/brk_types/src/p2pkhaddressindex.rs b/crates/brk_types/src/p2pkhaddressindex.rs index 50b37f81c..2e806dd31 100644 --- a/crates/brk_types/src/p2pkhaddressindex.rs +++ b/crates/brk_types/src/p2pkhaddressindex.rs @@ -106,7 +106,8 @@ impl std::fmt::Display for P2PKHAddressIndex { impl Formattable for P2PKHAddressIndex { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = itoa::Buffer::new(); + f.push_str(buf.format((self.0).0)); + Ok(()) } } diff --git a/crates/brk_types/src/p2traddressindex.rs b/crates/brk_types/src/p2traddressindex.rs index 0f6269fac..2ee821206 100644 --- a/crates/brk_types/src/p2traddressindex.rs +++ b/crates/brk_types/src/p2traddressindex.rs @@ -106,7 +106,8 @@ impl std::fmt::Display for P2TRAddressIndex { impl Formattable for P2TRAddressIndex { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = itoa::Buffer::new(); + f.push_str(buf.format((self.0).0)); + Ok(()) } } diff --git a/crates/brk_types/src/pairoutputindex.rs b/crates/brk_types/src/pairoutputindex.rs index c404e6f1e..f7a403256 100644 --- a/crates/brk_types/src/pairoutputindex.rs +++ b/crates/brk_types/src/pairoutputindex.rs @@ -116,7 +116,8 @@ impl std::fmt::Display for PairOutputIndex { impl Formattable for PairOutputIndex { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = itoa::Buffer::new(); + f.push_str(buf.format(self.0)); + Ok(()) } } diff --git a/crates/brk_types/src/poolslug.rs b/crates/brk_types/src/poolslug.rs index 78c71178d..70c0052da 100644 --- a/crates/brk_types/src/poolslug.rs +++ b/crates/brk_types/src/poolslug.rs @@ -377,85 +377,34 @@ pub enum PoolSlug { } impl PoolSlug { - /// Pools with ≥0.01% all-time dominance get full metrics (rolling windows, rewards, etc.). + /// Pools with dominance above per-window thresholds get full metrics. + /// Thresholds: all-time>=1.0%, 1y>=1.0%, 1m>=0.75%, 1w>=0.5%. + /// Generated by `scripts/pool_major_threshold.py`. pub fn is_major(&self) -> bool { matches!( self, - Self::Unknown - | Self::AntPool - | Self::AsicMiner - | Self::BatPool + Self::AntPool | Self::BinancePool - | Self::BitClub - | Self::BitcoinAffiliateNetwork - | Self::BitcoinCom - | Self::BitcoinIndia - | Self::BitcoinRussia | Self::BitFury - | Self::BitMinter - | Self::Bitparking - | Self::Bixin | Self::BraiinsPool - | Self::Btcc | Self::BtcCom | Self::BtcGuild | Self::BtcTop - | Self::BtPool + | Self::Btcc | Self::BwPool - | Self::BytePool - | Self::CanoePool - | Self::CoinLab - | Self::ConnectBtc - | Self::DigitalBtc - | Self::DPool - | Self::EclipseMc | Self::Eligius - | Self::EmcdPool - | Self::ExxBw | Self::F2Pool - | Self::FiftyEightCoin | Self::FoundryUsa - | Self::GbMiners - | Self::GhashIo - | Self::Hhtt - | Self::HuobiPool - | Self::KanoPool - | Self::KncMiner - | Self::KuCoinPool - | Self::LubianCom | Self::Luxor | Self::MaraPool - | Self::MaxBtc - | Self::MegaBigPower - | Self::NiceHash - | Self::NmcBit - | Self::NovaBlock | Self::Ocean - | Self::OkExPool - | Self::Okkong - | Self::OkpoolTop - | Self::OneThash - | Self::OzCoin - | Self::PegaPool - | Self::Polmine - | Self::Pool50btc | Self::Poolin | Self::SbiCrypto | Self::SecPool - | Self::SecretSuperstar - | Self::SigmapoolCom - | Self::SoloCk | Self::SpiderPool - | Self::Tangpool - | Self::TatmasPool - | Self::TerraPool - | Self::TigerpoolNet - | Self::TripleMining - | Self::UltimusPool + | Self::Unknown | Self::ViaBtc - | Self::WayiCn | Self::WhitePool - | Self::YourbtcNet ) } } diff --git a/crates/brk_types/src/sats.rs b/crates/brk_types/src/sats.rs index 003d811a8..d354f9bb6 100644 --- a/crates/brk_types/src/sats.rs +++ b/crates/brk_types/src/sats.rs @@ -347,7 +347,8 @@ impl std::fmt::Display for Sats { impl Formattable for Sats { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = itoa::Buffer::new(); + f.push_str(buf.format(self.0)); + Ok(()) } } diff --git a/crates/brk_types/src/sats_signed.rs b/crates/brk_types/src/sats_signed.rs index a08e5fc27..e975c81ca 100644 --- a/crates/brk_types/src/sats_signed.rs +++ b/crates/brk_types/src/sats_signed.rs @@ -215,7 +215,8 @@ impl std::fmt::Display for SatsSigned { impl Formattable for SatsSigned { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = itoa::Buffer::new(); + f.push_str(buf.format(self.0)); + Ok(()) } } diff --git a/crates/brk_types/src/stored_f32.rs b/crates/brk_types/src/stored_f32.rs index 0645d2227..d466838a2 100644 --- a/crates/brk_types/src/stored_f32.rs +++ b/crates/brk_types/src/stored_f32.rs @@ -274,7 +274,8 @@ impl std::fmt::Display for StoredF32 { impl Formattable for StoredF32 { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = ryu::Buffer::new(); + f.push_str(buf.format(self.0)); + Ok(()) } } diff --git a/crates/brk_types/src/stored_i16.rs b/crates/brk_types/src/stored_i16.rs index d08e55829..b4912c539 100644 --- a/crates/brk_types/src/stored_i16.rs +++ b/crates/brk_types/src/stored_i16.rs @@ -118,7 +118,8 @@ impl std::fmt::Display for StoredI16 { impl Formattable for StoredI16 { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = itoa::Buffer::new(); + f.push_str(buf.format(self.0)); + Ok(()) } } diff --git a/crates/brk_types/src/stored_i64.rs b/crates/brk_types/src/stored_i64.rs new file mode 100644 index 000000000..d185100d2 --- /dev/null +++ b/crates/brk_types/src/stored_i64.rs @@ -0,0 +1,140 @@ +use std::ops::{Add, AddAssign, Div, Sub, SubAssign}; + +use derive_more::Deref; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use vecdb::{CheckedSub, Formattable, Pco, PrintableIndex}; + +/// Fixed-size 64-bit signed integer optimized for on-disk storage +#[derive( + Debug, + Default, + Deref, + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize, + Pco, + JsonSchema, +)] +pub struct StoredI64(i64); + +impl StoredI64 { + pub const ZERO: Self = Self(0); + + pub fn new(v: i64) -> Self { + Self(v) + } +} + +impl From for StoredI64 { + #[inline] + fn from(value: i64) -> Self { + Self(value) + } +} + +impl From for i64 { + #[inline] + fn from(value: StoredI64) -> Self { + value.0 + } +} + +impl From for StoredI64 { + #[inline] + fn from(value: usize) -> Self { + Self(value as i64) + } +} + +impl From for usize { + #[inline] + fn from(value: StoredI64) -> Self { + value.0 as usize + } +} + +impl CheckedSub for StoredI64 { + fn checked_sub(self, rhs: Self) -> Option { + self.0.checked_sub(rhs.0).map(Self) + } +} + +impl Div for StoredI64 { + type Output = Self; + fn div(self, rhs: usize) -> Self::Output { + Self(self.0 / rhs as i64) + } +} + +impl Add for StoredI64 { + type Output = Self; + fn add(self, rhs: Self) -> Self::Output { + Self(self.0 + rhs.0) + } +} + +impl AddAssign for StoredI64 { + fn add_assign(&mut self, rhs: Self) { + *self = *self + rhs + } +} + +impl Sub for StoredI64 { + type Output = Self; + fn sub(self, rhs: Self) -> Self::Output { + Self(self.0 - rhs.0) + } +} + +impl SubAssign for StoredI64 { + fn sub_assign(&mut self, rhs: Self) { + *self = *self - rhs + } +} + +impl From for StoredI64 { + #[inline] + fn from(value: f64) -> Self { + Self(value as i64) + } +} + +impl From for f64 { + #[inline] + fn from(value: StoredI64) -> Self { + value.0 as f64 + } +} + +impl PrintableIndex for StoredI64 { + fn to_string() -> &'static str { + "i64" + } + + fn to_possible_strings() -> &'static [&'static str] { + &["i64"] + } +} + +impl std::fmt::Display for StoredI64 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut buf = itoa::Buffer::new(); + let str = buf.format(self.0); + f.write_str(str) + } +} + +impl Formattable for StoredI64 { + #[inline(always)] + fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { + let mut buf = itoa::Buffer::new(); + f.push_str(buf.format(self.0)); + Ok(()) + } +} diff --git a/crates/brk_types/src/stored_i8.rs b/crates/brk_types/src/stored_i8.rs index 828fb15a5..2b641ad27 100644 --- a/crates/brk_types/src/stored_i8.rs +++ b/crates/brk_types/src/stored_i8.rs @@ -118,7 +118,8 @@ impl std::fmt::Display for StoredI8 { impl Formattable for StoredI8 { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = itoa::Buffer::new(); + f.push_str(buf.format(self.0)); + Ok(()) } } diff --git a/crates/brk_types/src/stored_u16.rs b/crates/brk_types/src/stored_u16.rs index 88745a80a..d75b722ee 100644 --- a/crates/brk_types/src/stored_u16.rs +++ b/crates/brk_types/src/stored_u16.rs @@ -209,7 +209,8 @@ impl std::fmt::Display for StoredU16 { impl Formattable for StoredU16 { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = itoa::Buffer::new(); + f.push_str(buf.format(self.0)); + Ok(()) } } diff --git a/crates/brk_types/src/stored_u32.rs b/crates/brk_types/src/stored_u32.rs index ef0536603..09bd8282c 100644 --- a/crates/brk_types/src/stored_u32.rs +++ b/crates/brk_types/src/stored_u32.rs @@ -261,7 +261,8 @@ impl std::fmt::Display for StoredU32 { impl Formattable for StoredU32 { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = itoa::Buffer::new(); + f.push_str(buf.format(self.0)); + Ok(()) } } diff --git a/crates/brk_types/src/stored_u64.rs b/crates/brk_types/src/stored_u64.rs index 001392674..af0f851bd 100644 --- a/crates/brk_types/src/stored_u64.rs +++ b/crates/brk_types/src/stored_u64.rs @@ -276,7 +276,8 @@ impl std::fmt::Display for StoredU64 { impl Formattable for StoredU64 { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = itoa::Buffer::new(); + f.push_str(buf.format(self.0)); + Ok(()) } } diff --git a/crates/brk_types/src/stored_u8.rs b/crates/brk_types/src/stored_u8.rs index 3876c76de..25b543e5b 100644 --- a/crates/brk_types/src/stored_u8.rs +++ b/crates/brk_types/src/stored_u8.rs @@ -109,7 +109,8 @@ impl std::fmt::Display for StoredU8 { impl Formattable for StoredU8 { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = itoa::Buffer::new(); + f.push_str(buf.format(self.0)); + Ok(()) } } diff --git a/crates/brk_types/src/timestamp.rs b/crates/brk_types/src/timestamp.rs index 861e75a7e..55c2cd594 100644 --- a/crates/brk_types/src/timestamp.rs +++ b/crates/brk_types/src/timestamp.rs @@ -190,7 +190,8 @@ impl std::fmt::Display for Timestamp { impl Formattable for Timestamp { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = itoa::Buffer::new(); + f.push_str(buf.format(self.0)); + Ok(()) } } diff --git a/crates/brk_types/src/txindex.rs b/crates/brk_types/src/txindex.rs index dcc15d404..4d8a9f835 100644 --- a/crates/brk_types/src/txindex.rs +++ b/crates/brk_types/src/txindex.rs @@ -171,7 +171,8 @@ impl std::fmt::Display for TxIndex { impl Formattable for TxIndex { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = itoa::Buffer::new(); + f.push_str(buf.format(self.0)); + Ok(()) } } diff --git a/crates/brk_types/src/txinindex.rs b/crates/brk_types/src/txinindex.rs index 42a4e9e1e..b7df41ffb 100644 --- a/crates/brk_types/src/txinindex.rs +++ b/crates/brk_types/src/txinindex.rs @@ -132,7 +132,8 @@ impl std::fmt::Display for TxInIndex { impl Formattable for TxInIndex { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = itoa::Buffer::new(); + f.push_str(buf.format(self.0)); + Ok(()) } } diff --git a/crates/brk_types/src/txoutindex.rs b/crates/brk_types/src/txoutindex.rs index 30a1f21da..e5e60a28a 100644 --- a/crates/brk_types/src/txoutindex.rs +++ b/crates/brk_types/src/txoutindex.rs @@ -133,7 +133,8 @@ impl std::fmt::Display for TxOutIndex { impl Formattable for TxOutIndex { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = itoa::Buffer::new(); + f.push_str(buf.format(self.0)); + Ok(()) } } diff --git a/crates/brk_types/src/txversion.rs b/crates/brk_types/src/txversion.rs index 1074dbab0..d2f2111ec 100644 --- a/crates/brk_types/src/txversion.rs +++ b/crates/brk_types/src/txversion.rs @@ -66,7 +66,8 @@ impl std::fmt::Display for TxVersion { impl Formattable for TxVersion { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = itoa::Buffer::new(); + f.push_str(buf.format(self.0)); + Ok(()) } } diff --git a/crates/brk_types/src/typeindex.rs b/crates/brk_types/src/typeindex.rs index dbccf1d41..3b6a7be88 100644 --- a/crates/brk_types/src/typeindex.rs +++ b/crates/brk_types/src/typeindex.rs @@ -148,7 +148,8 @@ impl std::fmt::Display for TypeIndex { impl Formattable for TypeIndex { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = itoa::Buffer::new(); + f.push_str(buf.format(self.0)); + Ok(()) } } diff --git a/crates/brk_types/src/vout.rs b/crates/brk_types/src/vout.rs index 69601dff2..6fb0fdf64 100644 --- a/crates/brk_types/src/vout.rs +++ b/crates/brk_types/src/vout.rs @@ -99,7 +99,8 @@ impl std::fmt::Display for Vout { impl Formattable for Vout { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = itoa::Buffer::new(); + f.push_str(buf.format(self.0)); + Ok(()) } } diff --git a/crates/brk_types/src/vsize.rs b/crates/brk_types/src/vsize.rs index 4ae7c368d..bc15897b0 100644 --- a/crates/brk_types/src/vsize.rs +++ b/crates/brk_types/src/vsize.rs @@ -122,7 +122,8 @@ impl std::fmt::Display for VSize { impl Formattable for VSize { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = itoa::Buffer::new(); + f.push_str(buf.format(self.0)); + Ok(()) } } diff --git a/crates/brk_types/src/week1.rs b/crates/brk_types/src/week1.rs index 7a3716b0d..001e85e08 100644 --- a/crates/brk_types/src/week1.rs +++ b/crates/brk_types/src/week1.rs @@ -146,7 +146,8 @@ impl std::fmt::Display for Week1 { impl Formattable for Week1 { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = itoa::Buffer::new(); + f.push_str(buf.format(self.0)); + Ok(()) } } diff --git a/crates/brk_types/src/weight.rs b/crates/brk_types/src/weight.rs index d44bda0fe..f53f5d2a2 100644 --- a/crates/brk_types/src/weight.rs +++ b/crates/brk_types/src/weight.rs @@ -138,7 +138,8 @@ impl std::fmt::Display for Weight { impl Formattable for Weight { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = itoa::Buffer::new(); + f.push_str(buf.format(self.0)); + Ok(()) } } diff --git a/crates/brk_types/src/year.rs b/crates/brk_types/src/year.rs index 371ec8ce9..0072722b5 100644 --- a/crates/brk_types/src/year.rs +++ b/crates/brk_types/src/year.rs @@ -125,7 +125,8 @@ impl std::fmt::Display for Year { impl Formattable for Year { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = itoa::Buffer::new(); + f.push_str(buf.format(self.0)); + Ok(()) } } diff --git a/crates/brk_types/src/year1.rs b/crates/brk_types/src/year1.rs index 873573d8c..9ba687c4b 100644 --- a/crates/brk_types/src/year1.rs +++ b/crates/brk_types/src/year1.rs @@ -143,7 +143,8 @@ impl std::fmt::Display for Year1 { impl Formattable for Year1 { #[inline(always)] fn fmt_csv(&self, f: &mut String) -> std::fmt::Result { - use std::fmt::Write; - write!(f, "{}", self) + let mut buf = itoa::Buffer::new(); + f.push_str(buf.format(self.0)); + Ok(()) } } diff --git a/modules/brk-client/index.js b/modules/brk-client/index.js index c66fba177..1f566c3ab 100644 --- a/modules/brk-client/index.js +++ b/modules/brk-client/index.js @@ -2051,6 +2051,47 @@ function createRatioPattern(client, acc) { }; } +/** + * @typedef {Object} GrossInvestedInvestorNegNetSupplyUnrealizedPattern + * @property {CentsUsdPattern} grossPnl + * @property {CentsUsdPattern} investedCapitalInLoss + * @property {MetricPattern18} investedCapitalInLossRaw + * @property {CentsUsdPattern} investedCapitalInProfit + * @property {MetricPattern18} investedCapitalInProfitRaw + * @property {MetricPattern18} investorCapInLossRaw + * @property {MetricPattern18} investorCapInProfitRaw + * @property {MetricPattern1} negUnrealizedLoss + * @property {CentsUsdPattern} netUnrealizedPnl + * @property {BtcCentsSatsUsdPattern} supplyInLoss + * @property {BtcCentsSatsUsdPattern} supplyInProfit + * @property {CentsUsdPattern} unrealizedLoss + * @property {CentsUsdPattern} unrealizedProfit + */ + +/** + * Create a GrossInvestedInvestorNegNetSupplyUnrealizedPattern pattern node + * @param {BrkClientBase} client + * @param {string} acc - Accumulated metric name + * @returns {GrossInvestedInvestorNegNetSupplyUnrealizedPattern} + */ +function createGrossInvestedInvestorNegNetSupplyUnrealizedPattern(client, acc) { + return { + grossPnl: createCentsUsdPattern(client, _m(acc, 'unrealized_gross_pnl')), + investedCapitalInLoss: createCentsUsdPattern(client, _m(acc, 'invested_capital_in_loss')), + investedCapitalInLossRaw: createMetricPattern18(client, _m(acc, 'invested_capital_in_loss_raw')), + investedCapitalInProfit: createCentsUsdPattern(client, _m(acc, 'invested_capital_in_profit')), + investedCapitalInProfitRaw: createMetricPattern18(client, _m(acc, 'invested_capital_in_profit_raw')), + investorCapInLossRaw: createMetricPattern18(client, _m(acc, 'investor_cap_in_loss_raw')), + investorCapInProfitRaw: createMetricPattern18(client, _m(acc, 'investor_cap_in_profit_raw')), + negUnrealizedLoss: createMetricPattern1(client, _m(acc, 'neg_unrealized_loss')), + netUnrealizedPnl: createCentsUsdPattern(client, _m(acc, 'net_unrealized_pnl')), + supplyInLoss: createBtcCentsSatsUsdPattern(client, _m(acc, 'supply_in_loss')), + supplyInProfit: createBtcCentsSatsUsdPattern(client, _m(acc, 'supply_in_profit')), + unrealizedLoss: createCentsUsdPattern(client, _m(acc, 'unrealized_loss')), + unrealizedProfit: createCentsUsdPattern(client, _m(acc, 'unrealized_profit')), + }; +} + /** * @typedef {Object} _10y1m1w1y2y3m3y4y5y6m6y8yPattern2 * @property {BpsPercentRatioPattern} _10y @@ -2314,15 +2355,15 @@ function createAverageGainsLossesRsiStochPattern(client, acc) { /** * @typedef {Object} AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern - * @property {ChangeCountPattern} all - * @property {ChangeCountPattern} p2a - * @property {ChangeCountPattern} p2pk33 - * @property {ChangeCountPattern} p2pk65 - * @property {ChangeCountPattern} p2pkh - * @property {ChangeCountPattern} p2sh - * @property {ChangeCountPattern} p2tr - * @property {ChangeCountPattern} p2wpkh - * @property {ChangeCountPattern} p2wsh + * @property {MetricPattern1} all + * @property {MetricPattern1} p2a + * @property {MetricPattern1} p2pk33 + * @property {MetricPattern1} p2pk65 + * @property {MetricPattern1} p2pkh + * @property {MetricPattern1} p2sh + * @property {MetricPattern1} p2tr + * @property {MetricPattern1} p2wpkh + * @property {MetricPattern1} p2wsh */ /** @@ -2333,15 +2374,15 @@ function createAverageGainsLossesRsiStochPattern(client, acc) { */ function createAllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern(client, acc) { return { - all: createChangeCountPattern(client, acc), - p2a: createChangeCountPattern(client, _p('p2a', acc)), - p2pk33: createChangeCountPattern(client, _p('p2pk33', acc)), - p2pk65: createChangeCountPattern(client, _p('p2pk65', acc)), - p2pkh: createChangeCountPattern(client, _p('p2pkh', acc)), - p2sh: createChangeCountPattern(client, _p('p2sh', acc)), - p2tr: createChangeCountPattern(client, _p('p2tr', acc)), - p2wpkh: createChangeCountPattern(client, _p('p2wpkh', acc)), - p2wsh: createChangeCountPattern(client, _p('p2wsh', acc)), + all: createMetricPattern1(client, acc), + p2a: createMetricPattern1(client, _p('p2a', acc)), + p2pk33: createMetricPattern1(client, _p('p2pk33', acc)), + p2pk65: createMetricPattern1(client, _p('p2pk65', acc)), + p2pkh: createMetricPattern1(client, _p('p2pkh', acc)), + p2sh: createMetricPattern1(client, _p('p2sh', acc)), + p2tr: createMetricPattern1(client, _p('p2tr', acc)), + p2wpkh: createMetricPattern1(client, _p('p2wpkh', acc)), + p2wsh: createMetricPattern1(client, _p('p2wsh', acc)), }; } @@ -2378,39 +2419,6 @@ function createAverageMaxMedianMinPct10Pct25Pct75Pct90SumPattern2(client, acc) { }; } -/** - * @typedef {Object} AverageHeightMaxMedianMinPct10Pct25Pct75Pct90Pattern - * @property {_1m1w1y24hPattern} average - * @property {MetricPattern18} height - * @property {_1m1w1y24hPattern} max - * @property {_1m1w1y24hPattern} median - * @property {_1m1w1y24hPattern} min - * @property {_1m1w1y24hPattern} pct10 - * @property {_1m1w1y24hPattern} pct25 - * @property {_1m1w1y24hPattern} pct75 - * @property {_1m1w1y24hPattern} pct90 - */ - -/** - * Create a AverageHeightMaxMedianMinPct10Pct25Pct75Pct90Pattern pattern node - * @param {BrkClientBase} client - * @param {string} acc - Accumulated metric name - * @returns {AverageHeightMaxMedianMinPct10Pct25Pct75Pct90Pattern} - */ -function createAverageHeightMaxMedianMinPct10Pct25Pct75Pct90Pattern(client, acc) { - return { - average: create_1m1w1y24hPattern(client, _m(acc, 'average')), - height: createMetricPattern18(client, acc), - max: create_1m1w1y24hPattern(client, _m(acc, 'max')), - median: create_1m1w1y24hPattern(client, _m(acc, 'median')), - min: create_1m1w1y24hPattern(client, _m(acc, 'min')), - pct10: create_1m1w1y24hPattern(client, _m(acc, 'p10')), - pct25: create_1m1w1y24hPattern(client, _m(acc, 'p25')), - pct75: create_1m1w1y24hPattern(client, _m(acc, 'p75')), - pct90: create_1m1w1y24hPattern(client, _m(acc, 'p90')), - }; -} - /** * @typedef {Object} AverageMaxMedianMinPct10Pct25Pct75Pct90SumPattern * @property {_1m1w1y24hPattern} average @@ -2576,7 +2584,7 @@ function create_10y2y3y4y5y6y8yPattern(client, acc) { * @property {MvrvNegNetRealizedSentSoprValuePattern} realized * @property {InvestedNetNuplSupplyUnrealizedPattern} relative * @property {ChangeHalvedTotalPattern} supply - * @property {GreedGrossInvestedInvestorNegNetPainSupplyUnrealizedPattern} unrealized + * @property {GrossInvestedInvestorNegNetSupplyUnrealizedPattern} unrealized */ /** @@ -2593,7 +2601,7 @@ function createActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern(client realized: createMvrvNegNetRealizedSentSoprValuePattern(client, acc), relative: createInvestedNetNuplSupplyUnrealizedPattern(client, acc), supply: createChangeHalvedTotalPattern(client, _m(acc, 'supply')), - unrealized: createGreedGrossInvestedInvestorNegNetPainSupplyUnrealizedPattern(client, acc), + unrealized: createGrossInvestedInvestorNegNetSupplyUnrealizedPattern(client, acc), }; } @@ -3039,27 +3047,6 @@ function createBaseCumulativeSumPattern(client, acc) { }; } -/** - * @typedef {Object} BpsPercentRatioPattern3 - * @property {AverageHeightMaxMedianMinPct10Pct25Pct75Pct90Pattern} bps - * @property {MetricPattern1} percent - * @property {MetricPattern1} ratio - */ - -/** - * Create a BpsPercentRatioPattern3 pattern node - * @param {BrkClientBase} client - * @param {string} acc - Accumulated metric name - * @returns {BpsPercentRatioPattern3} - */ -function createBpsPercentRatioPattern3(client, acc) { - return { - bps: createAverageHeightMaxMedianMinPct10Pct25Pct75Pct90Pattern(client, _m(acc, 'bps')), - percent: createMetricPattern1(client, acc), - ratio: createMetricPattern1(client, _m(acc, 'ratio')), - }; -} - /** * @typedef {Object} BpsPercentRatioPattern * @property {MetricPattern1} bps @@ -3326,21 +3313,21 @@ function createCentsUsdPattern(client, acc) { } /** - * @typedef {Object} ChangeCountPattern - * @property {MetricPattern1} change1m - * @property {MetricPattern1} count + * @typedef {Object} ChangeRatePattern + * @property {_1m1w1y24hPattern} change + * @property {_1m1w1y24hPattern2} rate */ /** - * Create a ChangeCountPattern pattern node + * Create a ChangeRatePattern pattern node * @param {BrkClientBase} client * @param {string} acc - Accumulated metric name - * @returns {ChangeCountPattern} + * @returns {ChangeRatePattern} */ -function createChangeCountPattern(client, acc) { +function createChangeRatePattern(client, acc) { return { - change1m: createMetricPattern1(client, _m(acc, 'change_1m')), - count: createMetricPattern1(client, acc), + change: create_1m1w1y24hPattern(client, _m(acc, 'change')), + rate: create_1m1w1y24hPattern2(client, _m(acc, 'rate')), }; } @@ -4721,9 +4708,9 @@ function create_2wPattern(client, acc) { * @property {AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern} addrCount * @property {AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern} emptyAddrCount * @property {MetricsTree_Distribution_AddressActivity} addressActivity - * @property {MetricsTree_Distribution_TotalAddrCount} totalAddrCount + * @property {AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern} totalAddrCount * @property {MetricsTree_Distribution_NewAddrCount} newAddrCount - * @property {MetricsTree_Distribution_GrowthRate} growthRate + * @property {MetricsTree_Distribution_Delta} delta * @property {MetricPattern34} fundedaddressindex * @property {MetricPattern35} emptyaddressindex */ @@ -5074,19 +5061,6 @@ function create_2wPattern(client, acc) { * @property {BothReactivatedReceivingSendingPattern} p2a */ -/** - * @typedef {Object} MetricsTree_Distribution_TotalAddrCount - * @property {MetricPattern1} all - * @property {MetricPattern1} p2pk65 - * @property {MetricPattern1} p2pk33 - * @property {MetricPattern1} p2pkh - * @property {MetricPattern1} p2sh - * @property {MetricPattern1} p2wpkh - * @property {MetricPattern1} p2wsh - * @property {MetricPattern1} p2tr - * @property {MetricPattern1} p2a - */ - /** * @typedef {Object} MetricsTree_Distribution_NewAddrCount * @property {AverageCumulativeHeightMaxMedianMinPct10Pct25Pct75Pct90SumPattern} all @@ -5101,16 +5075,16 @@ function create_2wPattern(client, acc) { */ /** - * @typedef {Object} MetricsTree_Distribution_GrowthRate - * @property {BpsPercentRatioPattern3} all - * @property {BpsPercentRatioPattern3} p2pk65 - * @property {BpsPercentRatioPattern3} p2pk33 - * @property {BpsPercentRatioPattern3} p2pkh - * @property {BpsPercentRatioPattern3} p2sh - * @property {BpsPercentRatioPattern3} p2wpkh - * @property {BpsPercentRatioPattern3} p2wsh - * @property {BpsPercentRatioPattern3} p2tr - * @property {BpsPercentRatioPattern3} p2a + * @typedef {Object} MetricsTree_Distribution_Delta + * @property {ChangeRatePattern} all + * @property {ChangeRatePattern} p2pk65 + * @property {ChangeRatePattern} p2pk33 + * @property {ChangeRatePattern} p2pkh + * @property {ChangeRatePattern} p2sh + * @property {ChangeRatePattern} p2wpkh + * @property {ChangeRatePattern} p2wsh + * @property {ChangeRatePattern} p2tr + * @property {ChangeRatePattern} p2a */ /** @@ -7270,17 +7244,7 @@ class BrkClient extends BrkClientBase { p2tr: createBothReactivatedReceivingSendingPattern(this, 'p2tr_address_activity'), p2a: createBothReactivatedReceivingSendingPattern(this, 'p2a_address_activity'), }, - totalAddrCount: { - all: createMetricPattern1(this, 'total_addr_count'), - p2pk65: createMetricPattern1(this, 'p2pk65_total_addr_count'), - p2pk33: createMetricPattern1(this, 'p2pk33_total_addr_count'), - p2pkh: createMetricPattern1(this, 'p2pkh_total_addr_count'), - p2sh: createMetricPattern1(this, 'p2sh_total_addr_count'), - p2wpkh: createMetricPattern1(this, 'p2wpkh_total_addr_count'), - p2wsh: createMetricPattern1(this, 'p2wsh_total_addr_count'), - p2tr: createMetricPattern1(this, 'p2tr_total_addr_count'), - p2a: createMetricPattern1(this, 'p2a_total_addr_count'), - }, + totalAddrCount: createAllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern(this, 'total_addr_count'), newAddrCount: { all: createAverageCumulativeHeightMaxMedianMinPct10Pct25Pct75Pct90SumPattern(this, 'new_addr_count'), p2pk65: createAverageCumulativeHeightMaxMedianMinPct10Pct25Pct75Pct90SumPattern(this, 'p2pk65_new_addr_count'), @@ -7292,16 +7256,16 @@ class BrkClient extends BrkClientBase { p2tr: createAverageCumulativeHeightMaxMedianMinPct10Pct25Pct75Pct90SumPattern(this, 'p2tr_new_addr_count'), p2a: createAverageCumulativeHeightMaxMedianMinPct10Pct25Pct75Pct90SumPattern(this, 'p2a_new_addr_count'), }, - growthRate: { - all: createBpsPercentRatioPattern3(this, 'growth_rate'), - p2pk65: createBpsPercentRatioPattern3(this, 'p2pk65_growth_rate'), - p2pk33: createBpsPercentRatioPattern3(this, 'p2pk33_growth_rate'), - p2pkh: createBpsPercentRatioPattern3(this, 'p2pkh_growth_rate'), - p2sh: createBpsPercentRatioPattern3(this, 'p2sh_growth_rate'), - p2wpkh: createBpsPercentRatioPattern3(this, 'p2wpkh_growth_rate'), - p2wsh: createBpsPercentRatioPattern3(this, 'p2wsh_growth_rate'), - p2tr: createBpsPercentRatioPattern3(this, 'p2tr_growth_rate'), - p2a: createBpsPercentRatioPattern3(this, 'p2a_growth_rate'), + delta: { + all: createChangeRatePattern(this, 'addr_count'), + p2pk65: createChangeRatePattern(this, 'p2pk65_addr_count'), + p2pk33: createChangeRatePattern(this, 'p2pk33_addr_count'), + p2pkh: createChangeRatePattern(this, 'p2pkh_addr_count'), + p2sh: createChangeRatePattern(this, 'p2sh_addr_count'), + p2wpkh: createChangeRatePattern(this, 'p2wpkh_addr_count'), + p2wsh: createChangeRatePattern(this, 'p2wsh_addr_count'), + p2tr: createChangeRatePattern(this, 'p2tr_addr_count'), + p2a: createChangeRatePattern(this, 'p2a_addr_count'), }, fundedaddressindex: createMetricPattern34(this, 'fundedaddressindex'), emptyaddressindex: createMetricPattern35(this, 'emptyaddressindex'), diff --git a/packages/brk_client/brk_client/__init__.py b/packages/brk_client/brk_client/__init__.py index f3b1f5101..cf7939211 100644 --- a/packages/brk_client/brk_client/__init__.py +++ b/packages/brk_client/brk_client/__init__.py @@ -2293,6 +2293,25 @@ class RatioPattern: self.ratio_sma_1m: BpsRatioPattern = BpsRatioPattern(client, _m(acc, 'sma_1m')) self.ratio_sma_1w: BpsRatioPattern = BpsRatioPattern(client, _m(acc, 'sma_1w')) +class GrossInvestedInvestorNegNetSupplyUnrealizedPattern: + """Pattern struct for repeated tree structure.""" + + def __init__(self, client: BrkClientBase, acc: str): + """Create pattern node with accumulated metric name.""" + self.gross_pnl: CentsUsdPattern = CentsUsdPattern(client, _m(acc, 'unrealized_gross_pnl')) + self.invested_capital_in_loss: CentsUsdPattern = CentsUsdPattern(client, _m(acc, 'invested_capital_in_loss')) + self.invested_capital_in_loss_raw: MetricPattern18[CentsSats] = MetricPattern18(client, _m(acc, 'invested_capital_in_loss_raw')) + self.invested_capital_in_profit: CentsUsdPattern = CentsUsdPattern(client, _m(acc, 'invested_capital_in_profit')) + self.invested_capital_in_profit_raw: MetricPattern18[CentsSats] = MetricPattern18(client, _m(acc, 'invested_capital_in_profit_raw')) + self.investor_cap_in_loss_raw: MetricPattern18[CentsSquaredSats] = MetricPattern18(client, _m(acc, 'investor_cap_in_loss_raw')) + self.investor_cap_in_profit_raw: MetricPattern18[CentsSquaredSats] = MetricPattern18(client, _m(acc, 'investor_cap_in_profit_raw')) + self.neg_unrealized_loss: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'neg_unrealized_loss')) + self.net_unrealized_pnl: CentsUsdPattern = CentsUsdPattern(client, _m(acc, 'net_unrealized_pnl')) + self.supply_in_loss: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, _m(acc, 'supply_in_loss')) + self.supply_in_profit: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, _m(acc, 'supply_in_profit')) + self.unrealized_loss: CentsUsdPattern = CentsUsdPattern(client, _m(acc, 'unrealized_loss')) + self.unrealized_profit: CentsUsdPattern = CentsUsdPattern(client, _m(acc, 'unrealized_profit')) + class _10y1m1w1y2y3m3y4y5y6m6y8yPattern2: """Pattern struct for repeated tree structure.""" @@ -2418,15 +2437,15 @@ class AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern: def __init__(self, client: BrkClientBase, acc: str): """Create pattern node with accumulated metric name.""" - self.all: ChangeCountPattern = ChangeCountPattern(client, acc) - self.p2a: ChangeCountPattern = ChangeCountPattern(client, _p('p2a', acc)) - self.p2pk33: ChangeCountPattern = ChangeCountPattern(client, _p('p2pk33', acc)) - self.p2pk65: ChangeCountPattern = ChangeCountPattern(client, _p('p2pk65', acc)) - self.p2pkh: ChangeCountPattern = ChangeCountPattern(client, _p('p2pkh', acc)) - self.p2sh: ChangeCountPattern = ChangeCountPattern(client, _p('p2sh', acc)) - self.p2tr: ChangeCountPattern = ChangeCountPattern(client, _p('p2tr', acc)) - self.p2wpkh: ChangeCountPattern = ChangeCountPattern(client, _p('p2wpkh', acc)) - self.p2wsh: ChangeCountPattern = ChangeCountPattern(client, _p('p2wsh', acc)) + self.all: MetricPattern1[StoredU64] = MetricPattern1(client, acc) + self.p2a: MetricPattern1[StoredU64] = MetricPattern1(client, _p('p2a', acc)) + self.p2pk33: MetricPattern1[StoredU64] = MetricPattern1(client, _p('p2pk33', acc)) + self.p2pk65: MetricPattern1[StoredU64] = MetricPattern1(client, _p('p2pk65', acc)) + self.p2pkh: MetricPattern1[StoredU64] = MetricPattern1(client, _p('p2pkh', acc)) + self.p2sh: MetricPattern1[StoredU64] = MetricPattern1(client, _p('p2sh', acc)) + self.p2tr: MetricPattern1[StoredU64] = MetricPattern1(client, _p('p2tr', acc)) + self.p2wpkh: MetricPattern1[StoredU64] = MetricPattern1(client, _p('p2wpkh', acc)) + self.p2wsh: MetricPattern1[StoredU64] = MetricPattern1(client, _p('p2wsh', acc)) class AverageMaxMedianMinPct10Pct25Pct75Pct90SumPattern2: """Pattern struct for repeated tree structure.""" @@ -2443,21 +2462,6 @@ class AverageMaxMedianMinPct10Pct25Pct75Pct90SumPattern2: self.pct90: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, _m(acc, 'p90')) self.sum: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, _m(acc, 'sum')) -class AverageHeightMaxMedianMinPct10Pct25Pct75Pct90Pattern: - """Pattern struct for repeated tree structure.""" - - def __init__(self, client: BrkClientBase, acc: str): - """Create pattern node with accumulated metric name.""" - self.average: _1m1w1y24hPattern[BasisPoints16] = _1m1w1y24hPattern(client, _m(acc, 'average')) - self.height: MetricPattern18[BasisPoints16] = MetricPattern18(client, acc) - self.max: _1m1w1y24hPattern[BasisPoints16] = _1m1w1y24hPattern(client, _m(acc, 'max')) - self.median: _1m1w1y24hPattern[BasisPoints16] = _1m1w1y24hPattern(client, _m(acc, 'median')) - self.min: _1m1w1y24hPattern[BasisPoints16] = _1m1w1y24hPattern(client, _m(acc, 'min')) - self.pct10: _1m1w1y24hPattern[BasisPoints16] = _1m1w1y24hPattern(client, _m(acc, 'p10')) - self.pct25: _1m1w1y24hPattern[BasisPoints16] = _1m1w1y24hPattern(client, _m(acc, 'p25')) - self.pct75: _1m1w1y24hPattern[BasisPoints16] = _1m1w1y24hPattern(client, _m(acc, 'p75')) - self.pct90: _1m1w1y24hPattern[BasisPoints16] = _1m1w1y24hPattern(client, _m(acc, 'p90')) - class AverageMaxMedianMinPct10Pct25Pct75Pct90SumPattern: """Pattern struct for repeated tree structure.""" @@ -2539,7 +2543,7 @@ class ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern: self.realized: MvrvNegNetRealizedSentSoprValuePattern = MvrvNegNetRealizedSentSoprValuePattern(client, acc) self.relative: InvestedNetNuplSupplyUnrealizedPattern = InvestedNetNuplSupplyUnrealizedPattern(client, acc) self.supply: ChangeHalvedTotalPattern = ChangeHalvedTotalPattern(client, _m(acc, 'supply')) - self.unrealized: GreedGrossInvestedInvestorNegNetPainSupplyUnrealizedPattern = GreedGrossInvestedInvestorNegNetPainSupplyUnrealizedPattern(client, acc) + self.unrealized: GrossInvestedInvestorNegNetSupplyUnrealizedPattern = GrossInvestedInvestorNegNetSupplyUnrealizedPattern(client, acc) class MvrvRealizedPattern: """Pattern struct for repeated tree structure.""" @@ -2733,15 +2737,6 @@ class BaseCumulativeSumPattern: self.cumulative: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, _m(acc, 'cumulative')) self.sum: _1m1w1y24hPattern5 = _1m1w1y24hPattern5(client, _m(acc, 'sum')) -class BpsPercentRatioPattern3: - """Pattern struct for repeated tree structure.""" - - def __init__(self, client: BrkClientBase, acc: str): - """Create pattern node with accumulated metric name.""" - self.bps: AverageHeightMaxMedianMinPct10Pct25Pct75Pct90Pattern = AverageHeightMaxMedianMinPct10Pct25Pct75Pct90Pattern(client, _m(acc, 'bps')) - self.percent: MetricPattern1[StoredF32] = MetricPattern1(client, acc) - self.ratio: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'ratio')) - class BpsPercentRatioPattern: """Pattern struct for repeated tree structure.""" @@ -2853,13 +2848,13 @@ class CentsUsdPattern: self.cents: MetricPattern1[Cents] = MetricPattern1(client, _m(acc, 'cents')) self.usd: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'usd')) -class ChangeCountPattern: +class ChangeRatePattern: """Pattern struct for repeated tree structure.""" def __init__(self, client: BrkClientBase, acc: str): """Create pattern node with accumulated metric name.""" - self.change_1m: MetricPattern1[StoredF64] = MetricPattern1(client, _m(acc, 'change_1m')) - self.count: MetricPattern1[StoredU64] = MetricPattern1(client, acc) + self.change: _1m1w1y24hPattern[StoredU64] = _1m1w1y24hPattern(client, _m(acc, 'change')) + self.rate: _1m1w1y24hPattern2 = _1m1w1y24hPattern2(client, _m(acc, 'rate')) class MaxMinPattern: """Pattern struct for repeated tree structure.""" @@ -4590,20 +4585,6 @@ class MetricsTree_Distribution_AddressActivity: self.p2tr: BothReactivatedReceivingSendingPattern = BothReactivatedReceivingSendingPattern(client, 'p2tr_address_activity') self.p2a: BothReactivatedReceivingSendingPattern = BothReactivatedReceivingSendingPattern(client, 'p2a_address_activity') -class MetricsTree_Distribution_TotalAddrCount: - """Metrics tree node.""" - - def __init__(self, client: BrkClientBase, base_path: str = ''): - self.all: MetricPattern1[StoredU64] = MetricPattern1(client, 'total_addr_count') - self.p2pk65: MetricPattern1[StoredU64] = MetricPattern1(client, 'p2pk65_total_addr_count') - self.p2pk33: MetricPattern1[StoredU64] = MetricPattern1(client, 'p2pk33_total_addr_count') - self.p2pkh: MetricPattern1[StoredU64] = MetricPattern1(client, 'p2pkh_total_addr_count') - self.p2sh: MetricPattern1[StoredU64] = MetricPattern1(client, 'p2sh_total_addr_count') - self.p2wpkh: MetricPattern1[StoredU64] = MetricPattern1(client, 'p2wpkh_total_addr_count') - self.p2wsh: MetricPattern1[StoredU64] = MetricPattern1(client, 'p2wsh_total_addr_count') - self.p2tr: MetricPattern1[StoredU64] = MetricPattern1(client, 'p2tr_total_addr_count') - self.p2a: MetricPattern1[StoredU64] = MetricPattern1(client, 'p2a_total_addr_count') - class MetricsTree_Distribution_NewAddrCount: """Metrics tree node.""" @@ -4618,19 +4599,19 @@ class MetricsTree_Distribution_NewAddrCount: self.p2tr: AverageCumulativeHeightMaxMedianMinPct10Pct25Pct75Pct90SumPattern = AverageCumulativeHeightMaxMedianMinPct10Pct25Pct75Pct90SumPattern(client, 'p2tr_new_addr_count') self.p2a: AverageCumulativeHeightMaxMedianMinPct10Pct25Pct75Pct90SumPattern = AverageCumulativeHeightMaxMedianMinPct10Pct25Pct75Pct90SumPattern(client, 'p2a_new_addr_count') -class MetricsTree_Distribution_GrowthRate: +class MetricsTree_Distribution_Delta: """Metrics tree node.""" def __init__(self, client: BrkClientBase, base_path: str = ''): - self.all: BpsPercentRatioPattern3 = BpsPercentRatioPattern3(client, 'growth_rate') - self.p2pk65: BpsPercentRatioPattern3 = BpsPercentRatioPattern3(client, 'p2pk65_growth_rate') - self.p2pk33: BpsPercentRatioPattern3 = BpsPercentRatioPattern3(client, 'p2pk33_growth_rate') - self.p2pkh: BpsPercentRatioPattern3 = BpsPercentRatioPattern3(client, 'p2pkh_growth_rate') - self.p2sh: BpsPercentRatioPattern3 = BpsPercentRatioPattern3(client, 'p2sh_growth_rate') - self.p2wpkh: BpsPercentRatioPattern3 = BpsPercentRatioPattern3(client, 'p2wpkh_growth_rate') - self.p2wsh: BpsPercentRatioPattern3 = BpsPercentRatioPattern3(client, 'p2wsh_growth_rate') - self.p2tr: BpsPercentRatioPattern3 = BpsPercentRatioPattern3(client, 'p2tr_growth_rate') - self.p2a: BpsPercentRatioPattern3 = BpsPercentRatioPattern3(client, 'p2a_growth_rate') + self.all: ChangeRatePattern = ChangeRatePattern(client, 'addr_count') + self.p2pk65: ChangeRatePattern = ChangeRatePattern(client, 'p2pk65_addr_count') + self.p2pk33: ChangeRatePattern = ChangeRatePattern(client, 'p2pk33_addr_count') + self.p2pkh: ChangeRatePattern = ChangeRatePattern(client, 'p2pkh_addr_count') + self.p2sh: ChangeRatePattern = ChangeRatePattern(client, 'p2sh_addr_count') + self.p2wpkh: ChangeRatePattern = ChangeRatePattern(client, 'p2wpkh_addr_count') + self.p2wsh: ChangeRatePattern = ChangeRatePattern(client, 'p2wsh_addr_count') + self.p2tr: ChangeRatePattern = ChangeRatePattern(client, 'p2tr_addr_count') + self.p2a: ChangeRatePattern = ChangeRatePattern(client, 'p2a_addr_count') class MetricsTree_Distribution: """Metrics tree node.""" @@ -4644,9 +4625,9 @@ class MetricsTree_Distribution: self.addr_count: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern = AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern(client, 'addr_count') self.empty_addr_count: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern = AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern(client, 'empty_addr_count') self.address_activity: MetricsTree_Distribution_AddressActivity = MetricsTree_Distribution_AddressActivity(client) - self.total_addr_count: MetricsTree_Distribution_TotalAddrCount = MetricsTree_Distribution_TotalAddrCount(client) + self.total_addr_count: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern = AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern(client, 'total_addr_count') self.new_addr_count: MetricsTree_Distribution_NewAddrCount = MetricsTree_Distribution_NewAddrCount(client) - self.growth_rate: MetricsTree_Distribution_GrowthRate = MetricsTree_Distribution_GrowthRate(client) + self.delta: MetricsTree_Distribution_Delta = MetricsTree_Distribution_Delta(client) self.fundedaddressindex: MetricPattern34[FundedAddressIndex] = MetricPattern34(client, 'fundedaddressindex') self.emptyaddressindex: MetricPattern35[EmptyAddressIndex] = MetricPattern35(client, 'emptyaddressindex') diff --git a/scripts/pool_major_threshold.py b/scripts/pool_major_threshold.py new file mode 100644 index 000000000..c45a046c3 --- /dev/null +++ b/scripts/pool_major_threshold.py @@ -0,0 +1,188 @@ +#!/usr/bin/env python3 +"""Determine which mining pools qualify as "major" based on dominance. + +A pool is major if ANY window's dominance exceeds its threshold: + all-time >= 1%, 1y >= 1%, 1m >= 0.75%, 1w >= 0.5% + +Computes dominance from blocks_mined_cumulative / block_count_cumulative, +so it works for ALL pools (major and minor alike). + +Usage: + python3 scripts/pool_major_threshold.py + python3 scripts/pool_major_threshold.py --all-time 5 --1y 1 --1m 0.75 --1w 0.5 +""" + +import argparse +import json +import re +import urllib.request +import concurrent.futures +from pathlib import Path + +API_BASE = "https://bitview.space/api/metric" +POOLSLUG_PATH = Path(__file__).resolve().parent.parent / "crates/brk_types/src/poolslug.rs" +HEADERS = {"User-Agent": "pool-threshold-script"} +WINDOWS = {"1w": 7, "1m": 30, "1y": 365} + + +def parse_pool_variants(): + """Return [(VariantName, lowercase_slug), ...] from the PoolSlug enum.""" + src = POOLSLUG_PATH.read_text() + m = re.search(r"pub enum PoolSlug\s*\{(.*?)^\}", src, re.DOTALL | re.MULTILINE) + if not m: + raise RuntimeError("Could not find PoolSlug enum") + body = m.group(1) + variants = [] + for line in body.splitlines(): + line = line.strip().rstrip(",") + if not line or line.startswith("#[") or line.startswith("//"): + continue + name = line.split("(")[0].split("{")[0].strip() + if not name or not name[0].isupper(): + continue + if name.startswith("Dummy"): + continue + variants.append((name, name.lower())) + return variants + + +def fetch_json(url): + try: + req = urllib.request.Request(url, headers=HEADERS) + with urllib.request.urlopen(req, timeout=15) as resp: + return json.loads(resp.read()) + except Exception: + return None + + +def fetch_cumulative(slug, days): + url = f"{API_BASE}/{slug}_blocks_mined_cumulative/dateindex?from=-{days}" + return fetch_json(url) + + +def fetch_total_cumulative(days): + url = f"{API_BASE}/block_count_cumulative/dateindex?from=-{days}" + return fetch_json(url) + + +def is_major(doms, thresholds): + """Check if any window meets its threshold.""" + for label, thresh in thresholds.items(): + v = doms.get(label) + if v is not None and v >= thresh: + return True + return False + + +def main(): + parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("--all-time", type=float, default=1.0, help="All-time dominance threshold %% (default: 1.0)") + parser.add_argument("--1y", type=float, default=1.0, dest="t1y", help="1y rolling dominance threshold %% (default: 1.0)") + parser.add_argument("--1m", type=float, default=0.75, dest="t1m", help="1m rolling dominance threshold %% (default: 0.75)") + parser.add_argument("--1w", type=float, default=0.5, dest="t1w", help="1w rolling dominance threshold %% (default: 0.5)") + args = parser.parse_args() + + thresholds = { + "all-time": args.all_time, + "1y": args.t1y, + "1m": args.t1m, + "1w": args.t1w, + } + + variants = parse_pool_variants() + print(f"Found {len(variants)} pool variants in {POOLSLUG_PATH.name}") + print(f"Thresholds: {', '.join(f'{k}>={v}%' for k, v in thresholds.items())}") + + max_days = max(WINDOWS.values()) + 1 + print(f"Fetching blocks_mined_cumulative for all pools...") + + total_data = fetch_total_cumulative(max_days) + if not total_data: + print("ERROR: Could not fetch block_count_cumulative") + return + total_cum = total_data["data"] + + pool_cum = {} + with concurrent.futures.ThreadPoolExecutor(max_workers=20) as ex: + futures = {ex.submit(fetch_cumulative, slug, max_days): (name, slug) + for name, slug in variants} + for fut in concurrent.futures.as_completed(futures): + name, slug = futures[fut] + result = fut.result() + if result and result.get("data"): + pool_cum[name] = {"slug": slug, "data": result["data"]} + + results = [] + for name, info in pool_cum.items(): + pool_data = info["data"] + n = len(pool_data) + doms = {} + + if n > 0 and len(total_cum) > 0: + doms["all-time"] = pool_data[-1] / total_cum[-1] * 100 if total_cum[-1] > 0 else 0 + + for label, days in WINDOWS.items(): + if n > days and len(total_cum) > days: + pool_diff = pool_data[-1] - pool_data[-(days + 1)] + total_diff = total_cum[-1] - total_cum[-(days + 1)] + doms[label] = pool_diff / total_diff * 100 if total_diff > 0 else 0 + else: + doms[label] = None + + values = [v for v in doms.values() if v is not None] + max_dom = max(values) if values else None + major = is_major(doms, thresholds) + results.append((name, info["slug"], doms, max_dom, major)) + + results.sort(key=lambda x: -(x[3] or 0)) + + def fmt(v): + return f"{v:8.4f}%" if v is not None else " N/A" + + header = f"{'Pool':<30} {'all-time':>9} {'1w':>9} {'1m':>9} {'1y':>9} Major?" + thr_line = f"{'threshold:':<30} {'>=' + str(thresholds['all-time']) + '%':>9} {'>=' + str(thresholds['1w']) + '%':>9} {'>=' + str(thresholds['1m']) + '%':>9} {'>=' + str(thresholds['1y']) + '%':>9}" + print(f"\n{header}") + print(thr_line) + print("-" * len(header)) + for name, slug, doms, max_dom, major in results: + at = fmt(doms.get("all-time")) + w1w = fmt(doms.get("1w")) + w1m = fmt(doms.get("1m")) + w1y = fmt(doms.get("1y")) + marker = "***" if major else "" + print(f"{name:<30} {at} {w1w} {w1m} {w1y} {marker}") + + major_list = [(n, s, d, m, mj) for n, s, d, m, mj in results if mj] + print(f"\n--- {len(major_list)} major pools ---") + + print(f"\n--- Qualifying windows ---") + for name, slug, doms, max_dom, _ in major_list: + qualifying = [] + for label, thresh in thresholds.items(): + v = doms.get(label) + if v is not None and v >= thresh: + qualifying.append(f"{label}={v:.2f}%") + print(f" {name:<30} ({', '.join(qualifying)})") + + major_names = sorted(set(["Unknown"] + [n for n, _, _, _, _ in major_list])) + + thresholds_str = ", ".join(f"{k}>={v}%" for k, v in thresholds.items()) + print(f"\n--- Rust is_major() match arms ---\n") + print(f" /// Pools with dominance above per-window thresholds get full metrics.") + print(f" /// Thresholds: {thresholds_str}.") + print(f" /// Generated by `scripts/pool_major_threshold.py`.") + print(f" pub fn is_major(&self) -> bool {{") + print(f" matches!(") + print(f" self,") + for i, name in enumerate(major_names): + if i == 0: + print(f" Self::{name}", end="") + else: + print(f"\n | Self::{name}", end="") + print() + print(f" )") + print(f" }}") + + +if __name__ == "__main__": + main()