global: snap

This commit is contained in:
nym21
2026-04-17 21:23:11 +02:00
parent 008143ff00
commit 2a93f51e81
47 changed files with 2075 additions and 389 deletions

View File

@@ -67,14 +67,14 @@ pub(crate) fn process_funded_addrs(
// Pure pushes - no holes remain
addrs_data.funded.reserve_pushed(pushes_iter.len());
let mut next_index = addrs_data.funded.len();
for (addr_type, type_index, data) in pushes_iter {
for (next_index, (addr_type, type_index, data)) in
(addrs_data.funded.len()..).zip(pushes_iter)
{
addrs_data.funded.push(data);
result.get_mut(addr_type).unwrap().insert(
type_index,
AnyAddrIndex::from(FundedAddrIndex::from(next_index)),
);
next_index += 1;
}
Ok(result)
@@ -138,14 +138,14 @@ pub(crate) fn process_empty_addrs(
// Pure pushes - no holes remain
addrs_data.empty.reserve_pushed(pushes_iter.len());
let mut next_index = addrs_data.empty.len();
for (addr_type, type_index, data) in pushes_iter {
for (next_index, (addr_type, type_index, data)) in
(addrs_data.empty.len()..).zip(pushes_iter)
{
addrs_data.empty.push(data);
result.get_mut(addr_type).unwrap().insert(
type_index,
AnyAddrIndex::from(EmptyAddrIndex::from(next_index)),
);
next_index += 1;
}
Ok(result)

View File

@@ -542,17 +542,14 @@ impl UTXOCohorts<Rw> {
}
/// Second phase of post-processing: compute relative metrics.
pub(crate) fn compute_rest_part2<HM>(
pub(crate) fn compute_rest_part2(
&mut self,
blocks: &blocks::Vecs,
prices: &prices::Vecs,
starting_indexes: &Indexes,
height_to_market_cap: &HM,
height_to_market_cap: &impl ReadableVec<Height, Dollars>,
exit: &Exit,
) -> Result<()>
where
HM: ReadableVec<Height, Dollars> + Sync,
{
) -> Result<()> {
// Get under_1h value sources for adjusted computation (cloned to avoid borrow conflicts).
let under_1h_value_created = self
.age_range

View File

@@ -258,6 +258,7 @@ pub(crate) fn process_blocks(
.chain(vecs.addrs.activity.par_iter_height_mut())
.chain(vecs.addrs.reused.par_iter_height_mut())
.chain(vecs.addrs.exposed.par_iter_height_mut())
.chain(vecs.addrs.avg_amount.par_iter_height_mut())
.chain(rayon::iter::once(
&mut vecs.coinblocks_destroyed.block as &mut dyn AnyStoredVec,
))

View File

@@ -156,6 +156,7 @@ impl AllCohortMetrics {
starting_indexes.height,
&self.supply,
&self.unrealized,
&self.realized,
height_to_market_cap,
exit,
)?;

View File

@@ -132,6 +132,7 @@ impl ExtendedCohortMetrics {
starting_indexes.height,
&self.supply,
&self.unrealized,
&self.realized,
height_to_market_cap,
all_supply_sats,
&self.supply.total.usd.height,

View File

@@ -133,7 +133,7 @@ pub use realized::{
AdjustedSopr, RealizedCore, RealizedFull, RealizedFullAccum, RealizedLike, RealizedMinimal,
};
pub use relative::{RelativeForAll, RelativeToAll, RelativeWithExtended};
pub use supply::{SupplyBase, SupplyCore};
pub use supply::{AvgAmountMetrics, SupplyBase, SupplyCore};
pub use unrealized::{
UnrealizedBasic, UnrealizedCore, UnrealizedFull, UnrealizedLike, UnrealizedMinimal,
};

View File

@@ -118,11 +118,11 @@ impl RealizedMinimal {
|(i, cap_cents, supply, ..)| {
let cap = cap_cents.as_u128();
let supply_sats = Sats::from(supply).as_u128();
if supply_sats == 0 {
(i, Cents::ZERO)
} else {
(i, Cents::from(cap * Sats::ONE_BTC_U128 / supply_sats))
}
let cents = (cap * Sats::ONE_BTC_U128)
.checked_div(supply_sats)
.map(Cents::from)
.unwrap_or(Cents::ZERO);
(i, cents)
},
exit,
)?)

View File

@@ -4,9 +4,9 @@ use brk_types::{Dollars, Height};
use derive_more::{Deref, DerefMut};
use vecdb::{Exit, ReadableVec, Rw, StorageMode};
use crate::distribution::metrics::{ImportConfig, SupplyCore, UnrealizedFull};
use crate::distribution::metrics::{ImportConfig, RealizedFull, SupplyCore, UnrealizedFull};
use super::{RelativeExtendedOwnPnl, RelativeFull};
use super::{RelativeExtendedOwnPnl, RelativeFull, RelativeInvestedCapital};
/// Relative metrics for the "all" cohort (base + own_pnl, NO rel_to_all).
#[derive(Deref, DerefMut, Traversable)]
@@ -17,6 +17,8 @@ pub struct RelativeForAll<M: StorageMode = Rw> {
pub base: RelativeFull<M>,
#[traversable(flatten)]
pub extended_own_pnl: RelativeExtendedOwnPnl<M>,
#[traversable(flatten)]
pub invested_capital: RelativeInvestedCapital<M>,
}
impl RelativeForAll {
@@ -24,6 +26,7 @@ impl RelativeForAll {
Ok(Self {
base: RelativeFull::forced_import(cfg)?,
extended_own_pnl: RelativeExtendedOwnPnl::forced_import(cfg)?,
invested_capital: RelativeInvestedCapital::forced_import(cfg)?,
})
}
@@ -32,6 +35,7 @@ impl RelativeForAll {
max_from: Height,
supply: &SupplyCore,
unrealized: &UnrealizedFull,
realized: &RealizedFull,
market_cap: &impl ReadableVec<Height, Dollars>,
exit: &Exit,
) -> Result<()> {
@@ -43,6 +47,8 @@ impl RelativeForAll {
&unrealized.gross_pnl.usd.height,
exit,
)?;
self.invested_capital
.compute(max_from, unrealized, realized, exit)?;
Ok(())
}
}

View File

@@ -0,0 +1,53 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{BasisPoints16, Cents, Height, Version};
use vecdb::{Exit, Rw, StorageMode};
use crate::internal::{PercentPerBlock, RatioCentsBp16};
use crate::distribution::metrics::{ImportConfig, RealizedFull, UnrealizedFull};
/// Shares of invested capital in profit / in loss relative to own realized cap.
/// Present for cohorts with `UnrealizedFull` (all, sth, lth).
#[derive(Traversable)]
pub struct RelativeInvestedCapital<M: StorageMode = Rw> {
#[traversable(wrap = "invested_capital/in_profit", rename = "to_own")]
pub in_profit_to_own: PercentPerBlock<BasisPoints16, M>,
#[traversable(wrap = "invested_capital/in_loss", rename = "to_own")]
pub in_loss_to_own: PercentPerBlock<BasisPoints16, M>,
}
impl RelativeInvestedCapital {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
let v0 = Version::ZERO;
Ok(Self {
in_profit_to_own: cfg.import("invested_capital_in_profit_to_own", v0)?,
in_loss_to_own: cfg.import("invested_capital_in_loss_to_own", v0)?,
})
}
pub(crate) fn compute(
&mut self,
max_from: Height,
unrealized: &UnrealizedFull,
realized: &RealizedFull,
exit: &Exit,
) -> Result<()> {
let realized_cap = &realized.core.minimal.cap.cents.height;
self.in_profit_to_own
.compute_binary::<Cents, Cents, RatioCentsBp16>(
max_from,
&unrealized.invested_capital.in_profit.cents.height,
realized_cap,
exit,
)?;
self.in_loss_to_own
.compute_binary::<Cents, Cents, RatioCentsBp16>(
max_from,
&unrealized.invested_capital.in_loss.cents.height,
realized_cap,
exit,
)?;
Ok(())
}
}

View File

@@ -2,6 +2,7 @@ mod extended_own_market_cap;
mod extended_own_pnl;
mod for_all;
mod full;
mod invested_capital;
mod to_all;
mod with_extended;
@@ -9,5 +10,6 @@ pub use extended_own_market_cap::RelativeExtendedOwnMarketCap;
pub use extended_own_pnl::RelativeExtendedOwnPnl;
pub use for_all::RelativeForAll;
pub use full::RelativeFull;
pub use invested_capital::RelativeInvestedCapital;
pub use to_all::RelativeToAll;
pub use with_extended::RelativeWithExtended;

View File

@@ -4,9 +4,12 @@ use brk_types::{Dollars, Height, Sats};
use derive_more::{Deref, DerefMut};
use vecdb::{Exit, ReadableVec, Rw, StorageMode};
use crate::distribution::metrics::{ImportConfig, SupplyCore, UnrealizedFull};
use crate::distribution::metrics::{ImportConfig, RealizedFull, SupplyCore, UnrealizedFull};
use super::{RelativeExtendedOwnMarketCap, RelativeExtendedOwnPnl, RelativeFull, RelativeToAll};
use super::{
RelativeExtendedOwnMarketCap, RelativeExtendedOwnPnl, RelativeFull, RelativeInvestedCapital,
RelativeToAll,
};
/// Full extended relative metrics (base + rel_to_all + own_market_cap + own_pnl).
/// Used by: sth, lth cohorts.
@@ -22,6 +25,8 @@ pub struct RelativeWithExtended<M: StorageMode = Rw> {
pub extended_own_market_cap: RelativeExtendedOwnMarketCap<M>,
#[traversable(flatten)]
pub extended_own_pnl: RelativeExtendedOwnPnl<M>,
#[traversable(flatten)]
pub invested_capital: RelativeInvestedCapital<M>,
}
impl RelativeWithExtended {
@@ -31,6 +36,7 @@ impl RelativeWithExtended {
rel_to_all: RelativeToAll::forced_import(cfg)?,
extended_own_market_cap: RelativeExtendedOwnMarketCap::forced_import(cfg)?,
extended_own_pnl: RelativeExtendedOwnPnl::forced_import(cfg)?,
invested_capital: RelativeInvestedCapital::forced_import(cfg)?,
})
}
@@ -40,6 +46,7 @@ impl RelativeWithExtended {
max_from: Height,
supply: &SupplyCore,
unrealized: &UnrealizedFull,
realized: &RealizedFull,
market_cap: &impl ReadableVec<Height, Dollars>,
all_supply_sats: &impl ReadableVec<Height, Sats>,
own_market_cap: &impl ReadableVec<Height, Dollars>,
@@ -57,6 +64,8 @@ impl RelativeWithExtended {
&unrealized.gross_pnl.usd.height,
exit,
)?;
self.invested_capital
.compute(max_from, unrealized, realized, exit)?;
Ok(())
}
}

View File

@@ -0,0 +1,77 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Height, Sats, StoredU64, Version};
use vecdb::{AnyStoredVec, Database, Exit, ReadableVec, Rw, StorageMode, WritableVec};
use crate::{indexes, internal::AmountPerBlock, prices};
/// Average amount held per UTXO and per funded address.
///
/// `utxo = supply / utxo_count`, `addr = supply / funded_addr_count`.
#[derive(Traversable)]
pub struct AvgAmountMetrics<M: StorageMode = Rw> {
pub utxo: AmountPerBlock<M>,
pub addr: AmountPerBlock<M>,
}
impl AvgAmountMetrics {
pub(crate) fn forced_import(
db: &Database,
prefix: &str,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
let name = |suffix: &str| {
if prefix.is_empty() {
suffix.to_string()
} else {
format!("{prefix}_{suffix}")
}
};
Ok(Self {
utxo: AmountPerBlock::forced_import(db, &name("avg_utxo_amount"), version, indexes)?,
addr: AmountPerBlock::forced_import(db, &name("avg_addr_amount"), version, indexes)?,
})
}
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
vec![
&mut self.utxo.sats.height as &mut dyn AnyStoredVec,
&mut self.utxo.cents.height,
&mut self.addr.sats.height,
&mut self.addr.cents.height,
]
}
pub(crate) fn reset_height(&mut self) -> Result<()> {
self.utxo.sats.height.reset()?;
self.utxo.cents.height.reset()?;
self.addr.sats.height.reset()?;
self.addr.cents.height.reset()?;
Ok(())
}
pub(crate) fn compute(
&mut self,
prices: &prices::Vecs,
supply_sats: &impl ReadableVec<Height, Sats>,
utxo_count: &impl ReadableVec<Height, StoredU64>,
funded_addr_count: &impl ReadableVec<Height, StoredU64>,
max_from: Height,
exit: &Exit,
) -> Result<()> {
self.utxo
.sats
.height
.compute_divide(max_from, supply_sats, utxo_count, exit)?;
self.utxo.compute(prices, max_from, exit)?;
self.addr
.sats
.height
.compute_divide(max_from, supply_sats, funded_addr_count, exit)?;
self.addr.compute(prices, max_from, exit)?;
Ok(())
}
}

View File

@@ -49,7 +49,7 @@ impl SupplyBase {
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
vec![
&mut self.total.sats.height as &mut dyn AnyStoredVec,
&mut self.total.cents.height as &mut dyn AnyStoredVec,
&mut self.total.cents.height,
]
}

View File

@@ -1,5 +1,7 @@
mod avg_amount;
mod base;
mod core;
pub use avg_amount::AvgAmountMetrics;
pub use self::core::SupplyCore;
pub use base::SupplyBase;

View File

@@ -25,7 +25,7 @@ use crate::{
},
indexes, inputs,
internal::{
PerBlockCumulativeRolling, WindowStartVec, Windows,
PerBlockCumulativeRolling, WindowStartVec, Windows, WithAddrTypes,
db_utils::{finalize_db, open_db},
},
outputs, prices, transactions,
@@ -37,6 +37,7 @@ use super::{
AddrActivityVecs, AddrCountsVecs, DeltaVecs, ExposedAddrVecs, NewAddrCountVecs,
ReusedAddrVecs, TotalAddrCountVecs,
},
metrics::AvgAmountMetrics,
};
const VERSION: Version = Version::new(23);
@@ -51,6 +52,7 @@ pub struct AddrMetricsVecs<M: StorageMode = Rw> {
pub reused: ReusedAddrVecs<M>,
pub exposed: ExposedAddrVecs<M>,
pub delta: DeltaVecs,
pub avg_amount: WithAddrTypes<AvgAmountMetrics<M>>,
#[traversable(wrap = "indexes", rename = "funded")]
pub funded_index:
LazyVecFrom1<FundedAddrIndex, FundedAddrIndex, FundedAddrIndex, FundedAddrData>,
@@ -170,6 +172,9 @@ impl Vecs {
// Growth rate: delta change + rate (global + per-type)
let delta = DeltaVecs::new(version, &addr_count, cached_starts, indexes);
// Average amount (supply / utxo_count, supply / funded_addr_count) for `all` and per addr type.
let avg_amount = WithAddrTypes::<AvgAmountMetrics>::forced_import(&db, version, indexes)?;
let this = Self {
supply_state: BytesVec::forced_import_with(
vecdb::ImportOptions::new(&db, "supply_state", version)
@@ -185,6 +190,7 @@ impl Vecs {
reused: reused_addr_count,
exposed: exposed_addr_vecs,
delta,
avg_amount,
funded_index: funded_addr_index,
empty_index: empty_addr_index,
},
@@ -302,6 +308,7 @@ impl Vecs {
self.addrs.activity.reset_height()?;
self.addrs.reused.reset_height()?;
self.addrs.exposed.reset_height()?;
self.addrs.avg_amount.reset_height()?;
reset_state(
&mut self.any_addr_indexes,
&mut self.addrs_data,
@@ -490,6 +497,34 @@ impl Vecs {
exit,
)?;
// Average amount (supply / utxo_count, supply / funded_addr_count) for `all` and per addr type.
let all_m = &self.utxo_cohorts.all.metrics;
self.addrs.avg_amount.all.compute(
prices,
&all_m.supply.total.sats.height,
&all_m.outputs.unspent_count.height,
&self.addrs.funded.all.height,
starting_indexes.height,
exit,
)?;
for ((ot, avg), (_, funded)) in self
.addrs
.avg_amount
.by_addr_type
.iter_mut()
.zip(self.addrs.funded.by_addr_type.iter())
{
let type_m = &t.get(ot).metrics;
avg.compute(
prices,
&type_m.supply.total.sats.height,
&type_m.outputs.unspent_count.height,
&funded.height,
starting_indexes.height,
exit,
)?;
}
// 6c. Compute total_addr_count = addr_count + empty_addr_count
self.addrs.total.compute(
starting_indexes.height,

View File

@@ -267,7 +267,7 @@ impl<T: NumericValue + JsonSchema> PerBlockDistribution<T> {
vec.push(zero);
}
} else {
weighted.sort_unstable_by(|a, b| a.0.cmp(&b.0));
weighted.sort_unstable_by_key(|a| a.0);
max.push(weighted.last().unwrap().0);
pct90.push(get_weighted_percentile(&weighted, 0.90));

View File

@@ -24,9 +24,9 @@ pub use derived::{
RatioCents64, TimesSqrt,
};
pub use ratio::{
RatioCentsBp32, RatioCentsSignedCentsBps32, RatioCentsSignedDollarsBps32, RatioDiffCentsBps32,
RatioDiffDollarsBps32, RatioDiffF32Bps32, RatioDollarsBp16, RatioDollarsBp32,
RatioDollarsBps32, RatioSatsBp16, RatioU64Bp16, RatioU64F32,
RatioCentsBp16, RatioCentsBp32, RatioCentsSignedCentsBps32, RatioCentsSignedDollarsBps32,
RatioDiffCentsBps32, RatioDiffDollarsBps32, RatioDiffF32Bps32, RatioDollarsBp16,
RatioDollarsBp32, RatioDollarsBps32, RatioSatsBp16, RatioU64Bp16, RatioU64F32,
};
pub use specialized::{
BlockCountTarget1m, BlockCountTarget1w, BlockCountTarget1y, BlockCountTarget24h,

View File

@@ -43,6 +43,19 @@ impl BinaryTransform<Cents, Cents, BasisPoints32> for RatioCentsBp32 {
}
}
pub struct RatioCentsBp16;
impl BinaryTransform<Cents, Cents, BasisPoints16> for RatioCentsBp16 {
#[inline(always)]
fn apply(numerator: Cents, denominator: Cents) -> BasisPoints16 {
if denominator == Cents::ZERO {
BasisPoints16::ZERO
} else {
BasisPoints16::from(numerator.inner() as f64 / denominator.inner() as f64)
}
}
}
pub struct RatioDollarsBp16;
impl BinaryTransform<Dollars, Dollars, BasisPoints16> for RatioDollarsBp16 {

View File

@@ -17,6 +17,8 @@ use super::{
WindowStartVec, Windows,
};
use crate::distribution::metrics::AvgAmountMetrics;
/// `all` aggregate plus per-`AddrType` breakdown.
#[derive(Clone, Traversable)]
pub struct WithAddrTypes<T> {
@@ -246,6 +248,42 @@ impl WithAddrTypes<AmountPerBlock> {
}
}
impl WithAddrTypes<AvgAmountMetrics> {
pub(crate) fn forced_import(
db: &Database,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
let all = AvgAmountMetrics::forced_import(db, "", version, indexes)?;
let by_addr_type = ByAddrType::new_with_name(|type_name| {
AvgAmountMetrics::forced_import(db, type_name, version, indexes)
})?;
Ok(Self { all, by_addr_type })
}
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
let mut vecs = self.all.collect_vecs_mut();
for v in self.by_addr_type.values_mut() {
vecs.extend(v.collect_vecs_mut());
}
vecs
}
pub(crate) fn par_iter_height_mut(
&mut self,
) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
self.collect_vecs_mut().into_par_iter()
}
pub(crate) fn reset_height(&mut self) -> Result<()> {
self.all.reset_height()?;
for v in self.by_addr_type.values_mut() {
v.reset_height()?;
}
Ok(())
}
}
impl<B: BpsType> WithAddrTypes<PercentPerBlock<B>> {
pub(crate) fn forced_import(
db: &Database,