From d340855c8b36dc260f2e9b07e94cb1a3d381fc12 Mon Sep 17 00:00:00 2001 From: nym21 Date: Thu, 16 Apr 2026 22:17:41 +0200 Subject: [PATCH] global: snap --- Cargo.lock | 1 + crates/brk_client/src/lib.rs | 95 ++++++++++----- .../src/distribution/addr/exposed/mod.rs | 43 ++++++- .../distribution/addr/exposed/supply/mod.rs | 4 +- .../distribution/addr/exposed/supply/share.rs | 36 ++++++ .../distribution/addr/exposed/supply/state.rs | 17 ++- .../distribution/addr/exposed/supply/vecs.rs | 2 +- .../brk_computer/src/distribution/addr/mod.rs | 8 +- .../src/distribution/block/cohort/received.rs | 16 +-- .../src/distribution/block/cohort/sent.rs | 21 ++-- .../src/distribution/cohorts/utxo/groups.rs | 42 +++---- .../src/distribution/compute/block_loop.rs | 25 ++-- .../src/distribution/metrics/cohort/all.rs | 4 +- .../distribution/metrics/cohort/extended.rs | 4 +- .../distribution/metrics/cost_basis/mod.rs | 16 +-- .../src/distribution/metrics/realized/full.rs | 46 +++---- .../distribution/metrics/unrealized/full.rs | 54 ++++----- .../src/distribution/state/cohort/base.rs | 34 +++--- .../src/distribution/state/cost_basis/data.rs | 64 +++++----- .../distribution/state/cost_basis/realized.rs | 72 +++++------ .../state/cost_basis/unrealized.rs | 26 ++-- .../src/distribution/state/pending.rs | 2 +- crates/brk_computer/src/distribution/vecs.rs | 16 ++- .../src/indicators/rarity_meter/mod.rs | 14 +-- .../src/internal/with_addr_types.rs | 26 +++- crates/brk_mempool/Cargo.toml | 1 + crates/brk_mempool/src/sync.rs | 114 +++++++++++++++--- crates/brk_rpc/src/lib.rs | 73 ++--------- crates/brk_types/src/cents_sats.rs | 4 +- crates/brk_types/src/cents_squared_sats.rs | 4 +- crates/brk_types/src/funded_addr_data.rs | 32 ++--- modules/brk-client/index.js | 97 +++++++++------ packages/brk_client/brk_client/__init__.py | 63 ++++++---- website/scripts/_types.js | 8 +- website/scripts/explorer/chain.js | 36 +++++- website/scripts/options/cointime.js | 6 +- .../options/distribution/cost-basis.js | 6 +- .../scripts/options/distribution/prices.js | 26 ++-- website/scripts/panes/search.js | 77 +++++++++--- website/scripts/utils/colors.js | 1 + website/styles/nav.css | 4 + website/styles/panes/explorer.css | 103 ++++++++++++++-- 42 files changed, 850 insertions(+), 493 deletions(-) create mode 100644 crates/brk_computer/src/distribution/addr/exposed/supply/share.rs diff --git a/Cargo.lock b/Cargo.lock index 266701f4b..411617e2c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -592,6 +592,7 @@ dependencies = [ name = "brk_mempool" version = "0.3.0-beta.2" dependencies = [ + "bitcoin", "brk_error", "brk_logger", "brk_rpc", diff --git a/crates/brk_client/src/lib.rs b/crates/brk_client/src/lib.rs index 718854ada..5348798e2 100644 --- a/crates/brk_client/src/lib.rs +++ b/crates/brk_client/src/lib.rs @@ -1124,10 +1124,10 @@ pub struct AllEmptyP2aP2msP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshUnknownPattern { } /// Pattern struct for repeated tree structure. -pub struct CapGrossInvestorLossMvrvNetPeakPriceProfitSellSoprPattern { +pub struct CapCapitalizedGrossLossMvrvNetPeakPriceProfitSellSoprPattern { pub cap: CentsDeltaToUsdPattern, + pub capitalized: PricePattern, pub gross_pnl: BlockCumulativeSumPattern, - pub investor: PricePattern, pub loss: BlockCumulativeNegativeSumPattern, pub mvrv: SeriesPattern1, pub net_pnl: BlockChangeCumulativeDeltaSumPattern, @@ -1430,11 +1430,11 @@ impl AverageMaxMedianMinPct10Pct25Pct75Pct90SumPattern { } /// Pattern struct for repeated tree structure. -pub struct GrossInvestedInvestorLossNetNuplProfitSentimentPattern2 { +pub struct CapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2 { + pub capitalized_cap_in_loss_raw: SeriesPattern18, + pub capitalized_cap_in_profit_raw: SeriesPattern18, pub gross_pnl: CentsUsdPattern3, pub invested_capital: InPattern, - pub investor_cap_in_loss_raw: SeriesPattern18, - pub investor_cap_in_profit_raw: SeriesPattern18, pub loss: CentsNegativeToUsdPattern2, pub net_pnl: CentsToUsdPattern3, pub nupl: BpsRatioPattern, @@ -1442,14 +1442,14 @@ pub struct GrossInvestedInvestorLossNetNuplProfitSentimentPattern2 { pub sentiment: GreedNetPainPattern, } -impl GrossInvestedInvestorLossNetNuplProfitSentimentPattern2 { +impl CapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2 { /// Create a new pattern node with accumulated series name. pub fn new(client: Arc, acc: String) -> Self { Self { + capitalized_cap_in_loss_raw: SeriesPattern18::new(client.clone(), _m(&acc, "capitalized_cap_in_loss_raw")), + capitalized_cap_in_profit_raw: SeriesPattern18::new(client.clone(), _m(&acc, "capitalized_cap_in_profit_raw")), gross_pnl: CentsUsdPattern3::new(client.clone(), _m(&acc, "unrealized_gross_pnl")), invested_capital: InPattern::new(client.clone(), _m(&acc, "invested_capital_in")), - investor_cap_in_loss_raw: SeriesPattern18::new(client.clone(), _m(&acc, "investor_cap_in_loss_raw")), - investor_cap_in_profit_raw: SeriesPattern18::new(client.clone(), _m(&acc, "investor_cap_in_profit_raw")), loss: CentsNegativeToUsdPattern2::new(client.clone(), _m(&acc, "unrealized_loss")), net_pnl: CentsToUsdPattern3::new(client.clone(), _m(&acc, "net_unrealized_pnl")), nupl: BpsRatioPattern::new(client.clone(), _m(&acc, "nupl")), @@ -4555,20 +4555,51 @@ pub struct SeriesTree_Addrs_Exposed_Supply { pub p2wsh: BtcCentsSatsUsdPattern, pub p2tr: BtcCentsSatsUsdPattern, pub p2a: BtcCentsSatsUsdPattern, + pub share: SeriesTree_Addrs_Exposed_Supply_Share, } impl SeriesTree_Addrs_Exposed_Supply { pub fn new(client: Arc, base_path: String) -> Self { Self { - all: BtcCentsSatsUsdPattern::new(client.clone(), "exposed_addr_supply".to_string()), - p2pk65: BtcCentsSatsUsdPattern::new(client.clone(), "p2pk65_exposed_addr_supply".to_string()), - p2pk33: BtcCentsSatsUsdPattern::new(client.clone(), "p2pk33_exposed_addr_supply".to_string()), - p2pkh: BtcCentsSatsUsdPattern::new(client.clone(), "p2pkh_exposed_addr_supply".to_string()), - p2sh: BtcCentsSatsUsdPattern::new(client.clone(), "p2sh_exposed_addr_supply".to_string()), - p2wpkh: BtcCentsSatsUsdPattern::new(client.clone(), "p2wpkh_exposed_addr_supply".to_string()), - p2wsh: BtcCentsSatsUsdPattern::new(client.clone(), "p2wsh_exposed_addr_supply".to_string()), - p2tr: BtcCentsSatsUsdPattern::new(client.clone(), "p2tr_exposed_addr_supply".to_string()), - p2a: BtcCentsSatsUsdPattern::new(client.clone(), "p2a_exposed_addr_supply".to_string()), + all: BtcCentsSatsUsdPattern::new(client.clone(), "exposed_supply".to_string()), + p2pk65: BtcCentsSatsUsdPattern::new(client.clone(), "p2pk65_exposed_supply".to_string()), + p2pk33: BtcCentsSatsUsdPattern::new(client.clone(), "p2pk33_exposed_supply".to_string()), + p2pkh: BtcCentsSatsUsdPattern::new(client.clone(), "p2pkh_exposed_supply".to_string()), + p2sh: BtcCentsSatsUsdPattern::new(client.clone(), "p2sh_exposed_supply".to_string()), + p2wpkh: BtcCentsSatsUsdPattern::new(client.clone(), "p2wpkh_exposed_supply".to_string()), + p2wsh: BtcCentsSatsUsdPattern::new(client.clone(), "p2wsh_exposed_supply".to_string()), + p2tr: BtcCentsSatsUsdPattern::new(client.clone(), "p2tr_exposed_supply".to_string()), + p2a: BtcCentsSatsUsdPattern::new(client.clone(), "p2a_exposed_supply".to_string()), + share: SeriesTree_Addrs_Exposed_Supply_Share::new(client.clone(), format!("{base_path}_share")), + } + } +} + +/// Series tree node. +pub struct SeriesTree_Addrs_Exposed_Supply_Share { + pub all: BpsPercentRatioPattern2, + pub p2pk65: BpsPercentRatioPattern2, + pub p2pk33: BpsPercentRatioPattern2, + pub p2pkh: BpsPercentRatioPattern2, + pub p2sh: BpsPercentRatioPattern2, + pub p2wpkh: BpsPercentRatioPattern2, + pub p2wsh: BpsPercentRatioPattern2, + pub p2tr: BpsPercentRatioPattern2, + pub p2a: BpsPercentRatioPattern2, +} + +impl SeriesTree_Addrs_Exposed_Supply_Share { + pub fn new(client: Arc, base_path: String) -> Self { + Self { + all: BpsPercentRatioPattern2::new(client.clone(), "exposed_supply_share".to_string()), + p2pk65: BpsPercentRatioPattern2::new(client.clone(), "p2pk65_exposed_supply_share".to_string()), + p2pk33: BpsPercentRatioPattern2::new(client.clone(), "p2pk33_exposed_supply_share".to_string()), + p2pkh: BpsPercentRatioPattern2::new(client.clone(), "p2pkh_exposed_supply_share".to_string()), + p2sh: BpsPercentRatioPattern2::new(client.clone(), "p2sh_exposed_supply_share".to_string()), + p2wpkh: BpsPercentRatioPattern2::new(client.clone(), "p2wpkh_exposed_supply_share".to_string()), + p2wsh: BpsPercentRatioPattern2::new(client.clone(), "p2wsh_exposed_supply_share".to_string()), + p2tr: BpsPercentRatioPattern2::new(client.clone(), "p2tr_exposed_supply_share".to_string()), + p2a: BpsPercentRatioPattern2::new(client.clone(), "p2a_exposed_supply_share".to_string()), } } } @@ -7028,7 +7059,7 @@ pub struct SeriesTree_Cohorts_Utxo_All_Realized { pub gross_pnl: BlockCumulativeSumPattern, pub sell_side_risk_ratio: _1m1w1y24hPattern7, pub peak_regret: BlockCumulativeSumPattern, - pub investor: PricePattern, + pub capitalized: PricePattern, pub profit_to_loss_ratio: _1m1w1y24hPattern, } @@ -7045,7 +7076,7 @@ impl SeriesTree_Cohorts_Utxo_All_Realized { gross_pnl: BlockCumulativeSumPattern::new(client.clone(), "realized_gross_pnl".to_string()), sell_side_risk_ratio: _1m1w1y24hPattern7::new(client.clone(), "sell_side_risk_ratio".to_string()), peak_regret: BlockCumulativeSumPattern::new(client.clone(), "realized_peak_regret".to_string()), - investor: PricePattern::new(client.clone(), "investor_price".to_string()), + capitalized: PricePattern::new(client.clone(), "capitalized_price".to_string()), profit_to_loss_ratio: _1m1w1y24hPattern::new(client.clone(), "realized_profit_to_loss_ratio".to_string()), } } @@ -7328,8 +7359,8 @@ pub struct SeriesTree_Cohorts_Utxo_All_Unrealized { pub net_pnl: SeriesTree_Cohorts_Utxo_All_Unrealized_NetPnl, pub gross_pnl: CentsUsdPattern3, pub invested_capital: InPattern, - pub investor_cap_in_profit_raw: SeriesPattern18, - pub investor_cap_in_loss_raw: SeriesPattern18, + pub capitalized_cap_in_profit_raw: SeriesPattern18, + pub capitalized_cap_in_loss_raw: SeriesPattern18, pub sentiment: SeriesTree_Cohorts_Utxo_All_Unrealized_Sentiment, } @@ -7342,8 +7373,8 @@ impl SeriesTree_Cohorts_Utxo_All_Unrealized { net_pnl: SeriesTree_Cohorts_Utxo_All_Unrealized_NetPnl::new(client.clone(), format!("{base_path}_net_pnl")), gross_pnl: CentsUsdPattern3::new(client.clone(), "unrealized_gross_pnl".to_string()), invested_capital: InPattern::new(client.clone(), "invested_capital_in".to_string()), - investor_cap_in_profit_raw: SeriesPattern18::new(client.clone(), "investor_cap_in_profit_raw".to_string()), - investor_cap_in_loss_raw: SeriesPattern18::new(client.clone(), "investor_cap_in_loss_raw".to_string()), + capitalized_cap_in_profit_raw: SeriesPattern18::new(client.clone(), "capitalized_cap_in_profit_raw".to_string()), + capitalized_cap_in_loss_raw: SeriesPattern18::new(client.clone(), "capitalized_cap_in_loss_raw".to_string()), sentiment: SeriesTree_Cohorts_Utxo_All_Unrealized_Sentiment::new(client.clone(), format!("{base_path}_sentiment")), } } @@ -7430,7 +7461,7 @@ pub struct SeriesTree_Cohorts_Utxo_Sth { pub activity: CoindaysCoinyearsDormancyTransferPattern, pub realized: SeriesTree_Cohorts_Utxo_Sth_Realized, pub cost_basis: InMaxMinPerSupplyPattern, - pub unrealized: GrossInvestedInvestorLossNetNuplProfitSentimentPattern2, + pub unrealized: CapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2, } impl SeriesTree_Cohorts_Utxo_Sth { @@ -7441,7 +7472,7 @@ impl SeriesTree_Cohorts_Utxo_Sth { activity: CoindaysCoinyearsDormancyTransferPattern::new(client.clone(), "sth".to_string()), realized: SeriesTree_Cohorts_Utxo_Sth_Realized::new(client.clone(), format!("{base_path}_realized")), cost_basis: InMaxMinPerSupplyPattern::new(client.clone(), "sth".to_string()), - unrealized: GrossInvestedInvestorLossNetNuplProfitSentimentPattern2::new(client.clone(), "sth".to_string()), + unrealized: CapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2::new(client.clone(), "sth".to_string()), } } } @@ -7458,7 +7489,7 @@ pub struct SeriesTree_Cohorts_Utxo_Sth_Realized { pub gross_pnl: BlockCumulativeSumPattern, pub sell_side_risk_ratio: _1m1w1y24hPattern7, pub peak_regret: BlockCumulativeSumPattern, - pub investor: PricePattern, + pub capitalized: PricePattern, pub profit_to_loss_ratio: _1m1w1y24hPattern, } @@ -7475,7 +7506,7 @@ impl SeriesTree_Cohorts_Utxo_Sth_Realized { gross_pnl: BlockCumulativeSumPattern::new(client.clone(), "sth_realized_gross_pnl".to_string()), sell_side_risk_ratio: _1m1w1y24hPattern7::new(client.clone(), "sth_sell_side_risk_ratio".to_string()), peak_regret: BlockCumulativeSumPattern::new(client.clone(), "sth_realized_peak_regret".to_string()), - investor: PricePattern::new(client.clone(), "sth_investor_price".to_string()), + capitalized: PricePattern::new(client.clone(), "sth_capitalized_price".to_string()), profit_to_loss_ratio: _1m1w1y24hPattern::new(client.clone(), "sth_realized_profit_to_loss_ratio".to_string()), } } @@ -7698,7 +7729,7 @@ pub struct SeriesTree_Cohorts_Utxo_Lth { pub activity: CoindaysCoinyearsDormancyTransferPattern, pub realized: SeriesTree_Cohorts_Utxo_Lth_Realized, pub cost_basis: InMaxMinPerSupplyPattern, - pub unrealized: GrossInvestedInvestorLossNetNuplProfitSentimentPattern2, + pub unrealized: CapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2, } impl SeriesTree_Cohorts_Utxo_Lth { @@ -7709,7 +7740,7 @@ impl SeriesTree_Cohorts_Utxo_Lth { activity: CoindaysCoinyearsDormancyTransferPattern::new(client.clone(), "lth".to_string()), realized: SeriesTree_Cohorts_Utxo_Lth_Realized::new(client.clone(), format!("{base_path}_realized")), cost_basis: InMaxMinPerSupplyPattern::new(client.clone(), "lth".to_string()), - unrealized: GrossInvestedInvestorLossNetNuplProfitSentimentPattern2::new(client.clone(), "lth".to_string()), + unrealized: CapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2::new(client.clone(), "lth".to_string()), } } } @@ -7726,7 +7757,7 @@ pub struct SeriesTree_Cohorts_Utxo_Lth_Realized { pub gross_pnl: BlockCumulativeSumPattern, pub sell_side_risk_ratio: _1m1w1y24hPattern7, pub peak_regret: BlockCumulativeSumPattern, - pub investor: PricePattern, + pub capitalized: PricePattern, pub profit_to_loss_ratio: _1m1w1y24hPattern, } @@ -7743,7 +7774,7 @@ impl SeriesTree_Cohorts_Utxo_Lth_Realized { gross_pnl: BlockCumulativeSumPattern::new(client.clone(), "lth_realized_gross_pnl".to_string()), sell_side_risk_ratio: _1m1w1y24hPattern7::new(client.clone(), "lth_sell_side_risk_ratio".to_string()), peak_regret: BlockCumulativeSumPattern::new(client.clone(), "lth_realized_peak_regret".to_string()), - investor: PricePattern::new(client.clone(), "lth_investor_price".to_string()), + capitalized: PricePattern::new(client.clone(), "lth_capitalized_price".to_string()), profit_to_loss_ratio: _1m1w1y24hPattern::new(client.clone(), "lth_realized_profit_to_loss_ratio".to_string()), } } @@ -8676,7 +8707,7 @@ pub struct BrkClient { impl BrkClient { /// Client version. - pub const VERSION: &'static str = "v0.3.0-beta.1"; + pub const VERSION: &'static str = "v0.3.0-beta.2"; /// Create a new client with the given base URL. pub fn new(base_url: impl Into) -> Self { diff --git a/crates/brk_computer/src/distribution/addr/exposed/mod.rs b/crates/brk_computer/src/distribution/addr/exposed/mod.rs index a75ca0c60..04af499ca 100644 --- a/crates/brk_computer/src/distribution/addr/exposed/mod.rs +++ b/crates/brk_computer/src/distribution/addr/exposed/mod.rs @@ -37,22 +37,25 @@ mod count; mod supply; pub use count::{AddrTypeToExposedAddrCount, ExposedAddrCountsVecs}; -pub use supply::{AddrTypeToExposedAddrSupply, ExposedAddrSupplyVecs}; +pub use supply::{AddrTypeToExposedSupply, ExposedAddrSupplyVecs, ExposedSupplyShareVecs}; +use brk_cohort::ByAddrType; use brk_error::Result; use brk_traversable::Traversable; -use brk_types::{Indexes, Version}; +use brk_types::{Height, Indexes, Sats, Version}; use rayon::prelude::*; -use vecdb::{AnyStoredVec, Database, Exit, Rw, StorageMode}; +use vecdb::{AnyStoredVec, Database, Exit, ReadableVec, Rw, StorageMode}; -use crate::{indexes, prices}; +use crate::{indexes, internal::RatioSatsBp16, prices}; /// Top-level container for all exposed address tracking: counts (funded + -/// total) plus the funded supply. +/// total), the funded supply, and share of supply. #[derive(Traversable)] pub struct ExposedAddrVecs { pub count: ExposedAddrCountsVecs, pub supply: ExposedAddrSupplyVecs, + #[traversable(wrap = "supply", rename = "share")] + pub supply_share: ExposedSupplyShareVecs, } impl ExposedAddrVecs { @@ -64,6 +67,7 @@ impl ExposedAddrVecs { Ok(Self { count: ExposedAddrCountsVecs::forced_import(db, version, indexes)?, supply: ExposedAddrSupplyVecs::forced_import(db, version, indexes)?, + supply_share: ExposedSupplyShareVecs::forced_import(db, version, indexes)?, }) } @@ -84,6 +88,7 @@ impl ExposedAddrVecs { pub(crate) fn reset_height(&mut self) -> Result<()> { self.count.reset_height()?; self.supply.reset_height()?; + self.supply_share.reset_height()?; Ok(()) } @@ -91,11 +96,39 @@ impl ExposedAddrVecs { &mut self, starting_indexes: &Indexes, prices: &prices::Vecs, + all_supply_sats: &impl ReadableVec, + type_supply_sats: &ByAddrType<&impl ReadableVec>, exit: &Exit, ) -> Result<()> { self.count.compute_rest(starting_indexes, exit)?; self.supply .compute_rest(starting_indexes.height, prices, exit)?; + + let max_from = starting_indexes.height; + + self.supply_share + .all + .compute_binary::( + max_from, + &self.supply.all.sats.height, + all_supply_sats, + exit, + )?; + + for ((_, share), ((_, exposed), (_, denom))) in self + .supply_share + .by_addr_type + .iter_mut() + .zip(self.supply.by_addr_type.iter().zip(type_supply_sats.iter())) + { + share.compute_binary::( + max_from, + &exposed.sats.height, + *denom, + exit, + )?; + } + Ok(()) } } diff --git a/crates/brk_computer/src/distribution/addr/exposed/supply/mod.rs b/crates/brk_computer/src/distribution/addr/exposed/supply/mod.rs index bffecac9c..4fd7fe472 100644 --- a/crates/brk_computer/src/distribution/addr/exposed/supply/mod.rs +++ b/crates/brk_computer/src/distribution/addr/exposed/supply/mod.rs @@ -3,8 +3,10 @@ //! aggregated `all`. See the parent [`super`] module for the definition of //! "exposed" and how it varies by address type. +mod share; mod state; mod vecs; -pub use state::AddrTypeToExposedAddrSupply; +pub use share::ExposedSupplyShareVecs; +pub use state::AddrTypeToExposedSupply; pub use vecs::ExposedAddrSupplyVecs; diff --git a/crates/brk_computer/src/distribution/addr/exposed/supply/share.rs b/crates/brk_computer/src/distribution/addr/exposed/supply/share.rs new file mode 100644 index 000000000..47fad33c8 --- /dev/null +++ b/crates/brk_computer/src/distribution/addr/exposed/supply/share.rs @@ -0,0 +1,36 @@ +use brk_error::Result; +use brk_traversable::Traversable; +use brk_types::{BasisPoints16, Version}; +use derive_more::{Deref, DerefMut}; +use vecdb::{Database, Rw, StorageMode}; + +use crate::{ + indexes, + internal::{PercentPerBlock, WithAddrTypes}, +}; + +/// Share of exposed supply relative to total supply. +/// +/// - `all`: exposed_supply / circulating_supply +/// - Per-type: type's exposed_supply / type's total supply +#[derive(Deref, DerefMut, Traversable)] +pub struct ExposedSupplyShareVecs( + #[traversable(flatten)] pub WithAddrTypes>, +); + +impl ExposedSupplyShareVecs { + pub(crate) fn forced_import( + db: &Database, + version: Version, + indexes: &indexes::Vecs, + ) -> Result { + Ok(Self( + WithAddrTypes::>::forced_import( + db, + "exposed_supply_share", + version, + indexes, + )?, + )) + } +} diff --git a/crates/brk_computer/src/distribution/addr/exposed/supply/state.rs b/crates/brk_computer/src/distribution/addr/exposed/supply/state.rs index eadcc389f..974b8fd9a 100644 --- a/crates/brk_computer/src/distribution/addr/exposed/supply/state.rs +++ b/crates/brk_computer/src/distribution/addr/exposed/supply/state.rs @@ -1,5 +1,5 @@ use brk_cohort::ByAddrType; -use brk_types::Height; +use brk_types::{Height, Sats}; use derive_more::{Deref, DerefMut}; use vecdb::ReadableVec; @@ -10,22 +10,21 @@ use super::vecs::ExposedAddrSupplyVecs; /// Runtime running counter for the total balance (sats) held by funded /// exposed addresses, per address type. #[derive(Debug, Default, Deref, DerefMut)] -pub struct AddrTypeToExposedAddrSupply(ByAddrType); +pub struct AddrTypeToExposedSupply(ByAddrType); -impl AddrTypeToExposedAddrSupply { +impl AddrTypeToExposedSupply { #[inline] - pub(crate) fn sum(&self) -> u64 { - self.0.values().sum() + pub(crate) fn sum(&self) -> Sats { + self.0.values().copied().sum() } } -impl From<(&ExposedAddrSupplyVecs, Height)> for AddrTypeToExposedAddrSupply { +impl From<(&ExposedAddrSupplyVecs, Height)> for AddrTypeToExposedSupply { #[inline] fn from((vecs, starting_height): (&ExposedAddrSupplyVecs, Height)) -> Self { if let Some(prev_height) = starting_height.decremented() { - let read = |v: &AmountPerBlock| -> u64 { - u64::from(v.sats.height.collect_one(prev_height).unwrap()) - }; + let read = + |v: &AmountPerBlock| -> Sats { v.sats.height.collect_one(prev_height).unwrap() }; Self(ByAddrType { p2pk65: read(&vecs.by_addr_type.p2pk65), p2pk33: read(&vecs.by_addr_type.p2pk33), diff --git a/crates/brk_computer/src/distribution/addr/exposed/supply/vecs.rs b/crates/brk_computer/src/distribution/addr/exposed/supply/vecs.rs index bb2de9c1b..57353ca5e 100644 --- a/crates/brk_computer/src/distribution/addr/exposed/supply/vecs.rs +++ b/crates/brk_computer/src/distribution/addr/exposed/supply/vecs.rs @@ -26,7 +26,7 @@ impl ExposedAddrSupplyVecs { ) -> Result { Ok(Self(WithAddrTypes::::forced_import( db, - "exposed_addr_supply", + "exposed_supply", version, indexes, )?)) diff --git a/crates/brk_computer/src/distribution/addr/mod.rs b/crates/brk_computer/src/distribution/addr/mod.rs index bb3ac1d3f..433af7bdb 100644 --- a/crates/brk_computer/src/distribution/addr/mod.rs +++ b/crates/brk_computer/src/distribution/addr/mod.rs @@ -13,13 +13,9 @@ pub use activity::{AddrActivityVecs, AddrTypeToActivityCounts}; pub use addr_count::{AddrCountsVecs, AddrTypeToAddrCount}; pub use data::AddrsDataVecs; pub use delta::DeltaVecs; -pub use exposed::{ - AddrTypeToExposedAddrCount, AddrTypeToExposedAddrSupply, ExposedAddrVecs, -}; +pub use exposed::{AddrTypeToExposedAddrCount, AddrTypeToExposedSupply, ExposedAddrVecs,}; pub use indexes::AnyAddrIndexesVecs; pub use new_addr_count::NewAddrCountVecs; -pub use reused::{ - AddrTypeToReusedAddrCount, AddrTypeToReusedAddrEventCount, ReusedAddrVecs, -}; +pub use reused::{AddrTypeToReusedAddrCount, AddrTypeToReusedAddrEventCount, ReusedAddrVecs}; pub use total_addr_count::TotalAddrCountVecs; pub use type_map::{AddrTypeToTypeIndexMap, AddrTypeToVec, HeightToAddrTypeToVec}; diff --git a/crates/brk_computer/src/distribution/block/cohort/received.rs b/crates/brk_computer/src/distribution/block/cohort/received.rs index fddf3003f..e43ce1ace 100644 --- a/crates/brk_computer/src/distribution/block/cohort/received.rs +++ b/crates/brk_computer/src/distribution/block/cohort/received.rs @@ -4,7 +4,7 @@ use rustc_hash::FxHashMap; use crate::distribution::{ addr::{ - AddrTypeToActivityCounts, AddrTypeToExposedAddrCount, AddrTypeToExposedAddrSupply, + AddrTypeToActivityCounts, AddrTypeToExposedAddrCount, AddrTypeToExposedSupply, AddrTypeToReusedAddrCount, AddrTypeToReusedAddrEventCount, AddrTypeToVec, }, cohorts::AddrCohorts, @@ -34,7 +34,7 @@ pub(crate) fn process_received( active_reused_addr_count: &mut AddrTypeToReusedAddrEventCount, exposed_addr_count: &mut AddrTypeToExposedAddrCount, total_exposed_addr_count: &mut AddrTypeToExposedAddrCount, - exposed_addr_supply: &mut AddrTypeToExposedAddrSupply, + exposed_supply: &mut AddrTypeToExposedSupply, ) { let max_type_len = received_data .iter() @@ -56,11 +56,10 @@ pub(crate) fn process_received( let type_reused_count = reused_addr_count.get_mut(output_type).unwrap(); let type_total_reused_count = total_reused_addr_count.get_mut(output_type).unwrap(); let type_output_to_reused_count = output_to_reused_addr_count.get_mut(output_type).unwrap(); - let type_active_reused_count = - active_reused_addr_count.get_mut(output_type).unwrap(); + let type_active_reused_count = active_reused_addr_count.get_mut(output_type).unwrap(); let type_exposed_count = exposed_addr_count.get_mut(output_type).unwrap(); let type_total_exposed_count = total_exposed_addr_count.get_mut(output_type).unwrap(); - let type_exposed_supply = exposed_addr_supply.get_mut(output_type).unwrap(); + let type_exposed_supply = exposed_supply.get_mut(output_type).unwrap(); // Aggregate receives by address - each address processed exactly once for (type_index, value) in vec { @@ -206,15 +205,12 @@ pub(crate) fn process_received( if !was_funded && was_pubkey_exposed { *type_exposed_count += 1; } - if output_type.pubkey_exposed_at_funding() - && matches!(status, TrackingStatus::New) - { + if output_type.pubkey_exposed_at_funding() && matches!(status, TrackingStatus::New) { *type_total_exposed_count += 1; } // Update exposed supply via post-receive contribution delta. - let exposed_contribution_after = - addr_data.exposed_supply_contribution(output_type); + let exposed_contribution_after = addr_data.exposed_supply_contribution(output_type); // Receives can only add to balance and membership, so the delta // is always non-negative. *type_exposed_supply += exposed_contribution_after - exposed_contribution_before; diff --git a/crates/brk_computer/src/distribution/block/cohort/sent.rs b/crates/brk_computer/src/distribution/block/cohort/sent.rs index 717787ca3..cf7c05d02 100644 --- a/crates/brk_computer/src/distribution/block/cohort/sent.rs +++ b/crates/brk_computer/src/distribution/block/cohort/sent.rs @@ -6,7 +6,7 @@ use vecdb::VecIndex; use crate::distribution::{ addr::{ - AddrTypeToActivityCounts, AddrTypeToExposedAddrCount, AddrTypeToExposedAddrSupply, + AddrTypeToActivityCounts, AddrTypeToExposedAddrCount, AddrTypeToExposedSupply, AddrTypeToReusedAddrCount, AddrTypeToReusedAddrEventCount, HeightToAddrTypeToVec, }, cohorts::AddrCohorts, @@ -43,7 +43,7 @@ pub(crate) fn process_sent( active_reused_addr_count: &mut AddrTypeToReusedAddrEventCount, exposed_addr_count: &mut AddrTypeToExposedAddrCount, total_exposed_addr_count: &mut AddrTypeToExposedAddrCount, - exposed_addr_supply: &mut AddrTypeToExposedAddrSupply, + exposed_supply: &mut AddrTypeToExposedSupply, received_addrs: &ByAddrType>, height_to_price: &[Cents], height_to_timestamp: &[Timestamp], @@ -69,11 +69,10 @@ pub(crate) fn process_sent( let type_reused_count = reused_addr_count.get_mut(output_type).unwrap(); let type_input_from_reused_count = input_from_reused_addr_count.get_mut(output_type).unwrap(); - let type_active_reused_count = - active_reused_addr_count.get_mut(output_type).unwrap(); + let type_active_reused_count = active_reused_addr_count.get_mut(output_type).unwrap(); let type_exposed_count = exposed_addr_count.get_mut(output_type).unwrap(); let type_total_exposed_count = total_exposed_addr_count.get_mut(output_type).unwrap(); - let type_exposed_supply = exposed_addr_supply.get_mut(output_type).unwrap(); + let type_exposed_supply = exposed_supply.get_mut(output_type).unwrap(); let type_received = received_addrs.get(output_type); let type_seen = seen_senders.get_mut_unwrap(output_type); @@ -96,8 +95,7 @@ pub(crate) fn process_sent( if type_seen.insert(type_index) { type_activity.sending += 1; - let also_received = - type_received.is_some_and(|s| s.contains(&type_index)); + let also_received = type_received.is_some_and(|s| s.contains(&type_index)); // Track "bidirectional": addresses that sent AND // received this block. if also_received { @@ -136,12 +134,13 @@ pub(crate) fn process_sent( // addr_data.spent_txo_count is now incremented by 1. // Update exposed supply via post-spend contribution delta. - let exposed_contribution_after = - addr_data.exposed_supply_contribution(output_type); + let exposed_contribution_after = addr_data.exposed_supply_contribution(output_type); if exposed_contribution_after >= exposed_contribution_before { - *type_exposed_supply += exposed_contribution_after - exposed_contribution_before; + *type_exposed_supply += + exposed_contribution_after - exposed_contribution_before; } else { - *type_exposed_supply -= exposed_contribution_before - exposed_contribution_after; + *type_exposed_supply -= + exposed_contribution_before - exposed_contribution_after; } // Update exposed counts on first-ever pubkey exposure. diff --git a/crates/brk_computer/src/distribution/cohorts/utxo/groups.rs b/crates/brk_computer/src/distribution/cohorts/utxo/groups.rs index 45e74ba9a..c92edf388 100644 --- a/crates/brk_computer/src/distribution/cohorts/utxo/groups.rs +++ b/crates/brk_computer/src/distribution/cohorts/utxo/groups.rs @@ -830,26 +830,26 @@ impl UTXOCohorts { let mut sth_acc = RealizedFullAccum::default(); let mut lth_acc = RealizedFullAccum::default(); - let mut all_icap = (0u128, 0u128); - let mut sth_icap = (0u128, 0u128); - let mut lth_icap = (0u128, 0u128); + let mut all_ccap = (0u128, 0u128); + let mut sth_ccap = (0u128, 0u128); + let mut lth_ccap = (0u128, 0u128); for ar in age_range.iter_mut() { if let Some(state) = ar.state.as_mut() { all_acc.add(&state.realized); let u = state.compute_unrealized_state(height_price); - all_icap.0 += u.investor_cap_in_profit_raw; - all_icap.1 += u.investor_cap_in_loss_raw; + all_ccap.0 += u.capitalized_cap_in_profit_raw; + all_ccap.1 += u.capitalized_cap_in_loss_raw; if sth_filter.includes(&ar.metrics.filter) { sth_acc.add(&state.realized); - sth_icap.0 += u.investor_cap_in_profit_raw; - sth_icap.1 += u.investor_cap_in_loss_raw; + sth_ccap.0 += u.capitalized_cap_in_profit_raw; + sth_ccap.1 += u.capitalized_cap_in_loss_raw; } else { lth_acc.add(&state.realized); - lth_icap.0 += u.investor_cap_in_profit_raw; - lth_icap.1 += u.investor_cap_in_loss_raw; + lth_ccap.0 += u.capitalized_cap_in_profit_raw; + lth_ccap.1 += u.capitalized_cap_in_loss_raw; } } } @@ -860,28 +860,28 @@ impl UTXOCohorts { all.metrics .unrealized - .investor_cap_in_profit_raw - .push(CentsSquaredSats::new(all_icap.0)); + .capitalized_cap_in_profit_raw + .push(CentsSquaredSats::new(all_ccap.0)); all.metrics .unrealized - .investor_cap_in_loss_raw - .push(CentsSquaredSats::new(all_icap.1)); + .capitalized_cap_in_loss_raw + .push(CentsSquaredSats::new(all_ccap.1)); sth.metrics .unrealized - .investor_cap_in_profit_raw - .push(CentsSquaredSats::new(sth_icap.0)); + .capitalized_cap_in_profit_raw + .push(CentsSquaredSats::new(sth_ccap.0)); sth.metrics .unrealized - .investor_cap_in_loss_raw - .push(CentsSquaredSats::new(sth_icap.1)); + .capitalized_cap_in_loss_raw + .push(CentsSquaredSats::new(sth_ccap.1)); lth.metrics .unrealized - .investor_cap_in_profit_raw - .push(CentsSquaredSats::new(lth_icap.0)); + .capitalized_cap_in_profit_raw + .push(CentsSquaredSats::new(lth_ccap.0)); lth.metrics .unrealized - .investor_cap_in_loss_raw - .push(CentsSquaredSats::new(lth_icap.1)); + .capitalized_cap_in_loss_raw + .push(CentsSquaredSats::new(lth_ccap.1)); } } diff --git a/crates/brk_computer/src/distribution/compute/block_loop.rs b/crates/brk_computer/src/distribution/compute/block_loop.rs index 299477419..3f76482f5 100644 --- a/crates/brk_computer/src/distribution/compute/block_loop.rs +++ b/crates/brk_computer/src/distribution/compute/block_loop.rs @@ -13,8 +13,7 @@ use crate::{ distribution::{ addr::{ AddrTypeToActivityCounts, AddrTypeToAddrCount, AddrTypeToExposedAddrCount, - AddrTypeToExposedAddrSupply, AddrTypeToReusedAddrCount, - AddrTypeToReusedAddrEventCount, + AddrTypeToExposedSupply, AddrTypeToReusedAddrCount, AddrTypeToReusedAddrEventCount, }, block::{ AddrCache, InputsResult, process_inputs, process_outputs, process_received, @@ -203,7 +202,7 @@ pub(crate) fn process_blocks( mut total_reused_addr_counts, mut exposed_addr_counts, mut total_exposed_addr_counts, - mut exposed_addr_supply, + mut exposed_supply, ) = if starting_height > Height::ZERO { ( AddrTypeToAddrCount::from((&vecs.addrs.funded.by_addr_type, starting_height)), @@ -212,7 +211,7 @@ pub(crate) fn process_blocks( AddrTypeToReusedAddrCount::from((&vecs.addrs.reused.count.total, starting_height)), AddrTypeToExposedAddrCount::from((&vecs.addrs.exposed.count.funded, starting_height)), AddrTypeToExposedAddrCount::from((&vecs.addrs.exposed.count.total, starting_height)), - AddrTypeToExposedAddrSupply::from((&vecs.addrs.exposed.supply, starting_height)), + AddrTypeToExposedSupply::from((&vecs.addrs.exposed.supply, starting_height)), ) } else { ( @@ -222,7 +221,7 @@ pub(crate) fn process_blocks( AddrTypeToReusedAddrCount::default(), AddrTypeToExposedAddrCount::default(), AddrTypeToExposedAddrCount::default(), - AddrTypeToExposedAddrSupply::default(), + AddrTypeToExposedSupply::default(), ) }; debug!("addr_counts recovered"); @@ -489,7 +488,7 @@ pub(crate) fn process_blocks( &mut active_reused_addr_counts, &mut exposed_addr_counts, &mut total_exposed_addr_counts, - &mut exposed_addr_supply, + &mut exposed_supply, ); // Process sent inputs (addresses sending funds) @@ -507,7 +506,7 @@ pub(crate) fn process_blocks( &mut active_reused_addr_counts, &mut exposed_addr_counts, &mut total_exposed_addr_counts, - &mut exposed_addr_supply, + &mut exposed_supply, &received_addrs, height_to_price_vec, height_to_timestamp_vec, @@ -539,8 +538,8 @@ pub(crate) fn process_blocks( total_reused_addr_counts.values().copied(), ); let activity_totals = activity_counts.totals(); - let active_addr_count = activity_totals.sending + activity_totals.receiving - - activity_totals.bidirectional; + let active_addr_count = + activity_totals.sending + activity_totals.receiving - activity_totals.bidirectional; let active_reused = u32::try_from(active_reused_addr_counts.sum()).unwrap(); vecs.addrs.reused.events.push_height( &output_to_reused_addr_counts, @@ -556,10 +555,10 @@ pub(crate) fn process_blocks( total_exposed_addr_counts.sum(), total_exposed_addr_counts.values().copied(), ); - vecs.addrs.exposed.supply.push_height( - exposed_addr_supply.sum(), - exposed_addr_supply.values().copied(), - ); + vecs.addrs + .exposed + .supply + .push_height(exposed_supply.sum(), exposed_supply.values().copied()); let is_last_of_day = is_last_of_day[offset]; let date_opt = is_last_of_day.then(|| Date::from(timestamp)); diff --git a/crates/brk_computer/src/distribution/metrics/cohort/all.rs b/crates/brk_computer/src/distribution/metrics/cohort/all.rs index 663a56dfd..51164e325 100644 --- a/crates/brk_computer/src/distribution/metrics/cohort/all.rs +++ b/crates/brk_computer/src/distribution/metrics/cohort/all.rs @@ -144,8 +144,8 @@ impl AllCohortMetrics { &self.unrealized.invested_capital.in_loss.cents.height, &self.supply.in_profit.sats.height, &self.supply.in_loss.sats.height, - &self.unrealized.investor_cap_in_profit_raw, - &self.unrealized.investor_cap_in_loss_raw, + &self.unrealized.capitalized_cap_in_profit_raw, + &self.unrealized.capitalized_cap_in_loss_raw, exit, )?; diff --git a/crates/brk_computer/src/distribution/metrics/cohort/extended.rs b/crates/brk_computer/src/distribution/metrics/cohort/extended.rs index f4c857f56..e788645e3 100644 --- a/crates/brk_computer/src/distribution/metrics/cohort/extended.rs +++ b/crates/brk_computer/src/distribution/metrics/cohort/extended.rs @@ -120,8 +120,8 @@ impl ExtendedCohortMetrics { &self.unrealized.invested_capital.in_loss.cents.height, &self.supply.in_profit.sats.height, &self.supply.in_loss.sats.height, - &self.unrealized.investor_cap_in_profit_raw, - &self.unrealized.investor_cap_in_loss_raw, + &self.unrealized.capitalized_cap_in_profit_raw, + &self.unrealized.capitalized_cap_in_loss_raw, exit, )?; diff --git a/crates/brk_computer/src/distribution/metrics/cost_basis/mod.rs b/crates/brk_computer/src/distribution/metrics/cost_basis/mod.rs index e47fe1431..5b4f5288b 100644 --- a/crates/brk_computer/src/distribution/metrics/cost_basis/mod.rs +++ b/crates/brk_computer/src/distribution/metrics/cost_basis/mod.rs @@ -153,8 +153,8 @@ impl CostBasis { invested_cap_in_loss: &impl ReadableVec, supply_in_profit_sats: &impl ReadableVec, supply_in_loss_sats: &impl ReadableVec, - investor_cap_in_profit_raw: &impl ReadableVec, - investor_cap_in_loss_raw: &impl ReadableVec, + capitalized_cap_in_profit_raw: &impl ReadableVec, + capitalized_cap_in_loss_raw: &impl ReadableVec, exit: &Exit, ) -> Result<()> { self.in_profit.per_coin.cents.height.compute_transform3( @@ -193,29 +193,29 @@ impl CostBasis { )?; self.in_profit.per_dollar.cents.height.compute_transform3( starting_indexes.height, - investor_cap_in_profit_raw, + capitalized_cap_in_profit_raw, invested_cap_in_profit, spot, - |(h, investor_cap, invested_cents, spot, ..)| { + |(h, capitalized_cap, invested_cents, spot, ..)| { let invested_raw = invested_cents.as_u128() * Sats::ONE_BTC_U128; if invested_raw == 0 { return (h, spot); } - (h, Cents::new((investor_cap.inner() / invested_raw) as u64)) + (h, Cents::new((capitalized_cap.inner() / invested_raw) as u64)) }, exit, )?; self.in_loss.per_dollar.cents.height.compute_transform3( starting_indexes.height, - investor_cap_in_loss_raw, + capitalized_cap_in_loss_raw, invested_cap_in_loss, spot, - |(h, investor_cap, invested_cents, spot, ..)| { + |(h, capitalized_cap, invested_cents, spot, ..)| { let invested_raw = invested_cents.as_u128() * Sats::ONE_BTC_U128; if invested_raw == 0 { return (h, spot); } - (h, Cents::new((investor_cap.inner() / invested_raw) as u64)) + (h, Cents::new((capitalized_cap.inner() / invested_raw) as u64)) }, exit, )?; diff --git a/crates/brk_computer/src/distribution/metrics/realized/full.rs b/crates/brk_computer/src/distribution/metrics/realized/full.rs index 9c8e80357..9e12429a4 100644 --- a/crates/brk_computer/src/distribution/metrics/realized/full.rs +++ b/crates/brk_computer/src/distribution/metrics/realized/full.rs @@ -45,7 +45,7 @@ pub struct RealizedPeakRegret { } #[derive(Traversable)] -pub struct RealizedInvestor { +pub struct RealizedCapitalized { pub price: PriceWithRatioExtendedPerBlock, #[traversable(hidden)] pub cap_raw: M::Stored>, @@ -63,7 +63,7 @@ pub struct RealizedFull { pub net_pnl: RealizedNetPnl, pub sopr: RealizedSopr, pub peak_regret: RealizedPeakRegret, - pub investor: RealizedInvestor, + pub capitalized: RealizedCapitalized, pub profit_to_loss_ratio: RollingWindows, @@ -108,10 +108,10 @@ impl RealizedFull { value: cfg.import("realized_peak_regret", Version::new(3))?, }; - // Investor - let investor = RealizedInvestor { - price: cfg.import("investor_price", v0)?, - cap_raw: cfg.import("investor_cap_raw", v0)?, + // Capitalized + let capitalized = RealizedCapitalized { + price: cfg.import("capitalized_price", v0)?, + cap_raw: cfg.import("capitalized_cap_raw", v0)?, }; // Price ratio stats @@ -125,7 +125,7 @@ impl RealizedFull { net_pnl, sopr, peak_regret, - investor, + capitalized, profit_to_loss_ratio: cfg.import("realized_profit_to_loss_ratio", v1)?, cap_raw: cfg.import("cap_raw", v0)?, cap_to_own_mcap: cfg.import("realized_cap_to_own_mcap", v1)?, @@ -151,13 +151,13 @@ impl RealizedFull { } pub(crate) fn min_stateful_len(&self) -> usize { - self.investor + self.capitalized .price .cents .height .len() .min(self.cap_raw.len()) - .min(self.investor.cap_raw.len()) + .min(self.capitalized.cap_raw.len()) .min(self.peak_regret.value.block.cents.len()) } @@ -167,15 +167,15 @@ impl RealizedFull { state: &CohortState>, ) { self.core.push_state(state); - self.investor + self.capitalized .price .cents .height - .push(state.realized.investor_price()); + .push(state.realized.capitalized_price()); self.cap_raw.push(state.realized.cap_raw()); - self.investor + self.capitalized .cap_raw - .push(state.realized.investor_cap_raw()); + .push(state.realized.capitalized_cap_raw()); self.peak_regret .value .block @@ -185,9 +185,9 @@ impl RealizedFull { pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> { let mut vecs = self.core.collect_vecs_mut(); - vecs.push(&mut self.investor.price.cents.height); + vecs.push(&mut self.capitalized.price.cents.height); vecs.push(&mut self.cap_raw as &mut dyn AnyStoredVec); - vecs.push(&mut self.investor.cap_raw as &mut dyn AnyStoredVec); + vecs.push(&mut self.capitalized.cap_raw as &mut dyn AnyStoredVec); vecs.push(&mut self.peak_regret.value.block.cents); vecs } @@ -207,17 +207,17 @@ impl RealizedFull { #[inline(always)] pub(crate) fn push_accum(&mut self, accum: &RealizedFullAccum) { self.cap_raw.push(accum.cap_raw); - self.investor.cap_raw.push(accum.investor_cap_raw); + self.capitalized.cap_raw.push(accum.capitalized_cap_raw); - let investor_price = { + let capitalized_price = { let cap = accum.cap_raw.as_u128(); if cap == 0 { Cents::ZERO } else { - Cents::new((accum.investor_cap_raw / cap) as u64) + Cents::new((accum.capitalized_cap_raw / cap) as u64) } }; - self.investor.price.cents.height.push(investor_price); + self.capitalized.price.cents.height.push(capitalized_price); self.peak_regret.value.block.cents.push(accum.peak_regret()); } @@ -298,8 +298,8 @@ impl RealizedFull { exit, )?; - // Investor price ratio, percentiles and bands - self.investor + // Capitalized price ratio, percentiles and bands + self.capitalized .price .compute_rest(prices, starting_indexes, exit)?; @@ -374,14 +374,14 @@ impl RealizedFull { #[derive(Default)] pub struct RealizedFullAccum { pub(crate) cap_raw: CentsSats, - pub(crate) investor_cap_raw: CentsSquaredSats, + pub(crate) capitalized_cap_raw: CentsSquaredSats, peak_regret: CentsSats, } impl RealizedFullAccum { pub(crate) fn add(&mut self, state: &RealizedState) { self.cap_raw += state.cap_raw(); - self.investor_cap_raw += state.investor_cap_raw(); + self.capitalized_cap_raw += state.capitalized_cap_raw(); self.peak_regret += CentsSats::new(state.peak_regret_raw()); } diff --git a/crates/brk_computer/src/distribution/metrics/unrealized/full.rs b/crates/brk_computer/src/distribution/metrics/unrealized/full.rs index e5c276821..5d46dd078 100644 --- a/crates/brk_computer/src/distribution/metrics/unrealized/full.rs +++ b/crates/brk_computer/src/distribution/metrics/unrealized/full.rs @@ -33,8 +33,8 @@ pub struct UnrealizedFull { pub gross_pnl: FiatPerBlock, pub invested_capital: UnrealizedInvestedCapital, - pub investor_cap_in_profit_raw: M::Stored>, - pub investor_cap_in_loss_raw: M::Stored>, + pub capitalized_cap_in_profit_raw: M::Stored>, + pub capitalized_cap_in_loss_raw: M::Stored>, pub sentiment: UnrealizedSentiment, } @@ -53,8 +53,8 @@ impl UnrealizedFull { in_loss: cfg.import("invested_capital_in_loss", v1)?, }; - 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 capitalized_cap_in_profit_raw = cfg.import("capitalized_cap_in_profit_raw", v0)?; + let capitalized_cap_in_loss_raw = cfg.import("capitalized_cap_in_loss_raw", v0)?; let sentiment = UnrealizedSentiment { pain_index: cfg.import("pain_index", v1)?, @@ -66,33 +66,33 @@ impl UnrealizedFull { inner, gross_pnl, invested_capital, - investor_cap_in_profit_raw, - investor_cap_in_loss_raw, + capitalized_cap_in_profit_raw, + capitalized_cap_in_loss_raw, sentiment, }) } pub(crate) fn min_stateful_len(&self) -> usize { - // Only check per-block pushed vecs (investor_cap_raw). + // Only check per-block pushed vecs (capitalized_cap_raw). // Core-level vecs (profit/loss) are aggregated from age_range, not stateful. - self.investor_cap_in_profit_raw + self.capitalized_cap_in_profit_raw .len() - .min(self.investor_cap_in_loss_raw.len()) + .min(self.capitalized_cap_in_loss_raw.len()) } #[inline(always)] pub(crate) fn push_state_all(&mut self, state: &UnrealizedState) { self.inner.push_state(state); - self.investor_cap_in_profit_raw - .push(CentsSquaredSats::new(state.investor_cap_in_profit_raw)); - self.investor_cap_in_loss_raw - .push(CentsSquaredSats::new(state.investor_cap_in_loss_raw)); + self.capitalized_cap_in_profit_raw + .push(CentsSquaredSats::new(state.capitalized_cap_in_profit_raw)); + self.capitalized_cap_in_loss_raw + .push(CentsSquaredSats::new(state.capitalized_cap_in_loss_raw)); } pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> { let mut vecs = self.inner.collect_vecs_mut(); - 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.push(&mut self.capitalized_cap_in_profit_raw as &mut dyn AnyStoredVec); + vecs.push(&mut self.capitalized_cap_in_loss_raw as &mut dyn AnyStoredVec); vecs } @@ -154,7 +154,7 @@ impl UnrealizedFull { Ok(()) } - /// Compute sentiment using investor_price (original formula). + /// Compute sentiment using capitalized_price (original formula). /// Called after cost_basis.in_profit/loss are computed at the cohort level. pub(crate) fn compute_sentiment( &mut self, @@ -162,45 +162,45 @@ impl UnrealizedFull { spot: &impl ReadableVec, exit: &Exit, ) -> Result<()> { - // greed = spot - investor_price_winners - // investor_price = investor_cap / invested_cap + // greed = spot - capitalized_price_winners + // capitalized_price = capitalized_cap / invested_cap // invested_cap is in Cents (already / ONE_BTC), multiply back for CentsSats scale self.sentiment.greed_index.cents.height.compute_transform3( starting_indexes.height, - &self.investor_cap_in_profit_raw, + &self.capitalized_cap_in_profit_raw, &self.invested_capital.in_profit.cents.height, spot, - |(h, investor_cap, invested_cap_cents, spot, ..)| { + |(h, capitalized_cap, invested_cap_cents, spot, ..)| { let invested_cap_raw = invested_cap_cents.as_u128() * Sats::ONE_BTC_U128; if invested_cap_raw == 0 { return (h, Cents::ZERO); } - let investor_price = investor_cap.inner() / invested_cap_raw; + let capitalized_price = capitalized_cap.inner() / invested_cap_raw; let spot_u128 = spot.as_u128(); ( h, - Cents::new(spot_u128.saturating_sub(investor_price) as u64), + Cents::new(spot_u128.saturating_sub(capitalized_price) as u64), ) }, exit, )?; - // pain = investor_price_losers - spot + // pain = capitalized_price_losers - spot self.sentiment.pain_index.cents.height.compute_transform3( starting_indexes.height, - &self.investor_cap_in_loss_raw, + &self.capitalized_cap_in_loss_raw, &self.invested_capital.in_loss.cents.height, spot, - |(h, investor_cap, invested_cap_cents, spot, ..)| { + |(h, capitalized_cap, invested_cap_cents, spot, ..)| { let invested_cap_raw = invested_cap_cents.as_u128() * Sats::ONE_BTC_U128; if invested_cap_raw == 0 { return (h, Cents::ZERO); } - let investor_price = investor_cap.inner() / invested_cap_raw; + let capitalized_price = capitalized_cap.inner() / invested_cap_raw; let spot_u128 = spot.as_u128(); ( h, - Cents::new(investor_price.saturating_sub(spot_u128) as u64), + Cents::new(capitalized_price.saturating_sub(spot_u128) as u64), ) }, exit, diff --git a/crates/brk_computer/src/distribution/state/cohort/base.rs b/crates/brk_computer/src/distribution/state/cohort/base.rs index e69101932..a9b4b6468 100644 --- a/crates/brk_computer/src/distribution/state/cohort/base.rs +++ b/crates/brk_computer/src/distribution/state/cohort/base.rs @@ -18,7 +18,7 @@ pub struct SendPrecomputed { pub current_ps: CentsSats, pub prev_ps: CentsSats, pub ath_ps: CentsSats, - pub prev_investor_cap: CentsSquaredSats, + pub prev_capitalized_cap: CentsSquaredSats, } impl SendPrecomputed { @@ -42,7 +42,7 @@ impl SendPrecomputed { } else { CentsSats::from_price_sats(ath, sats) }; - let prev_investor_cap = prev_ps.to_investor_cap(prev_price); + let prev_capitalized_cap = prev_ps.to_capitalized_cap(prev_price); Some(Self { sats, prev_price, @@ -50,7 +50,7 @@ impl SendPrecomputed { current_ps, prev_ps, ath_ps, - prev_investor_cap, + prev_capitalized_cap, }) } } @@ -90,7 +90,7 @@ impl CohortState { pub(crate) fn restore_realized_cap(&mut self) { self.realized.set_cap_raw(self.cost_basis.cap_raw()); self.realized - .set_investor_cap_raw(self.cost_basis.investor_cap_raw()); + .set_capitalized_cap_raw(self.cost_basis.capitalized_cap_raw()); } pub(crate) fn reset_cost_basis_data_if_needed(&mut self) -> Result<()> { @@ -117,12 +117,12 @@ impl CohortState { if s.supply_state.value > Sats::ZERO { self.realized - .increment_snapshot(s.price_sats, s.investor_cap); + .increment_snapshot(s.price_sats, s.capitalized_cap_raw); self.cost_basis.increment( s.realized_price, s.supply_state.value, s.price_sats, - s.investor_cap, + s.capitalized_cap_raw, ); } } @@ -132,12 +132,12 @@ impl CohortState { if s.supply_state.value > Sats::ZERO { self.realized - .decrement_snapshot(s.price_sats, s.investor_cap); + .decrement_snapshot(s.price_sats, s.capitalized_cap_raw); self.cost_basis.decrement( s.realized_price, s.supply_state.value, s.price_sats, - s.investor_cap, + s.capitalized_cap_raw, ); } } @@ -162,7 +162,7 @@ impl CohortState { snapshot.realized_price, supply.value, snapshot.price_sats, - snapshot.investor_cap, + snapshot.capitalized_cap_raw, ); } } @@ -184,7 +184,7 @@ impl CohortState { current.realized_price, current.supply_state.value, current.price_sats, - current.investor_cap, + current.capitalized_cap_raw, ); } @@ -193,7 +193,7 @@ impl CohortState { prev.realized_price, prev.supply_state.value, prev.price_sats, - prev.investor_cap, + prev.capitalized_cap_raw, ); } } @@ -212,11 +212,11 @@ impl CohortState { pre.current_ps, pre.prev_ps, pre.ath_ps, - pre.prev_investor_cap, + pre.prev_capitalized_cap, ); self.cost_basis - .decrement(pre.prev_price, pre.sats, pre.prev_ps, pre.prev_investor_cap); + .decrement(pre.prev_price, pre.sats, pre.prev_ps, pre.prev_capitalized_cap); } pub(crate) fn send_utxo( @@ -265,17 +265,17 @@ impl CohortState { let current_ps = CentsSats::from_price_sats(current_price, sats); let prev_ps = CentsSats::from_price_sats(prev_price, sats); let ath_ps = CentsSats::from_price_sats(ath, sats); - let prev_investor_cap = prev_ps.to_investor_cap(prev_price); + let prev_capitalized_cap = prev_ps.to_capitalized_cap(prev_price); self.realized - .send(sats, current_ps, prev_ps, ath_ps, prev_investor_cap); + .send(sats, current_ps, prev_ps, ath_ps, prev_capitalized_cap); if current.supply_state.value.is_not_zero() { self.cost_basis.increment( current.realized_price, current.supply_state.value, current.price_sats, - current.investor_cap, + current.capitalized_cap_raw, ); } @@ -284,7 +284,7 @@ impl CohortState { prev.realized_price, prev.supply_state.value, prev.price_sats, - prev.investor_cap, + prev.capitalized_cap_raw, ); } } diff --git a/crates/brk_computer/src/distribution/state/cost_basis/data.rs b/crates/brk_computer/src/distribution/state/cost_basis/data.rs index b9a3c55cb..4efd0b3a4 100644 --- a/crates/brk_computer/src/distribution/state/cost_basis/data.rs +++ b/crates/brk_computer/src/distribution/state/cost_basis/data.rs @@ -12,7 +12,7 @@ use rustc_hash::FxHashMap; use vecdb::{Bytes, unlikely}; use super::{Accumulate, CachedUnrealizedState, UnrealizedState}; -use crate::distribution::state::pending::{PendingCapDelta, PendingDelta, PendingInvestorCapDelta}; +use crate::distribution::state::pending::{PendingCapDelta, PendingDelta, PendingCapitalizedCapRawDelta}; /// Type alias for the price-to-sats map used in cost basis data. pub(super) type CostBasisMap = BTreeMap; @@ -27,20 +27,20 @@ pub trait CostBasisOps: Send + Sync + 'static { fn with_price_rounding(self, digits: i32) -> Self; fn import_at_or_before(&mut self, height: Height) -> Result; fn cap_raw(&self) -> CentsSats; - fn investor_cap_raw(&self) -> CentsSquaredSats; + fn capitalized_cap_raw(&self) -> CentsSquaredSats; fn increment( &mut self, price: Cents, sats: Sats, price_sats: CentsSats, - investor_cap: CentsSquaredSats, + capitalized_cap: CentsSquaredSats, ); fn decrement( &mut self, price: Cents, sats: Sats, price_sats: CentsSats, - investor_cap: CentsSquaredSats, + capitalized_cap: CentsSquaredSats, ); fn apply_pending(&mut self); fn init(&mut self); @@ -182,7 +182,7 @@ impl CostBasisOps for CostBasisRaw { self.state.as_ref().unwrap().cap_raw } - fn investor_cap_raw(&self) -> CentsSquaredSats { + fn capitalized_cap_raw(&self) -> CentsSquaredSats { CentsSquaredSats::ZERO } @@ -192,7 +192,7 @@ impl CostBasisOps for CostBasisRaw { _price: Cents, _sats: Sats, price_sats: CentsSats, - _investor_cap: CentsSquaredSats, + _capitalized_cap: CentsSquaredSats, ) { self.pending_cap.inc += price_sats; } @@ -203,7 +203,7 @@ impl CostBasisOps for CostBasisRaw { _price: Cents, _sats: Sats, price_sats: CentsSats, - _investor_cap: CentsSquaredSats, + _capitalized_cap: CentsSquaredSats, ) { self.pending_cap.dec += price_sats; } @@ -240,7 +240,7 @@ impl CostBasisOps for CostBasisRaw { /// Composes `CostBasisRaw` for scalar tracking, adds map, pending, and cache. /// /// Generic over the accumulator `S`: -/// - `WithCapital`: tracks all fields including invested capital + investor cap (128 bytes) +/// - `WithCapital`: tracks all fields including invested capital + capitalized cap (128 bytes) /// - `WithoutCapital`: tracks only supply + unrealized profit/loss (64 bytes, 1 cache line) #[derive(Clone, Debug)] pub struct CostBasisData { @@ -249,8 +249,8 @@ pub struct CostBasisData { pending: FxHashMap, cache: Option>, rounding_digits: Option, - investor_cap_raw: CentsSquaredSats, - pending_investor_cap: PendingInvestorCapDelta, + capitalized_cap_raw: CentsSquaredSats, + pending_capitalized_cap: PendingCapitalizedCapRawDelta, } impl CostBasisData { @@ -351,8 +351,8 @@ impl CostBasisOps for CostBasisData { pending: FxHashMap::default(), cache: None, rounding_digits: None, - investor_cap_raw: CentsSquaredSats::ZERO, - pending_investor_cap: PendingInvestorCapDelta::default(), + capitalized_cap_raw: CentsSquaredSats::ZERO, + pending_capitalized_cap: PendingCapitalizedCapRawDelta::default(), } } @@ -375,10 +375,10 @@ impl CostBasisOps for CostBasisData { "CostBasisData state too short: {} bytes", rest.len() ); - self.investor_cap_raw = CentsSquaredSats::from_bytes(&rest[16..32])?; + self.capitalized_cap_raw = CentsSquaredSats::from_bytes(&rest[16..32])?; self.pending.clear(); self.raw.pending_cap = PendingCapDelta::default(); - self.pending_investor_cap = PendingInvestorCapDelta::default(); + self.pending_capitalized_cap = PendingCapitalizedCapRawDelta::default(); self.cache = None; Ok(height) } @@ -387,8 +387,8 @@ impl CostBasisOps for CostBasisData { self.raw.cap_raw() } - fn investor_cap_raw(&self) -> CentsSquaredSats { - self.investor_cap_raw + fn capitalized_cap_raw(&self) -> CentsSquaredSats { + self.capitalized_cap_raw } #[inline] @@ -397,13 +397,13 @@ impl CostBasisOps for CostBasisData { price: Cents, sats: Sats, price_sats: CentsSats, - investor_cap: CentsSquaredSats, + capitalized_cap: CentsSquaredSats, ) { let price = self.round_price(price); self.pending.entry(price.into()).or_default().inc += sats; self.raw.pending_cap.inc += price_sats; - if investor_cap != CentsSquaredSats::ZERO { - self.pending_investor_cap.inc += investor_cap; + if capitalized_cap != CentsSquaredSats::ZERO { + self.pending_capitalized_cap.inc += capitalized_cap; } if let Some(cache) = self.cache.as_mut() { cache.on_receive(price, sats); @@ -416,13 +416,13 @@ impl CostBasisOps for CostBasisData { price: Cents, sats: Sats, price_sats: CentsSats, - investor_cap: CentsSquaredSats, + capitalized_cap: CentsSquaredSats, ) { let price = self.round_price(price); self.pending.entry(price.into()).or_default().dec += sats; self.raw.pending_cap.dec += price_sats; - if investor_cap != CentsSquaredSats::ZERO { - self.pending_investor_cap.dec += investor_cap; + if capitalized_cap != CentsSquaredSats::ZERO { + self.pending_capitalized_cap.dec += capitalized_cap; } if let Some(cache) = self.cache.as_mut() { cache.on_send(price, sats); @@ -432,19 +432,19 @@ impl CostBasisOps for CostBasisData { fn apply_pending(&mut self) { self.apply_map_pending(); self.raw.apply_pending_cap(); - self.investor_cap_raw += self.pending_investor_cap.inc; + self.capitalized_cap_raw += self.pending_capitalized_cap.inc; debug_assert!( - self.investor_cap_raw >= self.pending_investor_cap.dec, - "CostBasis investor_cap_raw underflow!\n\ + self.capitalized_cap_raw >= self.pending_capitalized_cap.dec, + "CostBasis capitalized_cap_raw underflow!\n\ Path: {:?}\n\ Current (after increments): {:?}\n\ Trying to decrement by: {:?}", self.raw.pathbuf, - self.investor_cap_raw, - self.pending_investor_cap.dec + self.capitalized_cap_raw, + self.pending_capitalized_cap.dec ); - self.investor_cap_raw -= self.pending_investor_cap.dec; - self.pending_investor_cap = PendingInvestorCapDelta::default(); + self.capitalized_cap_raw -= self.pending_capitalized_cap.dec; + self.pending_capitalized_cap = PendingCapitalizedCapRawDelta::default(); } fn init(&mut self) { @@ -452,8 +452,8 @@ impl CostBasisOps for CostBasisData { self.map.replace(CostBasisDistribution::default()); self.pending.clear(); self.cache = None; - self.investor_cap_raw = CentsSquaredSats::ZERO; - self.pending_investor_cap = PendingInvestorCapDelta::default(); + self.capitalized_cap_raw = CentsSquaredSats::ZERO; + self.pending_capitalized_cap = PendingCapitalizedCapRawDelta::default(); } fn clean(&mut self) -> Result<()> { @@ -469,7 +469,7 @@ impl CostBasisOps for CostBasisData { let raw_state = self.raw.state.as_ref().unwrap(); let mut buffer = self.map.as_ref().unwrap().serialize()?; buffer.extend(raw_state.cap_raw.to_bytes()); - buffer.extend(self.investor_cap_raw.to_bytes()); + buffer.extend(self.capitalized_cap_raw.to_bytes()); fs::write(self.raw.path_state(height), buffer)?; Ok(()) diff --git a/crates/brk_computer/src/distribution/state/cost_basis/realized.rs b/crates/brk_computer/src/distribution/state/cost_basis/realized.rs index 2f7ea6b50..f37af2949 100644 --- a/crates/brk_computer/src/distribution/state/cost_basis/realized.rs +++ b/crates/brk_computer/src/distribution/state/cost_basis/realized.rs @@ -18,11 +18,11 @@ pub trait RealizedOps: Default + Clone + Send + Sync + 'static { Sats::ZERO } fn set_cap_raw(&mut self, cap_raw: CentsSats); - fn set_investor_cap_raw(&mut self, investor_cap_raw: CentsSquaredSats); + fn set_capitalized_cap_raw(&mut self, capitalized_cap_raw: CentsSquaredSats); fn reset_single_iteration_values(&mut self); fn increment(&mut self, price: Cents, sats: Sats); - fn increment_snapshot(&mut self, price_sats: CentsSats, investor_cap: CentsSquaredSats); - fn decrement_snapshot(&mut self, price_sats: CentsSats, investor_cap: CentsSquaredSats); + fn increment_snapshot(&mut self, price_sats: CentsSats, capitalized_cap: CentsSquaredSats); + fn decrement_snapshot(&mut self, price_sats: CentsSats, capitalized_cap: CentsSquaredSats); fn receive(&mut self, price: Cents, sats: Sats) { self.increment(price, sats); } @@ -32,7 +32,7 @@ pub trait RealizedOps: Default + Clone + Send + Sync + 'static { current_ps: CentsSats, prev_ps: CentsSats, ath_ps: CentsSats, - prev_investor_cap: CentsSquaredSats, + prev_capitalized_cap: CentsSquaredSats, ); } @@ -85,7 +85,7 @@ impl RealizedOps for MinimalRealizedState { } #[inline] - fn set_investor_cap_raw(&mut self, _investor_cap_raw: CentsSquaredSats) {} + fn set_capitalized_cap_raw(&mut self, _capitalized_cap_raw: CentsSquaredSats) {} #[inline] fn reset_single_iteration_values(&mut self) { @@ -104,12 +104,12 @@ impl RealizedOps for MinimalRealizedState { } #[inline] - fn increment_snapshot(&mut self, price_sats: CentsSats, _investor_cap: CentsSquaredSats) { + fn increment_snapshot(&mut self, price_sats: CentsSats, _capitalized_cap: CentsSquaredSats) { self.cap_raw += price_sats.as_u128(); } #[inline] - fn decrement_snapshot(&mut self, price_sats: CentsSats, _investor_cap: CentsSquaredSats) { + fn decrement_snapshot(&mut self, price_sats: CentsSats, _capitalized_cap: CentsSquaredSats) { self.cap_raw -= price_sats.as_u128(); } @@ -120,7 +120,7 @@ impl RealizedOps for MinimalRealizedState { current_ps: CentsSats, prev_ps: CentsSats, _ath_ps: CentsSats, - _prev_investor_cap: CentsSquaredSats, + _prev_capitalized_cap: CentsSquaredSats, ) { match current_ps.cmp(&prev_ps) { Ordering::Greater => { @@ -184,7 +184,7 @@ impl RealizedOps for CoreRealizedState { } #[inline] - fn set_investor_cap_raw(&mut self, _investor_cap_raw: CentsSquaredSats) {} + fn set_capitalized_cap_raw(&mut self, _capitalized_cap_raw: CentsSquaredSats) {} #[inline] fn reset_single_iteration_values(&mut self) { @@ -199,13 +199,13 @@ impl RealizedOps for CoreRealizedState { } #[inline] - fn increment_snapshot(&mut self, price_sats: CentsSats, _investor_cap: CentsSquaredSats) { - self.minimal.increment_snapshot(price_sats, _investor_cap); + fn increment_snapshot(&mut self, price_sats: CentsSats, _capitalized_cap: CentsSquaredSats) { + self.minimal.increment_snapshot(price_sats, _capitalized_cap); } #[inline] - fn decrement_snapshot(&mut self, price_sats: CentsSats, _investor_cap: CentsSquaredSats) { - self.minimal.decrement_snapshot(price_sats, _investor_cap); + fn decrement_snapshot(&mut self, price_sats: CentsSats, _capitalized_cap: CentsSquaredSats) { + self.minimal.decrement_snapshot(price_sats, _capitalized_cap); } #[inline] @@ -215,10 +215,10 @@ impl RealizedOps for CoreRealizedState { current_ps: CentsSats, prev_ps: CentsSats, ath_ps: CentsSats, - prev_investor_cap: CentsSquaredSats, + prev_capitalized_cap: CentsSquaredSats, ) { self.minimal - .send(sats, current_ps, prev_ps, ath_ps, prev_investor_cap); + .send(sats, current_ps, prev_ps, ath_ps, prev_capitalized_cap); match current_ps.cmp(&prev_ps) { Ordering::Greater | Ordering::Equal => { self.sent_in_profit += sats; @@ -242,8 +242,8 @@ impl CoreRealizedState { #[derive(Debug, Default, Clone)] pub struct RealizedState { core: CoreRealizedState, - /// Raw investor cap: Σ(price² × sats) - investor_cap_raw: CentsSquaredSats, + /// Raw capitalized cap: Σ(price² × sats) + capitalized_cap_raw: CentsSquaredSats, /// Raw realized peak regret: Σ((peak - sell_price) × sats) peak_regret_raw: u128, } @@ -287,8 +287,8 @@ impl RealizedOps for RealizedState { } #[inline] - fn set_investor_cap_raw(&mut self, investor_cap_raw: CentsSquaredSats) { - self.investor_cap_raw = investor_cap_raw; + fn set_capitalized_cap_raw(&mut self, capitalized_cap_raw: CentsSquaredSats) { + self.capitalized_cap_raw = capitalized_cap_raw; } #[inline] @@ -301,20 +301,20 @@ impl RealizedOps for RealizedState { fn increment(&mut self, price: Cents, sats: Sats) { self.core.increment(price, sats); if sats.is_not_zero() { - self.investor_cap_raw += CentsSats::from_price_sats(price, sats).to_investor_cap(price); + self.capitalized_cap_raw += CentsSats::from_price_sats(price, sats).to_capitalized_cap(price); } } #[inline] - fn increment_snapshot(&mut self, price_sats: CentsSats, investor_cap: CentsSquaredSats) { - self.core.increment_snapshot(price_sats, investor_cap); - self.investor_cap_raw += investor_cap; + fn increment_snapshot(&mut self, price_sats: CentsSats, capitalized_cap: CentsSquaredSats) { + self.core.increment_snapshot(price_sats, capitalized_cap); + self.capitalized_cap_raw += capitalized_cap; } #[inline] - fn decrement_snapshot(&mut self, price_sats: CentsSats, investor_cap: CentsSquaredSats) { - self.core.decrement_snapshot(price_sats, investor_cap); - self.investor_cap_raw -= investor_cap; + fn decrement_snapshot(&mut self, price_sats: CentsSats, capitalized_cap: CentsSquaredSats) { + self.core.decrement_snapshot(price_sats, capitalized_cap); + self.capitalized_cap_raw -= capitalized_cap; } #[inline] @@ -324,26 +324,26 @@ impl RealizedOps for RealizedState { current_ps: CentsSats, prev_ps: CentsSats, ath_ps: CentsSats, - prev_investor_cap: CentsSquaredSats, + prev_capitalized_cap: CentsSquaredSats, ) { self.core - .send(sats, current_ps, prev_ps, ath_ps, prev_investor_cap); + .send(sats, current_ps, prev_ps, ath_ps, prev_capitalized_cap); self.peak_regret_raw += (ath_ps - current_ps).as_u128(); - self.investor_cap_raw -= prev_investor_cap; + self.capitalized_cap_raw -= prev_capitalized_cap; } } impl RealizedState { - /// Get investor price as CentsUnsigned. - /// investor_price = Σ(price² × sats) / Σ(price × sats) + /// Get capitalized price as CentsUnsigned. + /// capitalized_price = Σ(price² × sats) / Σ(price × sats) #[inline] - pub(crate) fn investor_price(&self) -> Cents { + pub(crate) fn capitalized_price(&self) -> Cents { let cap_raw = self.core.cap_raw_u128(); if cap_raw == 0 { return Cents::ZERO; } - Cents::new((self.investor_cap_raw / cap_raw) as u64) + Cents::new((self.capitalized_cap_raw / cap_raw) as u64) } /// Get raw realized cap for aggregation. @@ -352,10 +352,10 @@ impl RealizedState { CentsSats::new(self.core.cap_raw_u128()) } - /// Get raw investor cap for aggregation. + /// Get raw capitalized cap for aggregation. #[inline] - pub(crate) fn investor_cap_raw(&self) -> CentsSquaredSats { - self.investor_cap_raw + pub(crate) fn capitalized_cap_raw(&self) -> CentsSquaredSats { + self.capitalized_cap_raw } /// Get realized peak regret as CentsUnsigned. diff --git a/crates/brk_computer/src/distribution/state/cost_basis/unrealized.rs b/crates/brk_computer/src/distribution/state/cost_basis/unrealized.rs index 3539ecd13..258267857 100644 --- a/crates/brk_computer/src/distribution/state/cost_basis/unrealized.rs +++ b/crates/brk_computer/src/distribution/state/cost_basis/unrealized.rs @@ -10,8 +10,8 @@ pub struct UnrealizedState { pub supply_in_loss: Sats, pub unrealized_profit: Cents, pub unrealized_loss: Cents, - pub investor_cap_in_profit_raw: u128, - pub investor_cap_in_loss_raw: u128, + pub capitalized_cap_in_profit_raw: u128, + pub capitalized_cap_in_loss_raw: u128, } impl UnrealizedState { @@ -20,8 +20,8 @@ impl UnrealizedState { supply_in_loss: Sats::ZERO, unrealized_profit: Cents::ZERO, unrealized_loss: Cents::ZERO, - investor_cap_in_profit_raw: 0, - investor_cap_in_loss_raw: 0, + capitalized_cap_in_profit_raw: 0, + capitalized_cap_in_loss_raw: 0, }; } @@ -34,12 +34,12 @@ pub struct WithoutCapital { pub(crate) unrealized_loss: u128, } -/// Full cache state: core + investor cap (for sentiment computation). +/// Full cache state: core + capitalized cap (for sentiment computation). #[derive(Debug, Default, Clone)] pub struct WithCapital { core: WithoutCapital, - investor_cap_in_profit: u128, - investor_cap_in_loss: u128, + capitalized_cap_in_profit: u128, + capitalized_cap_in_loss: u128, } #[inline(always)] @@ -116,8 +116,8 @@ impl Accumulate for WithoutCapital { impl Accumulate for WithCapital { fn to_output(&self) -> UnrealizedState { UnrealizedState { - investor_cap_in_profit_raw: self.investor_cap_in_profit, - investor_cap_in_loss_raw: self.investor_cap_in_loss, + capitalized_cap_in_profit_raw: self.capitalized_cap_in_profit, + capitalized_cap_in_loss_raw: self.capitalized_cap_in_loss, ..Accumulate::to_output(&self.core) } } @@ -133,25 +133,25 @@ impl Accumulate for WithCapital { fn accumulate_profit(&mut self, price_u128: u128, sats: Sats) { self.core.supply_in_profit += sats; let invested = price_u128 * sats.as_u128(); - self.investor_cap_in_profit += price_u128 * invested; + self.capitalized_cap_in_profit += price_u128 * invested; } #[inline(always)] fn accumulate_loss(&mut self, price_u128: u128, sats: Sats) { self.core.supply_in_loss += sats; let invested = price_u128 * sats.as_u128(); - self.investor_cap_in_loss += price_u128 * invested; + self.capitalized_cap_in_loss += price_u128 * invested; } #[inline(always)] fn deaccumulate_profit(&mut self, price_u128: u128, sats: Sats) { self.core.supply_in_profit -= sats; let invested = price_u128 * sats.as_u128(); - self.investor_cap_in_profit -= price_u128 * invested; + self.capitalized_cap_in_profit -= price_u128 * invested; } #[inline(always)] fn deaccumulate_loss(&mut self, price_u128: u128, sats: Sats) { self.core.supply_in_loss -= sats; let invested = price_u128 * sats.as_u128(); - self.investor_cap_in_loss -= price_u128 * invested; + self.capitalized_cap_in_loss -= price_u128 * invested; } } diff --git a/crates/brk_computer/src/distribution/state/pending.rs b/crates/brk_computer/src/distribution/state/pending.rs index 5e60e80fa..1f78516b3 100644 --- a/crates/brk_computer/src/distribution/state/pending.rs +++ b/crates/brk_computer/src/distribution/state/pending.rs @@ -13,7 +13,7 @@ impl PendingCapDelta { } #[derive(Clone, Debug, Default)] -pub(crate) struct PendingInvestorCapDelta { +pub(crate) struct PendingCapitalizedCapRawDelta { pub inc: CentsSquaredSats, pub dec: CentsSquaredSats, } diff --git a/crates/brk_computer/src/distribution/vecs.rs b/crates/brk_computer/src/distribution/vecs.rs index 6ed8c6626..dfb04c687 100644 --- a/crates/brk_computer/src/distribution/vecs.rs +++ b/crates/brk_computer/src/distribution/vecs.rs @@ -1,5 +1,6 @@ use std::path::{Path, PathBuf}; +use brk_cohort::{ByAddrType, Filter}; use brk_error::Result; use brk_indexer::Indexer; use brk_traversable::Traversable; @@ -476,9 +477,18 @@ impl Vecs { &inputs.by_type, exit, )?; - self.addrs - .exposed - .compute_rest(starting_indexes, prices, exit)?; + let t = &self.utxo_cohorts.type_; + let type_supply_sats = ByAddrType::new(|filter| { + let Filter::Type(ot) = filter else { unreachable!() }; + &t.get(ot).metrics.supply.total.sats.height + }); + self.addrs.exposed.compute_rest( + starting_indexes, + prices, + &self.utxo_cohorts.all.metrics.supply.total.sats.height, + &type_supply_sats, + exit, + )?; // 6c. Compute total_addr_count = addr_count + empty_addr_count self.addrs.total.compute( diff --git a/crates/brk_computer/src/indicators/rarity_meter/mod.rs b/crates/brk_computer/src/indicators/rarity_meter/mod.rs index 9890fec23..a6ae7b716 100644 --- a/crates/brk_computer/src/indicators/rarity_meter/mod.rs +++ b/crates/brk_computer/src/indicators/rarity_meter/mod.rs @@ -44,15 +44,15 @@ impl RarityMeter { let lth_realized = &distribution.utxo_cohorts.lth.metrics.realized; let spot = &prices.spot.cents.height; - // Full: all + sth + lth (rp + ip), 6 models + // Full: all + sth + lth (rp + cp), 6 models self.full.compute( &[ &realized.price_ratio_percentiles, - &realized.investor.price.percentiles, + &realized.capitalized.price.percentiles, &sth_realized.price_ratio_percentiles, - &sth_realized.investor.price.percentiles, + &sth_realized.capitalized.price.percentiles, <h_realized.price_ratio_percentiles, - <h_realized.investor.price.percentiles, + <h_realized.capitalized.price.percentiles, ], spot, starting_indexes, @@ -63,7 +63,7 @@ impl RarityMeter { self.local.compute( &[ &sth_realized.price_ratio_percentiles, - &sth_realized.investor.price.percentiles, + &sth_realized.capitalized.price.percentiles, ], spot, starting_indexes, @@ -74,9 +74,9 @@ impl RarityMeter { self.cycle.compute( &[ &realized.price_ratio_percentiles, - &realized.investor.price.percentiles, + &realized.capitalized.price.percentiles, <h_realized.price_ratio_percentiles, - <h_realized.investor.price.percentiles, + <h_realized.capitalized.price.percentiles, ], spot, starting_indexes, diff --git a/crates/brk_computer/src/internal/with_addr_types.rs b/crates/brk_computer/src/internal/with_addr_types.rs index b24bd4bd3..127e9f6b7 100644 --- a/crates/brk_computer/src/internal/with_addr_types.rs +++ b/crates/brk_computer/src/internal/with_addr_types.rs @@ -13,7 +13,8 @@ use vecdb::{AnyStoredVec, AnyVec, Database, EagerVec, Exit, PcoVec, WritableVec} use crate::{indexes, prices}; use super::{ - AmountPerBlock, NumericValue, PerBlock, PerBlockCumulativeRolling, WindowStartVec, Windows, + AmountPerBlock, BpsType, NumericValue, PerBlock, PerBlockCumulativeRolling, PercentPerBlock, + WindowStartVec, Windows, }; /// `all` aggregate plus per-`AddrType` breakdown. @@ -244,3 +245,26 @@ impl WithAddrTypes { Ok(()) } } + +impl WithAddrTypes> { + pub(crate) fn forced_import( + db: &Database, + name: &str, + version: Version, + indexes: &indexes::Vecs, + ) -> Result { + let all = PercentPerBlock::forced_import(db, name, version, indexes)?; + let by_addr_type = ByAddrType::new_with_name(|type_name| { + PercentPerBlock::forced_import(db, &format!("{type_name}_{name}"), version, indexes) + })?; + Ok(Self { all, by_addr_type }) + } + + pub(crate) fn reset_height(&mut self) -> Result<()> { + self.all.bps.height.reset()?; + for v in self.by_addr_type.values_mut() { + v.bps.height.reset()?; + } + Ok(()) + } +} diff --git a/crates/brk_mempool/Cargo.toml b/crates/brk_mempool/Cargo.toml index 2208a5a89..5a45374b7 100644 --- a/crates/brk_mempool/Cargo.toml +++ b/crates/brk_mempool/Cargo.toml @@ -9,6 +9,7 @@ repository.workspace = true exclude = ["examples/"] [dependencies] +bitcoin = { workspace = true } brk_error = { workspace = true } brk_rpc = { workspace = true, features = ["corepc"] } brk_types = { workspace = true } diff --git a/crates/brk_mempool/src/sync.rs b/crates/brk_mempool/src/sync.rs index 017066704..222f32a60 100644 --- a/crates/brk_mempool/src/sync.rs +++ b/crates/brk_mempool/src/sync.rs @@ -1,5 +1,6 @@ use std::{ hash::{DefaultHasher, Hash, Hasher}, + mem, sync::{ Arc, atomic::{AtomicBool, AtomicU64, Ordering}, @@ -8,9 +9,13 @@ use std::{ time::{Duration, SystemTime, UNIX_EPOCH}, }; +use bitcoin::hex::DisplayHex; use brk_error::Result; use brk_rpc::Client; -use brk_types::{AddrBytes, MempoolEntryInfo, MempoolInfo, TxWithHex, Txid, TxidPrefix}; +use brk_types::{ + AddrBytes, BlockHash, MempoolEntryInfo, MempoolInfo, Transaction, TxIn, TxOut, TxStatus, + TxWithHex, Txid, TxidPrefix, Vout, +}; use derive_more::Deref; use parking_lot::{RwLock, RwLockReadGuard}; use rustc_hash::FxHashMap; @@ -142,28 +147,101 @@ impl MempoolInner { /// Fetch full transaction data for new txids (needed for address tracking). fn fetch_new_txs(&self, entries_info: &[MempoolEntryInfo]) -> FxHashMap { - let txids_to_fetch: Vec = { - let txs = self.txs.read(); - entries_info - .iter() - .map(|e| &e.txid) - .filter(|txid| !txs.contains(txid)) - .take(MAX_TX_FETCHES_PER_CYCLE) - .cloned() - .collect() - }; - - txids_to_fetch - .into_iter() - .filter_map(|txid| { - self.client - .get_mempool_transaction(&txid) + let txs = self.txs.read(); + entries_info + .iter() + .filter(|e| !txs.contains(&e.txid)) + .take(MAX_TX_FETCHES_PER_CYCLE) + .filter_map(|entry| { + self.build_transaction(entry, &txs) .ok() - .map(|tx| (txid, tx)) + .map(|tx| (entry.txid.clone(), tx)) }) .collect() } + fn build_transaction( + &self, + entry: &MempoolEntryInfo, + mempool_txs: &TxStore, + ) -> Result { + let (mut btc_tx, hex) = self.client.get_mempool_raw_tx(&entry.txid)?; + + let total_size = hex.len() / 2; + let total_sigop_cost = btc_tx.total_sigop_cost(|_| None); + + // Collect unique parent txids not in the mempool store, + // fetch each once instead of one get_tx_out per input + let mut parent_cache: FxHashMap> = FxHashMap::default(); + for txin in &btc_tx.input { + let prev_txid: Txid = txin.previous_output.txid.into(); + if !mempool_txs.contains_key(&prev_txid) + && !parent_cache.contains_key(&prev_txid) + { + if let Ok(prev) = self + .client + .get_raw_transaction(&prev_txid, None as Option<&BlockHash>) + { + parent_cache.insert(prev_txid, prev.output); + } + } + } + + let input = mem::take(&mut btc_tx.input) + .into_iter() + .map(|txin| { + let prev_txid: Txid = txin.previous_output.txid.into(); + let prev_vout = usize::from(Vout::from(txin.previous_output.vout)); + + let prevout = if let Some(prev) = mempool_txs.get(&prev_txid) { + prev.tx() + .output + .get(prev_vout) + .map(|o| TxOut::from((o.script_pubkey.clone(), o.value))) + } else if let Some(outputs) = parent_cache.get(&prev_txid) { + outputs + .get(prev_vout) + .map(|o| TxOut::from((o.script_pubkey.clone(), o.value.into()))) + } else { + None + }; + + TxIn { + is_coinbase: prevout.is_none(), + prevout, + txid: prev_txid, + vout: txin.previous_output.vout.into(), + script_sig: txin.script_sig, + script_sig_asm: (), + witness: txin + .witness + .iter() + .map(|w| w.to_lower_hex_string()) + .collect(), + sequence: txin.sequence.into(), + inner_redeem_script_asm: (), + inner_witness_script_asm: (), + } + }) + .collect(); + + let tx = Transaction { + index: None, + txid: entry.txid.clone(), + version: btc_tx.version.into(), + total_sigop_cost, + weight: entry.weight.into(), + lock_time: btc_tx.lock_time.into(), + total_size, + fee: entry.fee, + input, + output: btc_tx.output.into_iter().map(TxOut::from).collect(), + status: TxStatus::UNCONFIRMED, + }; + + Ok(TxWithHex::new(tx, hex)) + } + /// Apply transaction additions and removals. Returns true if there were changes. fn apply_changes( &self, diff --git a/crates/brk_rpc/src/lib.rs b/crates/brk_rpc/src/lib.rs index a2ff1192a..59eae9da5 100644 --- a/crates/brk_rpc/src/lib.rs +++ b/crates/brk_rpc/src/lib.rs @@ -8,10 +8,7 @@ use std::{ use bitcoin::consensus::encode; use brk_error::{Error, Result}; -use brk_types::{ - BlockHash, Height, MempoolEntryInfo, Sats, Transaction, TxIn, TxOut, TxStatus, TxWithHex, Txid, - Vout, -}; +use brk_types::{BlockHash, Height, MempoolEntryInfo, Sats, Txid, Vout}; pub mod backend; @@ -128,69 +125,13 @@ impl Client { Ok(tx) } - pub fn get_mempool_transaction(&self, txid: &Txid) -> Result { - // Get hex first, then deserialize from it + pub fn get_mempool_raw_tx( + &self, + txid: &Txid, + ) -> Result<(bitcoin::Transaction, String)> { let hex = self.get_raw_transaction_hex(txid, None as Option<&BlockHash>)?; - let mut tx = encode::deserialize_hex::(&hex)?; - - let input = mem::take(&mut tx.input) - .into_iter() - .map(|txin| -> Result { - let txout_result = self.get_tx_out( - (&txin.previous_output.txid).into(), - txin.previous_output.vout.into(), - Some(true), - )?; - - let is_coinbase = txout_result.as_ref().is_none_or(|r| r.coinbase); - - let txout = if let Some(txout_result) = txout_result { - Some(TxOut::from(( - txout_result.script_pub_key, - txout_result.value, - ))) - } else { - None - }; - - let witness = txin - .witness - .iter() - .map(bitcoin::hex::DisplayHex::to_lower_hex_string) - .collect(); - - Ok(TxIn { - is_coinbase, - prevout: txout, - txid: txin.previous_output.txid.into(), - vout: txin.previous_output.vout.into(), - script_sig: txin.script_sig, - script_sig_asm: (), - witness, - sequence: txin.sequence.into(), - inner_redeem_script_asm: (), - inner_witness_script_asm: (), - }) - }) - .collect::>>()?; - - let mut tx = Transaction { - index: None, - txid: txid.clone(), - version: tx.version.into(), - total_sigop_cost: tx.total_sigop_cost(|_| None), - weight: tx.weight().into(), - lock_time: tx.lock_time.into(), - total_size: tx.total_size(), - fee: Sats::default(), - input, - output: tx.output.into_iter().map(TxOut::from).collect(), - status: TxStatus::UNCONFIRMED, - }; - - tx.compute_fee(); - - Ok(TxWithHex::new(tx, hex)) + let tx = encode::deserialize_hex::(&hex)?; + Ok((tx, hex)) } pub fn get_tx_out( diff --git a/crates/brk_types/src/cents_sats.rs b/crates/brk_types/src/cents_sats.rs index 2d2e2efe5..e192b32be 100644 --- a/crates/brk_types/src/cents_sats.rs +++ b/crates/brk_types/src/cents_sats.rs @@ -53,9 +53,9 @@ impl CentsSats { Cents::new(result.min(u32::MAX as u128) as u64) } - /// Compute investor cap (price² × sats) = price × (price × sats) + /// Compute capitalized cap (price² × sats) = price × (price × sats) #[inline(always)] - pub fn to_investor_cap(self, price: Cents) -> CentsSquaredSats { + pub fn to_capitalized_cap(self, price: Cents) -> CentsSquaredSats { CentsSquaredSats::new(price.inner() as u128 * self.0) } } diff --git a/crates/brk_types/src/cents_squared_sats.rs b/crates/brk_types/src/cents_squared_sats.rs index 9306fc8ed..326af7b55 100644 --- a/crates/brk_types/src/cents_squared_sats.rs +++ b/crates/brk_types/src/cents_squared_sats.rs @@ -5,8 +5,8 @@ use serde::{Deserialize, Serialize}; use vecdb::{Bytes, Formattable}; /// Raw cents squared (u128) - stores cents² × sats without division. -/// Used for precise accumulation of investor cap values: Σ(price² × sats). -/// investor_price = investor_cap_raw / realized_cap_raw +/// Used for precise accumulation of capitalized cap values: Σ(price² × sats). +/// capitalized_price = capitalized_cap_raw / realized_cap_raw #[derive( Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema, )] diff --git a/crates/brk_types/src/funded_addr_data.rs b/crates/brk_types/src/funded_addr_data.rs index 326b81c17..914f2da84 100644 --- a/crates/brk_types/src/funded_addr_data.rs +++ b/crates/brk_types/src/funded_addr_data.rs @@ -6,7 +6,7 @@ use vecdb::{Bytes, Formattable}; use crate::{Cents, CentsSats, CentsSquaredSats, EmptyAddrData, OutputType, Sats, SupplyState}; /// Snapshot of cost basis related state. -/// Uses CentsSats (u64) for single-UTXO values, CentsSquaredSats (u128) for investor cap. +/// Uses CentsSats (u64) for single-UTXO values, CentsSquaredSats (u128) for capitalized cap. #[derive(Clone, Debug)] pub struct CostBasisSnapshot { pub realized_price: Cents, @@ -14,7 +14,7 @@ pub struct CostBasisSnapshot { /// price × sats (fits u64 for individual UTXOs) pub price_sats: CentsSats, /// price² × sats (needs u128) - pub investor_cap: CentsSquaredSats, + pub capitalized_cap_raw: CentsSquaredSats, } impl CostBasisSnapshot { @@ -26,7 +26,7 @@ impl CostBasisSnapshot { realized_price: price, supply_state: *supply, price_sats, - investor_cap: price_sats.to_investor_cap(price), + capitalized_cap_raw: price_sats.to_capitalized_cap(price), } } } @@ -49,8 +49,8 @@ pub struct FundedAddrData { pub sent: Sats, /// The realized capitalization: Σ(price × sats) pub realized_cap_raw: CentsSats, - /// The investor capitalization: Σ(price² × sats) - pub investor_cap_raw: CentsSquaredSats, + /// The capitalized cap: Σ(price² × sats) + pub capitalized_cap_raw: CentsSquaredSats, } impl FundedAddrData { @@ -72,7 +72,7 @@ impl FundedAddrData { }, // Use exact value to avoid rounding errors from realized_price × balance price_sats: CentsSats::new(self.realized_cap_raw.inner()), - investor_cap: self.investor_cap_raw, + capitalized_cap_raw: self.capitalized_cap_raw, } } @@ -137,11 +137,11 @@ impl FundedAddrData { /// This address's contribution (in sats) to the "funds at quantum risk" /// supply: its balance if currently in the funded-exposed set, else 0. #[inline] - pub fn exposed_supply_contribution(&self, output_type: OutputType) -> u64 { + pub fn exposed_supply_contribution(&self, output_type: OutputType) -> Sats { if self.is_funded_with_exposed_pubkey(output_type) { - u64::from(self.balance()) + self.balance() } else { - 0 + Sats::ZERO } } @@ -154,7 +154,7 @@ impl FundedAddrData { self.funded_txo_count += output_count; let ps = CentsSats::from_price_sats(price, amount); self.realized_cap_raw += ps; - self.investor_cap_raw += ps.to_investor_cap(price); + self.capitalized_cap_raw += ps.to_capitalized_cap(price); } pub fn send(&mut self, amount: Sats, previous_price: Cents) -> Result<()> { @@ -165,7 +165,7 @@ impl FundedAddrData { self.spent_txo_count += 1; let ps = CentsSats::from_price_sats(previous_price, amount); self.realized_cap_raw -= ps; - self.investor_cap_raw -= ps.to_investor_cap(previous_price); + self.capitalized_cap_raw -= ps.to_capitalized_cap(previous_price); Ok(()) } } @@ -188,7 +188,7 @@ impl From<&EmptyAddrData> for FundedAddrData { received: value.transfered, sent: value.transfered, realized_cap_raw: CentsSats::ZERO, - investor_cap_raw: CentsSquaredSats::ZERO, + capitalized_cap_raw: CentsSquaredSats::ZERO, } } } @@ -197,14 +197,14 @@ impl std::fmt::Display for FundedAddrData { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, - "tx_count: {}, funded_txo_count: {}, spent_txo_count: {}, received: {}, sent: {}, realized_cap_raw: {}, investor_cap_raw: {}", + "tx_count: {}, funded_txo_count: {}, spent_txo_count: {}, received: {}, sent: {}, realized_cap_raw: {}, capitalized_cap_raw: {}", self.tx_count, self.funded_txo_count, self.spent_txo_count, self.received, self.sent, self.realized_cap_raw, - self.investor_cap_raw, + self.capitalized_cap_raw, ) } } @@ -246,7 +246,7 @@ impl Bytes for FundedAddrData { arr[16..24].copy_from_slice(self.received.to_bytes().as_ref()); arr[24..32].copy_from_slice(self.sent.to_bytes().as_ref()); arr[32..48].copy_from_slice(self.realized_cap_raw.to_bytes().as_ref()); - arr[48..64].copy_from_slice(self.investor_cap_raw.to_bytes().as_ref()); + arr[48..64].copy_from_slice(self.capitalized_cap_raw.to_bytes().as_ref()); arr } @@ -259,7 +259,7 @@ impl Bytes for FundedAddrData { received: Sats::from_bytes(&bytes[16..24])?, sent: Sats::from_bytes(&bytes[24..32])?, realized_cap_raw: CentsSats::from_bytes(&bytes[32..48])?, - investor_cap_raw: CentsSquaredSats::from_bytes(&bytes[48..64])?, + capitalized_cap_raw: CentsSquaredSats::from_bytes(&bytes[48..64])?, }) } } diff --git a/modules/brk-client/index.js b/modules/brk-client/index.js index 5ccedb8d0..648fd5926 100644 --- a/modules/brk-client/index.js +++ b/modules/brk-client/index.js @@ -314,8 +314,8 @@ Matches mempool.space/bitcoin-cli behavior. */ /** * Raw cents squared (u128) - stores cents² × sats without division. - * Used for precise accumulation of investor cap values: Σ(price² × sats). - * investor_price = investor_cap_raw / realized_cap_raw + * Used for precise accumulation of capitalized cap values: Σ(price² × sats). + * capitalized_price = capitalized_cap_raw / realized_cap_raw * * @typedef {number} CentsSquaredSats */ @@ -516,7 +516,7 @@ Matches mempool.space/bitcoin-cli behavior. * @property {Sats} received - Satoshis received by this address * @property {Sats} sent - Satoshis sent by this address * @property {CentsSats} realizedCapRaw - The realized capitalization: Σ(price × sats) - * @property {CentsSquaredSats} investorCapRaw - The investor capitalization: Σ(price² × sats) + * @property {CentsSquaredSats} capitalizedCapRaw - The capitalized cap: Σ(price² × sats) */ /** @typedef {TypeIndex} FundedAddrIndex */ /** @typedef {number} Halving */ @@ -2098,10 +2098,10 @@ function create_10y1m1w1y2y3m3y4y5y6m6y8yPattern3(client, acc) { */ /** - * @typedef {Object} CapGrossInvestorLossMvrvNetPeakPriceProfitSellSoprPattern + * @typedef {Object} CapCapitalizedGrossLossMvrvNetPeakPriceProfitSellSoprPattern * @property {CentsDeltaToUsdPattern} cap + * @property {PricePattern} capitalized * @property {BlockCumulativeSumPattern} grossPnl - * @property {PricePattern} investor * @property {BlockCumulativeNegativeSumPattern} loss * @property {SeriesPattern1} mvrv * @property {BlockChangeCumulativeDeltaSumPattern} netPnl @@ -2433,11 +2433,11 @@ function createAverageMaxMedianMinPct10Pct25Pct75Pct90SumPattern(client, acc) { } /** - * @typedef {Object} GrossInvestedInvestorLossNetNuplProfitSentimentPattern2 + * @typedef {Object} CapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2 + * @property {SeriesPattern18} capitalizedCapInLossRaw + * @property {SeriesPattern18} capitalizedCapInProfitRaw * @property {CentsUsdPattern3} grossPnl * @property {InPattern} investedCapital - * @property {SeriesPattern18} investorCapInLossRaw - * @property {SeriesPattern18} investorCapInProfitRaw * @property {CentsNegativeToUsdPattern2} loss * @property {CentsToUsdPattern3} netPnl * @property {BpsRatioPattern} nupl @@ -2446,17 +2446,17 @@ function createAverageMaxMedianMinPct10Pct25Pct75Pct90SumPattern(client, acc) { */ /** - * Create a GrossInvestedInvestorLossNetNuplProfitSentimentPattern2 pattern node + * Create a CapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2 pattern node * @param {BrkClientBase} client * @param {string} acc - Accumulated series name - * @returns {GrossInvestedInvestorLossNetNuplProfitSentimentPattern2} + * @returns {CapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2} */ -function createGrossInvestedInvestorLossNetNuplProfitSentimentPattern2(client, acc) { +function createCapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2(client, acc) { return { + capitalizedCapInLossRaw: createSeriesPattern18(client, _m(acc, 'capitalized_cap_in_loss_raw')), + capitalizedCapInProfitRaw: createSeriesPattern18(client, _m(acc, 'capitalized_cap_in_profit_raw')), grossPnl: createCentsUsdPattern3(client, _m(acc, 'unrealized_gross_pnl')), investedCapital: createInPattern(client, _m(acc, 'invested_capital_in')), - investorCapInLossRaw: createSeriesPattern18(client, _m(acc, 'investor_cap_in_loss_raw')), - investorCapInProfitRaw: createSeriesPattern18(client, _m(acc, 'investor_cap_in_profit_raw')), loss: createCentsNegativeToUsdPattern2(client, _m(acc, 'unrealized_loss')), netPnl: createCentsToUsdPattern3(client, _m(acc, 'net_unrealized_pnl')), nupl: createBpsRatioPattern(client, _m(acc, 'nupl')), @@ -5183,6 +5183,20 @@ function createTransferPattern(client, acc) { * @property {BtcCentsSatsUsdPattern} p2wsh * @property {BtcCentsSatsUsdPattern} p2tr * @property {BtcCentsSatsUsdPattern} p2a + * @property {SeriesTree_Addrs_Exposed_Supply_Share} share + */ + +/** + * @typedef {Object} SeriesTree_Addrs_Exposed_Supply_Share + * @property {BpsPercentRatioPattern2} all + * @property {BpsPercentRatioPattern2} p2pk65 + * @property {BpsPercentRatioPattern2} p2pk33 + * @property {BpsPercentRatioPattern2} p2pkh + * @property {BpsPercentRatioPattern2} p2sh + * @property {BpsPercentRatioPattern2} p2wpkh + * @property {BpsPercentRatioPattern2} p2wsh + * @property {BpsPercentRatioPattern2} p2tr + * @property {BpsPercentRatioPattern2} p2a */ /** @@ -6261,7 +6275,7 @@ function createTransferPattern(client, acc) { * @property {BlockCumulativeSumPattern} grossPnl * @property {_1m1w1y24hPattern7} sellSideRiskRatio * @property {BlockCumulativeSumPattern} peakRegret - * @property {PricePattern} investor + * @property {PricePattern} capitalized * @property {_1m1w1y24hPattern} profitToLossRatio */ @@ -6394,8 +6408,8 @@ function createTransferPattern(client, acc) { * @property {SeriesTree_Cohorts_Utxo_All_Unrealized_NetPnl} netPnl * @property {CentsUsdPattern3} grossPnl * @property {InPattern} investedCapital - * @property {SeriesPattern18} investorCapInProfitRaw - * @property {SeriesPattern18} investorCapInLossRaw + * @property {SeriesPattern18} capitalizedCapInProfitRaw + * @property {SeriesPattern18} capitalizedCapInLossRaw * @property {SeriesTree_Cohorts_Utxo_All_Unrealized_Sentiment} sentiment */ @@ -6437,7 +6451,7 @@ function createTransferPattern(client, acc) { * @property {CoindaysCoinyearsDormancyTransferPattern} activity * @property {SeriesTree_Cohorts_Utxo_Sth_Realized} realized * @property {InMaxMinPerSupplyPattern} costBasis - * @property {GrossInvestedInvestorLossNetNuplProfitSentimentPattern2} unrealized + * @property {CapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2} unrealized */ /** @@ -6452,7 +6466,7 @@ function createTransferPattern(client, acc) { * @property {BlockCumulativeSumPattern} grossPnl * @property {_1m1w1y24hPattern7} sellSideRiskRatio * @property {BlockCumulativeSumPattern} peakRegret - * @property {PricePattern} investor + * @property {PricePattern} capitalized * @property {_1m1w1y24hPattern} profitToLossRatio */ @@ -6559,7 +6573,7 @@ function createTransferPattern(client, acc) { * @property {CoindaysCoinyearsDormancyTransferPattern} activity * @property {SeriesTree_Cohorts_Utxo_Lth_Realized} realized * @property {InMaxMinPerSupplyPattern} costBasis - * @property {GrossInvestedInvestorLossNetNuplProfitSentimentPattern2} unrealized + * @property {CapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2} unrealized */ /** @@ -6574,7 +6588,7 @@ function createTransferPattern(client, acc) { * @property {BlockCumulativeSumPattern} grossPnl * @property {_1m1w1y24hPattern7} sellSideRiskRatio * @property {BlockCumulativeSumPattern} peakRegret - * @property {PricePattern} investor + * @property {PricePattern} capitalized * @property {_1m1w1y24hPattern} profitToLossRatio */ @@ -8543,15 +8557,26 @@ class BrkClient extends BrkClientBase { exposed: { count: createFundedTotalPattern(this, 'exposed_addr_count'), supply: { - all: createBtcCentsSatsUsdPattern(this, 'exposed_addr_supply'), - p2pk65: createBtcCentsSatsUsdPattern(this, 'p2pk65_exposed_addr_supply'), - p2pk33: createBtcCentsSatsUsdPattern(this, 'p2pk33_exposed_addr_supply'), - p2pkh: createBtcCentsSatsUsdPattern(this, 'p2pkh_exposed_addr_supply'), - p2sh: createBtcCentsSatsUsdPattern(this, 'p2sh_exposed_addr_supply'), - p2wpkh: createBtcCentsSatsUsdPattern(this, 'p2wpkh_exposed_addr_supply'), - p2wsh: createBtcCentsSatsUsdPattern(this, 'p2wsh_exposed_addr_supply'), - p2tr: createBtcCentsSatsUsdPattern(this, 'p2tr_exposed_addr_supply'), - p2a: createBtcCentsSatsUsdPattern(this, 'p2a_exposed_addr_supply'), + all: createBtcCentsSatsUsdPattern(this, 'exposed_supply'), + p2pk65: createBtcCentsSatsUsdPattern(this, 'p2pk65_exposed_supply'), + p2pk33: createBtcCentsSatsUsdPattern(this, 'p2pk33_exposed_supply'), + p2pkh: createBtcCentsSatsUsdPattern(this, 'p2pkh_exposed_supply'), + p2sh: createBtcCentsSatsUsdPattern(this, 'p2sh_exposed_supply'), + p2wpkh: createBtcCentsSatsUsdPattern(this, 'p2wpkh_exposed_supply'), + p2wsh: createBtcCentsSatsUsdPattern(this, 'p2wsh_exposed_supply'), + p2tr: createBtcCentsSatsUsdPattern(this, 'p2tr_exposed_supply'), + p2a: createBtcCentsSatsUsdPattern(this, 'p2a_exposed_supply'), + share: { + all: createBpsPercentRatioPattern2(this, 'exposed_supply_share'), + p2pk65: createBpsPercentRatioPattern2(this, 'p2pk65_exposed_supply_share'), + p2pk33: createBpsPercentRatioPattern2(this, 'p2pk33_exposed_supply_share'), + p2pkh: createBpsPercentRatioPattern2(this, 'p2pkh_exposed_supply_share'), + p2sh: createBpsPercentRatioPattern2(this, 'p2sh_exposed_supply_share'), + p2wpkh: createBpsPercentRatioPattern2(this, 'p2wpkh_exposed_supply_share'), + p2wsh: createBpsPercentRatioPattern2(this, 'p2wsh_exposed_supply_share'), + p2tr: createBpsPercentRatioPattern2(this, 'p2tr_exposed_supply_share'), + p2a: createBpsPercentRatioPattern2(this, 'p2a_exposed_supply_share'), + }, }, }, delta: { @@ -9394,7 +9419,7 @@ class BrkClient extends BrkClientBase { grossPnl: createBlockCumulativeSumPattern(this, 'realized_gross_pnl'), sellSideRiskRatio: create_1m1w1y24hPattern7(this, 'sell_side_risk_ratio'), peakRegret: createBlockCumulativeSumPattern(this, 'realized_peak_regret'), - investor: createPricePattern(this, 'investor_price'), + capitalized: createPricePattern(this, 'capitalized_price'), profitToLossRatio: create_1m1w1y24hPattern(this, 'realized_profit_to_loss_ratio'), }, costBasis: { @@ -9428,8 +9453,8 @@ class BrkClient extends BrkClientBase { }, grossPnl: createCentsUsdPattern3(this, 'unrealized_gross_pnl'), investedCapital: createInPattern(this, 'invested_capital_in'), - investorCapInProfitRaw: createSeriesPattern18(this, 'investor_cap_in_profit_raw'), - investorCapInLossRaw: createSeriesPattern18(this, 'investor_cap_in_loss_raw'), + capitalizedCapInProfitRaw: createSeriesPattern18(this, 'capitalized_cap_in_profit_raw'), + capitalizedCapInLossRaw: createSeriesPattern18(this, 'capitalized_cap_in_loss_raw'), sentiment: { painIndex: createCentsUsdPattern3(this, 'pain_index'), greedIndex: createCentsUsdPattern3(this, 'greed_index'), @@ -9530,11 +9555,11 @@ class BrkClient extends BrkClientBase { grossPnl: createBlockCumulativeSumPattern(this, 'sth_realized_gross_pnl'), sellSideRiskRatio: create_1m1w1y24hPattern7(this, 'sth_sell_side_risk_ratio'), peakRegret: createBlockCumulativeSumPattern(this, 'sth_realized_peak_regret'), - investor: createPricePattern(this, 'sth_investor_price'), + capitalized: createPricePattern(this, 'sth_capitalized_price'), profitToLossRatio: create_1m1w1y24hPattern(this, 'sth_realized_profit_to_loss_ratio'), }, costBasis: createInMaxMinPerSupplyPattern(this, 'sth'), - unrealized: createGrossInvestedInvestorLossNetNuplProfitSentimentPattern2(this, 'sth'), + unrealized: createCapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2(this, 'sth'), }, lth: { supply: createDeltaHalfInToTotalPattern2(this, 'lth_supply'), @@ -9632,11 +9657,11 @@ class BrkClient extends BrkClientBase { grossPnl: createBlockCumulativeSumPattern(this, 'lth_realized_gross_pnl'), sellSideRiskRatio: create_1m1w1y24hPattern7(this, 'lth_sell_side_risk_ratio'), peakRegret: createBlockCumulativeSumPattern(this, 'lth_realized_peak_regret'), - investor: createPricePattern(this, 'lth_investor_price'), + capitalized: createPricePattern(this, 'lth_capitalized_price'), profitToLossRatio: create_1m1w1y24hPattern(this, 'lth_realized_profit_to_loss_ratio'), }, costBasis: createInMaxMinPerSupplyPattern(this, 'lth'), - unrealized: createGrossInvestedInvestorLossNetNuplProfitSentimentPattern2(this, 'lth'), + unrealized: createCapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2(this, 'lth'), }, ageRange: { under1h: createActivityOutputsRealizedSupplyUnrealizedPattern(this, 'utxos_under_1h_old'), diff --git a/packages/brk_client/brk_client/__init__.py b/packages/brk_client/brk_client/__init__.py index eefd0731c..545930b7b 100644 --- a/packages/brk_client/brk_client/__init__.py +++ b/packages/brk_client/brk_client/__init__.py @@ -77,8 +77,8 @@ CentsSats = int # Used for profit/loss calculations, deltas, etc. CentsSigned = int # Raw cents squared (u128) - stores cents² × sats without division. -# Used for precise accumulation of investor cap values: Σ(price² × sats). -# investor_price = investor_cap_raw / realized_cap_raw +# Used for precise accumulation of capitalized cap values: Σ(price² × sats). +# capitalized_price = capitalized_cap_raw / realized_cap_raw CentsSquaredSats = int # Closing price value for a time period Close = Dollars @@ -843,7 +843,7 @@ class FundedAddrData(TypedDict): received: Satoshis received by this address sent: Satoshis sent by this address realized_cap_raw: The realized capitalization: Σ(price × sats) - investor_cap_raw: The investor capitalization: Σ(price² × sats) + capitalized_cap_raw: The capitalized cap: Σ(price² × sats) """ tx_count: int funded_txo_count: int @@ -851,7 +851,7 @@ class FundedAddrData(TypedDict): received: Sats sent: Sats realized_cap_raw: CentsSats - investor_cap_raw: CentsSquaredSats + capitalized_cap_raw: CentsSquaredSats class HashrateEntry(TypedDict): """ @@ -2649,7 +2649,7 @@ class AllEmptyP2aP2msP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshUnknownPattern: """Pattern struct for repeated tree structure.""" pass -class CapGrossInvestorLossMvrvNetPeakPriceProfitSellSoprPattern: +class CapCapitalizedGrossLossMvrvNetPeakPriceProfitSellSoprPattern: """Pattern struct for repeated tree structure.""" pass @@ -2798,15 +2798,15 @@ class AverageMaxMedianMinPct10Pct25Pct75Pct90SumPattern: self.pct90: _1m1w1y24hPattern[StoredU64] = _1m1w1y24hPattern(client, _m(acc, 'pct90')) self.sum: _1m1w1y24hPattern[StoredU64] = _1m1w1y24hPattern(client, _m(acc, 'sum')) -class GrossInvestedInvestorLossNetNuplProfitSentimentPattern2: +class CapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2: """Pattern struct for repeated tree structure.""" def __init__(self, client: BrkClientBase, acc: str): """Create pattern node with accumulated series name.""" + self.capitalized_cap_in_loss_raw: SeriesPattern18[CentsSquaredSats] = SeriesPattern18(client, _m(acc, 'capitalized_cap_in_loss_raw')) + self.capitalized_cap_in_profit_raw: SeriesPattern18[CentsSquaredSats] = SeriesPattern18(client, _m(acc, 'capitalized_cap_in_profit_raw')) self.gross_pnl: CentsUsdPattern3 = CentsUsdPattern3(client, _m(acc, 'unrealized_gross_pnl')) self.invested_capital: InPattern = InPattern(client, _m(acc, 'invested_capital_in')) - self.investor_cap_in_loss_raw: SeriesPattern18[CentsSquaredSats] = SeriesPattern18(client, _m(acc, 'investor_cap_in_loss_raw')) - self.investor_cap_in_profit_raw: SeriesPattern18[CentsSquaredSats] = SeriesPattern18(client, _m(acc, 'investor_cap_in_profit_raw')) self.loss: CentsNegativeToUsdPattern2 = CentsNegativeToUsdPattern2(client, _m(acc, 'unrealized_loss')) self.net_pnl: CentsToUsdPattern3 = CentsToUsdPattern3(client, _m(acc, 'net_unrealized_pnl')) self.nupl: BpsRatioPattern = BpsRatioPattern(client, _m(acc, 'nupl')) @@ -4284,19 +4284,34 @@ class SeriesTree_Addrs_Reused: self.count: FundedTotalPattern = FundedTotalPattern(client, 'reused_addr_count') self.events: SeriesTree_Addrs_Reused_Events = SeriesTree_Addrs_Reused_Events(client) +class SeriesTree_Addrs_Exposed_Supply_Share: + """Series tree node.""" + + def __init__(self, client: BrkClientBase, base_path: str = ''): + self.all: BpsPercentRatioPattern2 = BpsPercentRatioPattern2(client, 'exposed_supply_share') + self.p2pk65: BpsPercentRatioPattern2 = BpsPercentRatioPattern2(client, 'p2pk65_exposed_supply_share') + self.p2pk33: BpsPercentRatioPattern2 = BpsPercentRatioPattern2(client, 'p2pk33_exposed_supply_share') + self.p2pkh: BpsPercentRatioPattern2 = BpsPercentRatioPattern2(client, 'p2pkh_exposed_supply_share') + self.p2sh: BpsPercentRatioPattern2 = BpsPercentRatioPattern2(client, 'p2sh_exposed_supply_share') + self.p2wpkh: BpsPercentRatioPattern2 = BpsPercentRatioPattern2(client, 'p2wpkh_exposed_supply_share') + self.p2wsh: BpsPercentRatioPattern2 = BpsPercentRatioPattern2(client, 'p2wsh_exposed_supply_share') + self.p2tr: BpsPercentRatioPattern2 = BpsPercentRatioPattern2(client, 'p2tr_exposed_supply_share') + self.p2a: BpsPercentRatioPattern2 = BpsPercentRatioPattern2(client, 'p2a_exposed_supply_share') + class SeriesTree_Addrs_Exposed_Supply: """Series tree node.""" def __init__(self, client: BrkClientBase, base_path: str = ''): - self.all: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'exposed_addr_supply') - self.p2pk65: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'p2pk65_exposed_addr_supply') - self.p2pk33: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'p2pk33_exposed_addr_supply') - self.p2pkh: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'p2pkh_exposed_addr_supply') - self.p2sh: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'p2sh_exposed_addr_supply') - self.p2wpkh: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'p2wpkh_exposed_addr_supply') - self.p2wsh: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'p2wsh_exposed_addr_supply') - self.p2tr: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'p2tr_exposed_addr_supply') - self.p2a: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'p2a_exposed_addr_supply') + self.all: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'exposed_supply') + self.p2pk65: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'p2pk65_exposed_supply') + self.p2pk33: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'p2pk33_exposed_supply') + self.p2pkh: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'p2pkh_exposed_supply') + self.p2sh: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'p2sh_exposed_supply') + self.p2wpkh: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'p2wpkh_exposed_supply') + self.p2wsh: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'p2wsh_exposed_supply') + self.p2tr: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'p2tr_exposed_supply') + self.p2a: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'p2a_exposed_supply') + self.share: SeriesTree_Addrs_Exposed_Supply_Share = SeriesTree_Addrs_Exposed_Supply_Share(client) class SeriesTree_Addrs_Exposed: """Series tree node.""" @@ -5585,7 +5600,7 @@ class SeriesTree_Cohorts_Utxo_All_Realized: self.gross_pnl: BlockCumulativeSumPattern = BlockCumulativeSumPattern(client, 'realized_gross_pnl') self.sell_side_risk_ratio: _1m1w1y24hPattern7 = _1m1w1y24hPattern7(client, 'sell_side_risk_ratio') self.peak_regret: BlockCumulativeSumPattern = BlockCumulativeSumPattern(client, 'realized_peak_regret') - self.investor: PricePattern = PricePattern(client, 'investor_price') + self.capitalized: PricePattern = PricePattern(client, 'capitalized_price') self.profit_to_loss_ratio: _1m1w1y24hPattern[StoredF64] = _1m1w1y24hPattern(client, 'realized_profit_to_loss_ratio') class SeriesTree_Cohorts_Utxo_All_CostBasis: @@ -5645,8 +5660,8 @@ class SeriesTree_Cohorts_Utxo_All_Unrealized: self.net_pnl: SeriesTree_Cohorts_Utxo_All_Unrealized_NetPnl = SeriesTree_Cohorts_Utxo_All_Unrealized_NetPnl(client) self.gross_pnl: CentsUsdPattern3 = CentsUsdPattern3(client, 'unrealized_gross_pnl') self.invested_capital: InPattern = InPattern(client, 'invested_capital_in') - self.investor_cap_in_profit_raw: SeriesPattern18[CentsSquaredSats] = SeriesPattern18(client, 'investor_cap_in_profit_raw') - self.investor_cap_in_loss_raw: SeriesPattern18[CentsSquaredSats] = SeriesPattern18(client, 'investor_cap_in_loss_raw') + self.capitalized_cap_in_profit_raw: SeriesPattern18[CentsSquaredSats] = SeriesPattern18(client, 'capitalized_cap_in_profit_raw') + self.capitalized_cap_in_loss_raw: SeriesPattern18[CentsSquaredSats] = SeriesPattern18(client, 'capitalized_cap_in_loss_raw') self.sentiment: SeriesTree_Cohorts_Utxo_All_Unrealized_Sentiment = SeriesTree_Cohorts_Utxo_All_Unrealized_Sentiment(client) class SeriesTree_Cohorts_Utxo_All: @@ -5776,7 +5791,7 @@ class SeriesTree_Cohorts_Utxo_Sth_Realized: self.gross_pnl: BlockCumulativeSumPattern = BlockCumulativeSumPattern(client, 'sth_realized_gross_pnl') self.sell_side_risk_ratio: _1m1w1y24hPattern7 = _1m1w1y24hPattern7(client, 'sth_sell_side_risk_ratio') self.peak_regret: BlockCumulativeSumPattern = BlockCumulativeSumPattern(client, 'sth_realized_peak_regret') - self.investor: PricePattern = PricePattern(client, 'sth_investor_price') + self.capitalized: PricePattern = PricePattern(client, 'sth_capitalized_price') self.profit_to_loss_ratio: _1m1w1y24hPattern[StoredF64] = _1m1w1y24hPattern(client, 'sth_realized_profit_to_loss_ratio') class SeriesTree_Cohorts_Utxo_Sth: @@ -5788,7 +5803,7 @@ class SeriesTree_Cohorts_Utxo_Sth: self.activity: CoindaysCoinyearsDormancyTransferPattern = CoindaysCoinyearsDormancyTransferPattern(client, 'sth') self.realized: SeriesTree_Cohorts_Utxo_Sth_Realized = SeriesTree_Cohorts_Utxo_Sth_Realized(client) self.cost_basis: InMaxMinPerSupplyPattern = InMaxMinPerSupplyPattern(client, 'sth') - self.unrealized: GrossInvestedInvestorLossNetNuplProfitSentimentPattern2 = GrossInvestedInvestorLossNetNuplProfitSentimentPattern2(client, 'sth') + self.unrealized: CapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2 = CapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2(client, 'sth') class SeriesTree_Cohorts_Utxo_Lth_Realized_Price_StdDev_All: """Series tree node.""" @@ -5913,7 +5928,7 @@ class SeriesTree_Cohorts_Utxo_Lth_Realized: self.gross_pnl: BlockCumulativeSumPattern = BlockCumulativeSumPattern(client, 'lth_realized_gross_pnl') self.sell_side_risk_ratio: _1m1w1y24hPattern7 = _1m1w1y24hPattern7(client, 'lth_sell_side_risk_ratio') self.peak_regret: BlockCumulativeSumPattern = BlockCumulativeSumPattern(client, 'lth_realized_peak_regret') - self.investor: PricePattern = PricePattern(client, 'lth_investor_price') + self.capitalized: PricePattern = PricePattern(client, 'lth_capitalized_price') self.profit_to_loss_ratio: _1m1w1y24hPattern[StoredF64] = _1m1w1y24hPattern(client, 'lth_realized_profit_to_loss_ratio') class SeriesTree_Cohorts_Utxo_Lth: @@ -5925,7 +5940,7 @@ class SeriesTree_Cohorts_Utxo_Lth: self.activity: CoindaysCoinyearsDormancyTransferPattern = CoindaysCoinyearsDormancyTransferPattern(client, 'lth') self.realized: SeriesTree_Cohorts_Utxo_Lth_Realized = SeriesTree_Cohorts_Utxo_Lth_Realized(client) self.cost_basis: InMaxMinPerSupplyPattern = InMaxMinPerSupplyPattern(client, 'lth') - self.unrealized: GrossInvestedInvestorLossNetNuplProfitSentimentPattern2 = GrossInvestedInvestorLossNetNuplProfitSentimentPattern2(client, 'lth') + self.unrealized: CapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2 = CapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2(client, 'lth') class SeriesTree_Cohorts_Utxo_AgeRange: """Series tree node.""" diff --git a/website/scripts/_types.js b/website/scripts/_types.js index 7b8aac357..1dd28b432 100644 --- a/website/scripts/_types.js +++ b/website/scripts/_types.js @@ -86,7 +86,7 @@ * Profitability bucket pattern (supply + realized_cap + unrealized_pnl + nupl) * @typedef {Brk.NuplRealizedSupplyUnrealizedPattern} RealizedSupplyPattern * - * Realized pattern (full: cap + gross + investor + loss + mvrv + net + peak + price + profit + sell + sopr) + * Realized pattern (full: cap + gross + capitalized + loss + mvrv + net + peak + price + profit + sell + sopr) * @typedef {Brk.CapGrossInvestorLossMvrvNetPeakPriceProfitSellSoprPattern} RealizedPattern * * Transfer volume pattern (block + cumulative + inProfit/inLoss + sum windows) @@ -256,9 +256,9 @@ * @typedef {Brk.AbsoluteRatePattern} DeltaPattern * @typedef {Brk.AbsoluteRatePattern2} FiatDeltaPattern * - * Investor price percentiles (pct1/2/5/95/98/99) - * @typedef {Brk.Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern} InvestorPercentilesPattern - * @typedef {Brk.BpsPriceRatioPattern} InvestorPercentileEntry + * Capitalized price percentiles (pct1/2/5/95/98/99) + * @typedef {Brk.Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern} CapitalizedPercentilesPattern + * @typedef {Brk.BpsPriceRatioPattern} CapitalizedPercentileEntry * * Generic tree node type for walking * @typedef {AnySeriesPattern | Record} TreeNode diff --git a/website/scripts/explorer/chain.js b/website/scripts/explorer/chain.js index c537d4e6b..8d9c06305 100644 --- a/website/scripts/explorer/chain.js +++ b/website/scripts/explorer/chain.js @@ -117,12 +117,13 @@ function appendNewerBlocks(blocks) { for (let i = blocks.length - 1; i >= 0; i--) { const b = blocks[i]; if (b.height > newestHeight) { - blocksEl.append(createBlockCube(b)); + appendCube(createBlockCube(b)); } else { blocksByHash.set(b.id, b); } } newestHeight = Math.max(newestHeight, blocks[0].height); + if (anchor && anchorRect) { const r = anchor.getBoundingClientRect(); chainEl.scrollTop += r.top - anchorRect.top; @@ -139,11 +140,12 @@ async function loadInitial(height) { : await brk.getBlocksV1(); clear(); - for (const b of blocks) blocksEl.prepend(createBlockCube(b)); + for (const b of blocks) prependCube(createBlockCube(b)); newestHeight = blocks[0].height; oldestHeight = blocks[blocks.length - 1].height; reachedTip = height == null; observeOldestEdge(); + if (!reachedTip) await loadNewer(); return blocks[0].id; } @@ -197,11 +199,12 @@ async function loadOlder() { loadingOlder = true; try { const blocks = await brk.getBlocksV1FromHeight(oldestHeight - 1); - for (const block of blocks) blocksEl.prepend(createBlockCube(block)); + for (const block of blocks) prependCube(createBlockCube(block)); if (blocks.length) { oldestHeight = blocks[blocks.length - 1].height; observeOldestEdge(); } + } catch (e) { console.error("explorer loadOlder:", e); } @@ -227,6 +230,8 @@ function createBlockCube(block) { cubeElement.dataset.hash = block.id; cubeElement.dataset.height = String(block.height); + cubeElement.dataset.timestamp = String(block.timestamp); + cubeElement.style.setProperty("--fill", String(Math.min(1, block.weight / 3_990_000))); blocksByHash.set(block.id, block); cubeElement.addEventListener("click", () => onCubeClick(cubeElement)); @@ -268,6 +273,9 @@ function createBlockCube(block) { function createCube() { const cubeElement = document.createElement("div"); cubeElement.classList.add("cube"); + const innerTopElement = document.createElement("div"); + innerTopElement.classList.add("face", "inner-top"); + cubeElement.append(innerTopElement); const rightFaceElement = document.createElement("div"); rightFaceElement.classList.add("face", "right"); cubeElement.append(rightFaceElement); @@ -279,3 +287,25 @@ function createCube() { cubeElement.append(topFaceElement); return { cubeElement, leftFaceElement, rightFaceElement, topFaceElement }; } + +/** @param {HTMLElement} cube */ +function setGap(cube) { + const prev = /** @type {HTMLElement | null} */ (cube.previousElementSibling); + if (!prev) return; + const dt = Math.max(0, Number(cube.dataset.timestamp) - Number(prev.dataset.timestamp)); + cube.style.setProperty("--dt", String(dt)); +} + +/** @param {HTMLDivElement} cube */ +function prependCube(cube) { + const next = /** @type {HTMLElement | null} */ (blocksEl.firstElementChild); + blocksEl.prepend(cube); + if (next) setGap(next); +} + +/** @param {HTMLDivElement} cube */ +function appendCube(cube) { + blocksEl.append(cube); + setGap(cube); +} + diff --git a/website/scripts/options/cointime.js b/website/scripts/options/cointime.js index b5ee0ef5e..fe12af1ec 100644 --- a/website/scripts/options/cointime.js +++ b/website/scripts/options/cointime.js @@ -178,9 +178,9 @@ export function createCointimeSection() { color: colors.realized, }), price({ - series: all.realized.investor.price, - name: "Investor", - color: colors.investor, + series: all.realized.capitalized.price, + name: "Capitalized", + color: colors.capitalized, }), ...prices.map(({ pattern, name, color, defaultActive }) => price({ series: pattern, name, color, defaultActive }), diff --git a/website/scripts/options/distribution/cost-basis.js b/website/scripts/options/distribution/cost-basis.js index 5cfadb810..9b41cb88a 100644 --- a/website/scripts/options/distribution/cost-basis.js +++ b/website/scripts/options/distribution/cost-basis.js @@ -42,7 +42,7 @@ function percentileSeries(p, n = (x) => x) { /** * Per Coin or Per Dollar folder for a single cohort * @param {Object} args - * @param {AnyPricePattern} args.avgPrice - realized price (per coin) or investor price (per dollar) + * @param {AnyPricePattern} args.avgPrice - realized price (per coin) or capitalized price (per dollar) * @param {string} args.avgName * @param {AnyPricePattern} args.inProfit * @param {AnyPricePattern} args.inLoss @@ -100,7 +100,7 @@ export function createCostBasisSectionWithPercentiles({ cohort, title }) { { name: "Per Dollar", tree: singleWeightFolder({ - avgPrice: tree.realized.investor.price, avgName: "All", + avgPrice: tree.realized.capitalized.price, avgName: "All", inProfit: cb.inProfit.perDollar, inLoss: cb.inLoss.perDollar, percentiles: cb.perDollar, color, weightLabel: "USD-weighted", title, }), @@ -192,7 +192,7 @@ export function createGroupedCostBasisSectionWithPercentiles({ list, all, title name: "Per Dollar", tree: groupedWeightFolder({ list, all, title, - getAvgPrice: (c) => c.tree.realized.investor.price, + getAvgPrice: (c) => c.tree.realized.capitalized.price, getInProfit: (c) => c.tree.costBasis.inProfit.perDollar, getInLoss: (c) => c.tree.costBasis.inLoss.perDollar, getPercentiles: (c) => c.tree.costBasis.perDollar, diff --git a/website/scripts/options/distribution/prices.js b/website/scripts/options/distribution/prices.js index b1a0615ad..b24556a58 100644 --- a/website/scripts/options/distribution/prices.js +++ b/website/scripts/options/distribution/prices.js @@ -4,11 +4,11 @@ * Structure (single cohort): * - Compare: Both prices on one chart * - Realized: Price + Ratio (MVRV) + Z-Scores (for full cohorts) - * - Investor: Price + Ratio + Z-Scores (for full cohorts) + * - Capitalized: Price + Ratio + Z-Scores (for full cohorts) * * Structure (grouped cohorts): * - Realized: Price + Ratio comparison across cohorts - * - Investor: Price + Ratio comparison across cohorts + * - Capitalized: Price + Ratio comparison across cohorts * * For cohorts WITHOUT full ratio patterns: basic Price/Ratio charts only (no Z-Scores) */ @@ -34,7 +34,7 @@ export function createPricesSectionFull({ cohort, title }) { title: title("Realized Prices"), top: [ price({ series: tree.realized.price, name: "Realized", color: colors.realized }), - price({ series: tree.realized.investor.price, name: "Investor", color: colors.investor }), + price({ series: tree.realized.capitalized.price, name: "Capitalized", color: colors.capitalized }), ], }, { @@ -50,12 +50,12 @@ export function createPricesSectionFull({ cohort, title }) { }), }, { - name: "Investor", + name: "Capitalized", tree: priceRatioPercentilesTree({ - pattern: tree.realized.investor.price, - title: title("Investor Price"), - ratioTitle: title("Investor Price Ratio"), - legend: "Investor", + pattern: tree.realized.capitalized.price, + title: title("Capitalized Price"), + ratioTitle: title("Capitalized Price Ratio"), + legend: "Capitalized", color, }), }, @@ -150,20 +150,20 @@ export function createGroupedPricesSectionFull({ list, all, title }) { tree: [ ...groupedRealizedPriceItems(list, all, title), { - name: "Investor", + name: "Capitalized", tree: [ { name: "Price", - title: title("Investor Price"), + title: title("Capitalized Price"), top: mapCohortsWithAll(list, all, ({ name, color, tree }) => - price({ series: tree.realized.investor.price, name, color }), + price({ series: tree.realized.capitalized.price, name, color }), ), }, { name: "Ratio", - title: title("Investor Price Ratio"), + title: title("Capitalized Price Ratio"), bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => - baseline({ series: tree.realized.investor.price.ratio, name, color, unit: Unit.ratio, base: 1 }), + baseline({ series: tree.realized.capitalized.price.ratio, name, color, unit: Unit.ratio, base: 1 }), ), }, ], diff --git a/website/scripts/panes/search.js b/website/scripts/panes/search.js index 2cefaa65f..ddd274c45 100644 --- a/website/scripts/panes/search.js +++ b/website/scripts/panes/search.js @@ -4,6 +4,7 @@ import { searchResultsElement, } from "../utils/elements.js"; import { QuickMatch } from "../modules/quickmatch-js/0.4.1/src/index.js"; +import { brk } from "../utils/client.js"; /** * @param {Options} options @@ -28,9 +29,67 @@ export function init(options) { if (li) li.dataset.highlight = ""; } + const HEX64_RE = /^[0-9a-f]{64}$/i; + const ADDR_RE = /^([13][a-km-zA-HJ-NP-Z1-9]{25,34}|bc1[a-z0-9]{8,87})$/; + + /** @param {string} label @param {string} href @param {Element | null} [before] */ + function createResultLink(label, href, before) { + const li = window.document.createElement("li"); + const a = window.document.createElement("a"); + a.href = href; + a.textContent = label; + a.title = label; + if (href === window.location.pathname) setHighlight(li); + a.addEventListener("click", (e) => { + e.preventDefault(); + setHighlight(li); + history.pushState(null, "", href); + options.resolveUrl(); + }); + li.append(a); + searchResultsElement.insertBefore(li, before ?? null); + } + + /** @type {AbortController | undefined} */ + let lookupController; + + /** @param {string} needle @param {AbortSignal} signal */ + async function lookup(needle, signal) { + /** @type {Array<[string, string]>} */ + const results = []; + + if (HEX64_RE.test(needle)) { + const [blockRes, txRes] = await Promise.allSettled([ + brk.getBlock(needle, { signal }), + brk.getTx(needle, { signal }), + ]); + if (signal.aborted) return; + if (blockRes.status === "fulfilled") results.push(["Block", `/block/${needle}`]); + if (txRes.status === "fulfilled") results.push(["Transaction", `/tx/${needle}`]); + } else if (ADDR_RE.test(needle)) { + try { + const { isvalid } = await brk.validateAddress(needle, { signal }); + if (signal.aborted || !isvalid) return; + results.push(["Address", `/address/${needle}`]); + } catch { return; } + } else { + return; + } + + const before = searchResultsElement.firstElementChild; + for (const [label, href] of results) { + createResultLink(`${label} ${needle}`, href, before); + } + // Remove "No results" placeholder if present + const last = searchResultsElement.lastElementChild; + if (last && !last.querySelector("a")) last.remove(); + } + function inputEvent() { const needle = /** @type {string} */ (searchInput.value).trim(); + if (lookupController) lookupController.abort(); + searchResultsElement.scrollTo({ top: 0 }); searchResultsElement.innerHTML = ""; setHighlight(); @@ -54,23 +113,13 @@ export function init(options) { ["Transaction", `/tx/${num}`], ]; for (const [label, href] of entries) { - const li = window.document.createElement("li"); - const a = window.document.createElement("a"); - a.href = href; - a.textContent = `${label} #${num}`; - a.title = `${label} #${num}`; - if (href === window.location.pathname) setHighlight(li); - a.addEventListener("click", (e) => { - e.preventDefault(); - setHighlight(li); - history.pushState(null, "", href); - options.resolveUrl(); - }); - li.append(a); - searchResultsElement.appendChild(li); + createResultLink(`${label} #${num}`, href); } } + lookupController = new AbortController(); + lookup(needle, lookupController.signal); + if (matches.length) { matches.forEach((title) => { const option = titleToOption.get(title); diff --git a/website/scripts/utils/colors.js b/website/scripts/utils/colors.js index 5745d8928..ca3017d90 100644 --- a/website/scripts/utils/colors.js +++ b/website/scripts/utils/colors.js @@ -162,6 +162,7 @@ export const colors = { // Valuations realized: palette.orange, investor: palette.fuchsia, + capitalized: palette.green, thermo: palette.emerald, trueMarketMean: palette.blue, vocdd: palette.purple, diff --git a/website/styles/nav.css b/website/styles/nav.css index aed441ae1..6698d3b40 100644 --- a/website/styles/nav.css +++ b/website/styles/nav.css @@ -28,6 +28,10 @@ nav { padding: 0.25rem 0; } + a:visited { + color: transparent; + } + ul { color: var(--off-color); overflow: hidden; diff --git a/website/styles/panes/explorer.css b/website/styles/panes/explorer.css index 6baf37046..355950e22 100644 --- a/website/styles/panes/explorer.css +++ b/website/styles/panes/explorer.css @@ -40,13 +40,16 @@ .blocks { display: flex; flex-direction: column-reverse; - --gap: 0.8; - gap: calc(var(--cube) * var(--gap)); + --min-gap: 0rem; + --max-gap: calc(var(--cube) * 6); + --min-dt: 0; + --max-dt: 10800; margin-right: var(--cube); margin-top: calc(var(--cube) * -0.25); @media (max-width: 767px) { - --gap: 1.25; + --min-gap: 0rem; + --max-gap: calc(var(--cube) * 1.5); flex-direction: row-reverse; height: 11.5rem; width: max-content; @@ -58,7 +61,32 @@ } .cube { - margin-top: -0.375rem; + --t: pow( + clamp( + 0, + (var(--dt, 600) - var(--min-dt)) / (var(--max-dt) - var(--min-dt)), + 1 + ), + 0.7 + ); + --block-gap: calc( + var(--min-gap) + var(--t) * (var(--max-gap) - var(--min-gap)) + ); + --empty-alpha: 0.5; + --face-step: 0.033; + --face-right-color: light-dark( + oklch(from var(--face-color) calc(l - var(--face-step) * 2) c h), + var(--face-color) + ); + --face-left-color: light-dark( + oklch(from var(--face-color) calc(l - var(--face-step)) c h), + oklch(from var(--face-color) calc(l + var(--face-step)) c h) + ); + --face-top-color: light-dark( + var(--face-color), + oklch(from var(--face-color) calc(l + var(--face-step) * 2) c h) + ); + /*margin-top: -0.375rem;*/ margin-left: calc(var(--cube) * -0.25); flex-shrink: 0; position: relative; @@ -116,17 +144,40 @@ width: var(--cube); height: var(--cube); padding: 0.1rem; + backdrop-filter: blur(4px); + } + + .inner-top { + backdrop-filter: none; + background-color: var(--face-top-color); + /*-webkit-mask-image: linear-gradient(transparent, black 0.5rem, black calc(100% - 0.5rem), transparent); + mask-image: linear-gradient(transparent, black 0.5rem, black calc(100% - 0.5rem), transparent);*/ + transform: rotate(30deg) skew(-30deg) + translate( + calc(var(--cube) * (1.99 - var(--fill, 1))), + calc(var(--cube) * (0.599 - 0.864 * var(--fill, 1))) + ) + scaleY(0.864); } .right { - background-color: oklch(from var(--face-color) calc(l - 0.05) c h); + background: linear-gradient( + to top, + var(--face-right-color) calc(var(--fill, 1) * 100%), + oklch(from var(--face-right-color) l c h / var(--empty-alpha)) + calc(var(--fill, 1) * 100%) + ); transform: rotate(-30deg) skewX(-30deg) translate(calc(var(--cube) * 1.3), calc(var(--cube) * 1.725)) scaleY(0.864); } .top { - background-color: oklch(from var(--face-color) calc(l + 0.05) c h); + --is-full: round(down, calc(var(--fill, 1) + 0.0025), 1); + background-color: oklch( + from var(--face-top-color) l c h / + calc(var(--empty-alpha) + var(--is-full) * (1 - var(--empty-alpha))) + ); transform: rotate(30deg) skew(-30deg) translate(calc(var(--cube) * 0.99), calc(var(--cube) * -0.265)) scaleY(0.864); @@ -143,7 +194,12 @@ .left { font-size: var(--font-size-xs); line-height: var(--line-height-xs); - background-color: var(--face-color); + background: linear-gradient( + to top, + var(--face-left-color) calc(var(--fill, 1) * 100%), + oklch(from var(--face-left-color) l c h / var(--empty-alpha)) + calc(var(--fill, 1) * 100%) + ); transform: rotate(30deg) skewX(30deg) translate(calc(var(--cube) * 0.3), calc(var(--cube) * 0.6)) scaleY(0.864); @@ -151,7 +207,9 @@ &.skeleton { pointer-events: none; - .face { color: transparent; } + .face { + color: transparent; + } } .fees { @@ -161,6 +219,35 @@ justify-content: center; align-items: center; } + + & + & { + margin-bottom: var(--block-gap); + + &::before { + content: ""; + position: absolute; + top: calc(var(--cube) * 1.75); + left: calc(var(--cube) * 1.12); + width: 1px; + height: var(--block-gap); + background: var(--border-color); + z-index: -1; + } + + @media (max-width: 767px) { + margin-bottom: 0; + margin-right: var(--block-gap); + + &::before { + bottom: auto; + left: auto; + right: calc(-1 * var(--block-gap)); + top: 50%; + width: var(--block-gap); + height: 1px; + } + } + } } }