mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-24 06:39:58 -07:00
global: snapshot part 3
This commit is contained in:
@@ -6,7 +6,7 @@ use brk_cohort::{
|
||||
};
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Dollars, Height, Indexes, Sats, Version};
|
||||
use brk_types::{Cents, CentsSquaredSats, Dollars, Height, Indexes, Sats, Version};
|
||||
use rayon::prelude::*;
|
||||
use vecdb::{
|
||||
AnyStoredVec, Database, Exit, ReadOnlyClone, ReadableVec, Rw, StorageMode, WritableVec,
|
||||
@@ -738,15 +738,9 @@ impl UTXOCohorts<Rw> {
|
||||
.min()
|
||||
.unwrap_or_default()
|
||||
.min(Height::from(self.profitability.min_stateful_len()))
|
||||
.min(Height::from(
|
||||
self.all.metrics.realized.min_stateful_len(),
|
||||
))
|
||||
.min(Height::from(
|
||||
self.sth.metrics.realized.min_stateful_len(),
|
||||
))
|
||||
.min(Height::from(
|
||||
self.lth.metrics.realized.min_stateful_len(),
|
||||
))
|
||||
.min(Height::from(self.all.min_stateful_len()))
|
||||
.min(Height::from(self.sth.min_stateful_len()))
|
||||
.min(Height::from(self.lth.min_stateful_len()))
|
||||
}
|
||||
|
||||
/// Import state for all separate cohorts at or before given height.
|
||||
@@ -790,7 +784,7 @@ impl UTXOCohorts<Rw> {
|
||||
|
||||
/// Aggregate RealizedFull fields from age_range states and push to all/sth/lth.
|
||||
/// Called during the block loop after separate cohorts' push_state but before reset.
|
||||
pub(crate) fn push_overlapping_realized_full(&mut self) {
|
||||
pub(crate) fn push_overlapping(&mut self, height_price: Cents) {
|
||||
let Self {
|
||||
all,
|
||||
sth,
|
||||
@@ -805,14 +799,26 @@ impl UTXOCohorts<Rw> {
|
||||
let mut sth_acc = RealizedFullAccum::default();
|
||||
let mut lth_acc = RealizedFullAccum::default();
|
||||
|
||||
for ar in age_range.iter() {
|
||||
if let Some(state) = ar.state.as_ref() {
|
||||
let r = &state.realized;
|
||||
all_acc.add(r);
|
||||
let mut all_icap = (0u128, 0u128);
|
||||
let mut sth_icap = (0u128, 0u128);
|
||||
let mut lth_icap = (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;
|
||||
|
||||
if sth_filter.includes(&ar.metrics.filter) {
|
||||
sth_acc.add(r);
|
||||
sth_acc.add(&state.realized);
|
||||
sth_icap.0 += u.investor_cap_in_profit_raw;
|
||||
sth_icap.1 += u.investor_cap_in_loss_raw;
|
||||
} else {
|
||||
lth_acc.add(r);
|
||||
lth_acc.add(&state.realized);
|
||||
lth_icap.0 += u.investor_cap_in_profit_raw;
|
||||
lth_icap.1 += u.investor_cap_in_loss_raw;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -820,6 +826,13 @@ impl UTXOCohorts<Rw> {
|
||||
all.metrics.realized.push_accum(&all_acc);
|
||||
sth.metrics.realized.push_accum(&sth_acc);
|
||||
lth.metrics.realized.push_accum(<h_acc);
|
||||
|
||||
all.metrics.unrealized.investor_cap_in_profit_raw.push(CentsSquaredSats::new(all_icap.0));
|
||||
all.metrics.unrealized.investor_cap_in_loss_raw.push(CentsSquaredSats::new(all_icap.1));
|
||||
sth.metrics.unrealized.investor_cap_in_profit_raw.push(CentsSquaredSats::new(sth_icap.0));
|
||||
sth.metrics.unrealized.investor_cap_in_loss_raw.push(CentsSquaredSats::new(sth_icap.1));
|
||||
lth.metrics.unrealized.investor_cap_in_profit_raw.push(CentsSquaredSats::new(lth_icap.0));
|
||||
lth.metrics.unrealized.investor_cap_in_loss_raw.push(CentsSquaredSats::new(lth_icap.1));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -551,8 +551,8 @@ fn push_cohort_states(
|
||||
},
|
||||
);
|
||||
|
||||
// Phase 2: aggregate age_range realized states → push to overlapping cohorts' RealizedFull
|
||||
utxo_cohorts.push_overlapping_realized_full();
|
||||
// Phase 2: aggregate age_range states → push to overlapping cohorts
|
||||
utxo_cohorts.push_overlapping(height_price);
|
||||
|
||||
// Phase 3: reset per-block values
|
||||
utxo_cohorts
|
||||
|
||||
@@ -50,11 +50,10 @@ impl CohortMetricsBase for AllCohortMetrics {
|
||||
}
|
||||
|
||||
fn min_stateful_len(&self) -> usize {
|
||||
self.supply
|
||||
.min_len()
|
||||
.min(self.outputs.min_len())
|
||||
.min(self.activity.min_len())
|
||||
.min(self.realized.min_stateful_len())
|
||||
// Only check per-block pushed vecs, not aggregated ones (supply, outputs,
|
||||
// activity, realized core, unrealized core are summed from age_range).
|
||||
self.realized
|
||||
.min_stateful_len()
|
||||
.min(self.unrealized.min_stateful_len())
|
||||
.min(self.cost_basis.min_stateful_len())
|
||||
}
|
||||
@@ -138,6 +137,7 @@ impl AllCohortMetrics {
|
||||
|
||||
self.cost_basis.compute_prices(
|
||||
starting_indexes,
|
||||
&prices.spot.cents.height,
|
||||
&self.unrealized.invested_capital.in_profit.cents.height,
|
||||
&self.unrealized.invested_capital.in_loss.cents.height,
|
||||
&self.supply.in_profit.sats.height,
|
||||
|
||||
@@ -47,11 +47,10 @@ impl CohortMetricsBase for ExtendedCohortMetrics {
|
||||
}
|
||||
|
||||
fn min_stateful_len(&self) -> usize {
|
||||
self.supply
|
||||
.min_len()
|
||||
.min(self.outputs.min_len())
|
||||
.min(self.activity.min_len())
|
||||
.min(self.realized.min_stateful_len())
|
||||
// Only check per-block pushed vecs, not aggregated ones (supply, outputs,
|
||||
// activity, realized core, unrealized core are summed from age_range).
|
||||
self.realized
|
||||
.min_stateful_len()
|
||||
.min(self.unrealized.min_stateful_len())
|
||||
.min(self.cost_basis.min_stateful_len())
|
||||
}
|
||||
@@ -116,6 +115,7 @@ impl ExtendedCohortMetrics {
|
||||
|
||||
self.cost_basis.compute_prices(
|
||||
starting_indexes,
|
||||
&prices.spot.cents.height,
|
||||
&self.unrealized.invested_capital.in_profit.cents.height,
|
||||
&self.unrealized.invested_capital.in_loss.cents.height,
|
||||
&self.supply.in_profit.sats.height,
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{BasisPoints16, Cents, Height, Indexes, Sats, Version};
|
||||
use brk_types::CentsSquaredSats;
|
||||
use brk_types::{BasisPoints16, Cents, Height, Indexes, Sats, Version};
|
||||
use vecdb::{AnyStoredVec, AnyVec, Exit, ReadableVec, Rw, StorageMode, WritableVec};
|
||||
|
||||
use crate::internal::{FiatPerBlock, PerBlock, PercentPerBlock, PercentilesVecs, Price, PERCENTILES_LEN};
|
||||
use crate::internal::{
|
||||
PERCENTILES_LEN, PerBlock, PercentPerBlock, PercentilesVecs, Price,
|
||||
};
|
||||
|
||||
use super::ImportConfig;
|
||||
|
||||
#[derive(Traversable)]
|
||||
pub struct CostBasisSide<M: StorageMode = Rw> {
|
||||
pub per_coin: FiatPerBlock<Cents, M>,
|
||||
pub per_dollar: FiatPerBlock<Cents, M>,
|
||||
pub per_coin: Price<PerBlock<Cents, M>>,
|
||||
pub per_dollar: Price<PerBlock<Cents, M>>,
|
||||
}
|
||||
|
||||
/// Cost basis metrics: min/max + profit/loss splits + percentiles + supply density.
|
||||
@@ -31,12 +33,12 @@ impl CostBasis {
|
||||
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
|
||||
Ok(Self {
|
||||
in_profit: CostBasisSide {
|
||||
per_coin: cfg.import("cost_basis_in_profit_per_coin", Version::ZERO)?,
|
||||
per_dollar: cfg.import("cost_basis_in_profit_per_dollar", Version::ZERO)?,
|
||||
per_coin: Price::forced_import(cfg.db, &cfg.name("cost_basis_in_profit_per_coin"), cfg.version + Version::ONE, cfg.indexes)?,
|
||||
per_dollar: Price::forced_import(cfg.db, &cfg.name("cost_basis_in_profit_per_dollar"), cfg.version + Version::ONE, cfg.indexes)?,
|
||||
},
|
||||
in_loss: CostBasisSide {
|
||||
per_coin: cfg.import("cost_basis_in_loss_per_coin", Version::ZERO)?,
|
||||
per_dollar: cfg.import("cost_basis_in_loss_per_dollar", Version::ZERO)?,
|
||||
per_coin: Price::forced_import(cfg.db, &cfg.name("cost_basis_in_loss_per_coin"), cfg.version + Version::ONE, cfg.indexes)?,
|
||||
per_dollar: Price::forced_import(cfg.db, &cfg.name("cost_basis_in_loss_per_dollar"), cfg.version + Version::ONE, cfg.indexes)?,
|
||||
},
|
||||
min: cfg.import("cost_basis_min", Version::ZERO)?,
|
||||
max: cfg.import("cost_basis_max", Version::ZERO)?,
|
||||
@@ -124,9 +126,11 @@ impl CostBasis {
|
||||
vecs
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn compute_prices(
|
||||
&mut self,
|
||||
starting_indexes: &Indexes,
|
||||
spot: &impl ReadableVec<Height, Cents>,
|
||||
invested_cap_in_profit: &impl ReadableVec<Height, Cents>,
|
||||
invested_cap_in_loss: &impl ReadableVec<Height, Cents>,
|
||||
supply_in_profit_sats: &impl ReadableVec<Height, Sats>,
|
||||
@@ -135,46 +139,64 @@ impl CostBasis {
|
||||
investor_cap_in_loss_raw: &impl ReadableVec<Height, CentsSquaredSats>,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.in_profit.per_coin.cents.height.compute_transform2(
|
||||
self.in_profit.per_coin.cents.height.compute_transform3(
|
||||
starting_indexes.height,
|
||||
invested_cap_in_profit,
|
||||
supply_in_profit_sats,
|
||||
|(h, invested_cents, supply_sats, ..)| {
|
||||
spot,
|
||||
|(h, invested_cents, supply_sats, spot, ..)| {
|
||||
let supply = supply_sats.as_u128();
|
||||
if supply == 0 { return (h, Cents::ZERO); }
|
||||
(h, Cents::new((invested_cents.as_u128() * Sats::ONE_BTC_U128 / supply) as u64))
|
||||
if supply == 0 {
|
||||
return (h, spot);
|
||||
}
|
||||
(
|
||||
h,
|
||||
Cents::new((invested_cents.as_u128() * Sats::ONE_BTC_U128 / supply) as u64),
|
||||
)
|
||||
},
|
||||
exit,
|
||||
)?;
|
||||
self.in_loss.per_coin.cents.height.compute_transform2(
|
||||
self.in_loss.per_coin.cents.height.compute_transform3(
|
||||
starting_indexes.height,
|
||||
invested_cap_in_loss,
|
||||
supply_in_loss_sats,
|
||||
|(h, invested_cents, supply_sats, ..)| {
|
||||
spot,
|
||||
|(h, invested_cents, supply_sats, spot, ..)| {
|
||||
let supply = supply_sats.as_u128();
|
||||
if supply == 0 { return (h, Cents::ZERO); }
|
||||
(h, Cents::new((invested_cents.as_u128() * Sats::ONE_BTC_U128 / supply) as u64))
|
||||
if supply == 0 {
|
||||
return (h, spot);
|
||||
}
|
||||
(
|
||||
h,
|
||||
Cents::new((invested_cents.as_u128() * Sats::ONE_BTC_U128 / supply) as u64),
|
||||
)
|
||||
},
|
||||
exit,
|
||||
)?;
|
||||
self.in_profit.per_dollar.cents.height.compute_transform2(
|
||||
self.in_profit.per_dollar.cents.height.compute_transform3(
|
||||
starting_indexes.height,
|
||||
investor_cap_in_profit_raw,
|
||||
invested_cap_in_profit,
|
||||
|(h, investor_cap, invested_cents, ..)| {
|
||||
spot,
|
||||
|(h, investor_cap, invested_cents, spot, ..)| {
|
||||
let invested_raw = invested_cents.as_u128() * Sats::ONE_BTC_U128;
|
||||
if invested_raw == 0 { return (h, Cents::ZERO); }
|
||||
if invested_raw == 0 {
|
||||
return (h, spot);
|
||||
}
|
||||
(h, Cents::new((investor_cap.inner() / invested_raw) as u64))
|
||||
},
|
||||
exit,
|
||||
)?;
|
||||
self.in_loss.per_dollar.cents.height.compute_transform2(
|
||||
self.in_loss.per_dollar.cents.height.compute_transform3(
|
||||
starting_indexes.height,
|
||||
investor_cap_in_loss_raw,
|
||||
invested_cap_in_loss,
|
||||
|(h, investor_cap, invested_cents, ..)| {
|
||||
spot,
|
||||
|(h, investor_cap, invested_cents, spot, ..)| {
|
||||
let invested_raw = invested_cents.as_u128() * Sats::ONE_BTC_U128;
|
||||
if invested_raw == 0 { return (h, Cents::ZERO); }
|
||||
if invested_raw == 0 {
|
||||
return (h, spot);
|
||||
}
|
||||
(h, Cents::new((investor_cap.inner() / invested_raw) as u64))
|
||||
},
|
||||
exit,
|
||||
|
||||
@@ -127,7 +127,7 @@ impl RealizedFull {
|
||||
|
||||
// Peak regret
|
||||
let peak_regret = RealizedPeakRegret {
|
||||
value: cfg.import("realized_peak_regret", Version::new(2))?,
|
||||
value: cfg.import("realized_peak_regret", Version::new(3))?,
|
||||
to_rcap: cfg.import("realized_peak_regret_to_rcap", Version::new(2))?,
|
||||
};
|
||||
|
||||
|
||||
@@ -33,9 +33,7 @@ pub struct UnrealizedFull<M: StorageMode = Rw> {
|
||||
pub gross_pnl: FiatPerBlock<Cents, M>,
|
||||
pub invested_capital: UnrealizedInvestedCapital<M>,
|
||||
|
||||
#[traversable(hidden)]
|
||||
pub investor_cap_in_profit_raw: M::Stored<BytesVec<Height, CentsSquaredSats>>,
|
||||
#[traversable(hidden)]
|
||||
pub investor_cap_in_loss_raw: M::Stored<BytesVec<Height, CentsSquaredSats>>,
|
||||
|
||||
pub sentiment: UnrealizedSentiment<M>,
|
||||
@@ -48,18 +46,20 @@ impl UnrealizedFull {
|
||||
|
||||
let gross_pnl = cfg.import("unrealized_gross_pnl", v0)?;
|
||||
|
||||
let v1 = Version::ONE;
|
||||
|
||||
let invested_capital = UnrealizedInvestedCapital {
|
||||
in_profit: cfg.import("invested_capital_in_profit", v0)?,
|
||||
in_loss: cfg.import("invested_capital_in_loss", v0)?,
|
||||
in_profit: cfg.import("invested_capital_in_profit", v1)?,
|
||||
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 sentiment = UnrealizedSentiment {
|
||||
pain_index: cfg.import("pain_index", v0)?,
|
||||
greed_index: cfg.import("greed_index", v0)?,
|
||||
net: cfg.import("net_sentiment", Version::ONE)?,
|
||||
pain_index: cfg.import("pain_index", v1)?,
|
||||
greed_index: cfg.import("greed_index", v1)?,
|
||||
net: cfg.import("net_sentiment", Version::new(2))?,
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
@@ -73,9 +73,10 @@ impl UnrealizedFull {
|
||||
}
|
||||
|
||||
pub(crate) fn min_stateful_len(&self) -> usize {
|
||||
self.inner
|
||||
.min_stateful_len()
|
||||
.min(self.investor_cap_in_profit_raw.len())
|
||||
// Only check per-block pushed vecs (investor_cap_raw).
|
||||
// Core-level vecs (profit/loss) are aggregated from age_range, not stateful.
|
||||
self.investor_cap_in_profit_raw
|
||||
.len()
|
||||
.min(self.investor_cap_in_loss_raw.len())
|
||||
}
|
||||
|
||||
@@ -90,14 +91,8 @@ impl UnrealizedFull {
|
||||
|
||||
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
|
||||
let mut vecs = self.inner.collect_vecs_mut();
|
||||
vecs.push(&mut self.gross_pnl.cents.height as &mut dyn AnyStoredVec);
|
||||
vecs.push(&mut self.invested_capital.in_profit.cents.height as &mut dyn AnyStoredVec);
|
||||
vecs.push(&mut self.invested_capital.in_loss.cents.height as &mut dyn AnyStoredVec);
|
||||
vecs.push(&mut self.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.sentiment.pain_index.cents.height as &mut dyn AnyStoredVec);
|
||||
vecs.push(&mut self.sentiment.greed_index.cents.height as &mut dyn AnyStoredVec);
|
||||
vecs.push(&mut self.sentiment.net.cents.height as &mut dyn AnyStoredVec);
|
||||
vecs
|
||||
}
|
||||
|
||||
@@ -120,30 +115,41 @@ impl UnrealizedFull {
|
||||
)?;
|
||||
|
||||
// invested_capital_in_profit = supply_profit_sats × spot / ONE_BTC - unrealized_profit
|
||||
self.invested_capital.in_profit.cents.height.compute_transform3(
|
||||
starting_indexes.height,
|
||||
supply_in_profit_sats,
|
||||
&prices.spot.cents.height,
|
||||
&self.inner.basic.profit.cents.height,
|
||||
|(h, supply_sats, spot, profit, ..): (_, Sats, Cents, Cents, _)| {
|
||||
let market_value = supply_sats.as_u128() * spot.as_u128() / Sats::ONE_BTC_U128;
|
||||
(h, Cents::new(market_value.saturating_sub(profit.as_u128()) as u64))
|
||||
},
|
||||
exit,
|
||||
)?;
|
||||
self.invested_capital
|
||||
.in_profit
|
||||
.cents
|
||||
.height
|
||||
.compute_transform3(
|
||||
starting_indexes.height,
|
||||
supply_in_profit_sats,
|
||||
&prices.spot.cents.height,
|
||||
&self.inner.basic.profit.cents.height,
|
||||
|(h, supply_sats, spot, profit, ..): (_, Sats, Cents, Cents, _)| {
|
||||
let market_value = supply_sats.as_u128() * spot.as_u128() / Sats::ONE_BTC_U128;
|
||||
(
|
||||
h,
|
||||
Cents::new(market_value.saturating_sub(profit.as_u128()) as u64),
|
||||
)
|
||||
},
|
||||
exit,
|
||||
)?;
|
||||
|
||||
// invested_capital_in_loss = supply_loss_sats × spot / ONE_BTC + unrealized_loss
|
||||
self.invested_capital.in_loss.cents.height.compute_transform3(
|
||||
starting_indexes.height,
|
||||
supply_in_loss_sats,
|
||||
&prices.spot.cents.height,
|
||||
&self.inner.basic.loss.cents.height,
|
||||
|(h, supply_sats, spot, loss, ..): (_, Sats, Cents, Cents, _)| {
|
||||
let market_value = supply_sats.as_u128() * spot.as_u128() / Sats::ONE_BTC_U128;
|
||||
(h, Cents::new((market_value + loss.as_u128()) as u64))
|
||||
},
|
||||
exit,
|
||||
)?;
|
||||
self.invested_capital
|
||||
.in_loss
|
||||
.cents
|
||||
.height
|
||||
.compute_transform3(
|
||||
starting_indexes.height,
|
||||
supply_in_loss_sats,
|
||||
&prices.spot.cents.height,
|
||||
&self.inner.basic.loss.cents.height,
|
||||
|(h, supply_sats, spot, loss, ..): (_, Sats, Cents, Cents, _)| {
|
||||
let market_value = supply_sats.as_u128() * spot.as_u128() / Sats::ONE_BTC_U128;
|
||||
(h, Cents::new((market_value + loss.as_u128()) as u64))
|
||||
},
|
||||
exit,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -171,7 +177,10 @@ impl UnrealizedFull {
|
||||
}
|
||||
let investor_price = investor_cap.inner() / invested_cap_raw;
|
||||
let spot_u128 = spot.as_u128();
|
||||
(h, Cents::new(spot_u128.saturating_sub(investor_price) as u64))
|
||||
(
|
||||
h,
|
||||
Cents::new(spot_u128.saturating_sub(investor_price) as u64),
|
||||
)
|
||||
},
|
||||
exit,
|
||||
)?;
|
||||
@@ -189,7 +198,10 @@ impl UnrealizedFull {
|
||||
}
|
||||
let investor_price = investor_cap.inner() / invested_cap_raw;
|
||||
let spot_u128 = spot.as_u128();
|
||||
(h, Cents::new(investor_price.saturating_sub(spot_u128) as u64))
|
||||
(
|
||||
h,
|
||||
Cents::new(investor_price.saturating_sub(spot_u128) as u64),
|
||||
)
|
||||
},
|
||||
exit,
|
||||
)?;
|
||||
|
||||
@@ -63,7 +63,7 @@ impl UnrealizedLike for UnrealizedFull {
|
||||
&mut self.inner
|
||||
}
|
||||
fn min_stateful_len(&self) -> usize {
|
||||
self.inner.min_stateful_len()
|
||||
UnrealizedFull::min_stateful_len(self)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn push_state(&mut self, state: &UnrealizedState) {
|
||||
|
||||
@@ -5,7 +5,10 @@ use vecdb::{Database, EagerVec, Exit, PcoVec, Rw, StorageMode};
|
||||
|
||||
use crate::{
|
||||
indexes,
|
||||
internal::{AmountPerBlock, CachedWindowStarts, LazyRollingSumsAmountFromHeight, SatsToCents},
|
||||
internal::{
|
||||
AmountPerBlock, CachedWindowStarts, LazyRollingAvgsAmountFromHeight,
|
||||
LazyRollingSumsAmountFromHeight, SatsToCents,
|
||||
},
|
||||
prices,
|
||||
};
|
||||
|
||||
@@ -14,6 +17,7 @@ pub struct AmountPerBlockCumulativeWithSums<M: StorageMode = Rw> {
|
||||
pub base: AmountPerBlock<M>,
|
||||
pub cumulative: AmountPerBlock<M>,
|
||||
pub sum: LazyRollingSumsAmountFromHeight,
|
||||
pub average: LazyRollingAvgsAmountFromHeight,
|
||||
}
|
||||
|
||||
const VERSION: Version = Version::TWO;
|
||||
@@ -39,11 +43,20 @@ impl AmountPerBlockCumulativeWithSums {
|
||||
cached_starts,
|
||||
indexes,
|
||||
);
|
||||
let average = LazyRollingAvgsAmountFromHeight::new(
|
||||
&format!("{name}_average"),
|
||||
v,
|
||||
&cumulative.sats.height,
|
||||
&cumulative.cents.height,
|
||||
cached_starts,
|
||||
indexes,
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
base,
|
||||
cumulative,
|
||||
sum,
|
||||
average,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,9 @@ use vecdb::{Database, EagerVec, Exit, PcoVec, Rw, StorageMode};
|
||||
use crate::{
|
||||
indexes,
|
||||
internal::{
|
||||
AmountPerBlock, CachedWindowStarts, LazyRollingSumsAmountFromHeight,
|
||||
RollingDistributionAmountPerBlock, SatsToCents, WindowStarts,
|
||||
AmountPerBlock, CachedWindowStarts, LazyRollingAvgsAmountFromHeight,
|
||||
LazyRollingSumsAmountFromHeight, RollingDistributionAmountPerBlock, SatsToCents,
|
||||
WindowStarts,
|
||||
},
|
||||
prices,
|
||||
};
|
||||
@@ -17,8 +18,9 @@ pub struct AmountPerBlockFull<M: StorageMode = Rw> {
|
||||
pub base: AmountPerBlock<M>,
|
||||
pub cumulative: AmountPerBlock<M>,
|
||||
pub sum: LazyRollingSumsAmountFromHeight,
|
||||
pub average: LazyRollingAvgsAmountFromHeight,
|
||||
#[traversable(flatten)]
|
||||
pub rolling: RollingDistributionAmountPerBlock<M>,
|
||||
pub distribution: RollingDistributionAmountPerBlock<M>,
|
||||
}
|
||||
|
||||
const VERSION: Version = Version::TWO;
|
||||
@@ -34,12 +36,8 @@ impl AmountPerBlockFull {
|
||||
let v = version + VERSION;
|
||||
|
||||
let base = AmountPerBlock::forced_import(db, name, v, indexes)?;
|
||||
let cumulative = AmountPerBlock::forced_import(
|
||||
db,
|
||||
&format!("{name}_cumulative"),
|
||||
v,
|
||||
indexes,
|
||||
)?;
|
||||
let cumulative =
|
||||
AmountPerBlock::forced_import(db, &format!("{name}_cumulative"), v, indexes)?;
|
||||
let sum = LazyRollingSumsAmountFromHeight::new(
|
||||
&format!("{name}_sum"),
|
||||
v,
|
||||
@@ -48,14 +46,22 @@ impl AmountPerBlockFull {
|
||||
cached_starts,
|
||||
indexes,
|
||||
);
|
||||
let rolling =
|
||||
RollingDistributionAmountPerBlock::forced_import(db, name, v, indexes)?;
|
||||
let average = LazyRollingAvgsAmountFromHeight::new(
|
||||
&format!("{name}_average"),
|
||||
v,
|
||||
&cumulative.sats.height,
|
||||
&cumulative.cents.height,
|
||||
cached_starts,
|
||||
indexes,
|
||||
);
|
||||
let rolling = RollingDistributionAmountPerBlock::forced_import(db, name, v, indexes)?;
|
||||
|
||||
Ok(Self {
|
||||
base,
|
||||
cumulative,
|
||||
sum,
|
||||
rolling,
|
||||
average,
|
||||
distribution: rolling,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -89,7 +95,7 @@ impl AmountPerBlockFull {
|
||||
.height
|
||||
.compute_cumulative(max_from, &self.base.cents.height, exit)?;
|
||||
|
||||
self.rolling.compute(
|
||||
self.distribution.compute(
|
||||
max_from,
|
||||
windows,
|
||||
&self.base.sats.height,
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Bitcoin, Cents, Dollars, Height, Sats, Version};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use vecdb::{DeltaAvg, LazyDeltaVec, LazyVecFrom1, ReadableCloneableVec};
|
||||
|
||||
use crate::{
|
||||
indexes,
|
||||
internal::{
|
||||
CachedWindowStarts, CentsUnsignedToDollars, DerivedResolutions, LazyPerBlock,
|
||||
LazyRollingAvgFromHeight, Resolutions, SatsToBitcoin, Windows,
|
||||
},
|
||||
};
|
||||
|
||||
/// Single window slot: lazy rolling average for Amount (sats + btc + cents + usd).
|
||||
#[derive(Clone, Traversable)]
|
||||
pub struct LazyRollingAvgAmountFromHeight {
|
||||
pub btc: LazyPerBlock<Bitcoin, Sats>,
|
||||
pub sats: LazyRollingAvgFromHeight<Sats>,
|
||||
pub usd: LazyPerBlock<Dollars, Cents>,
|
||||
pub cents: LazyRollingAvgFromHeight<Cents>,
|
||||
}
|
||||
|
||||
/// Lazy rolling averages for all 4 windows, for Amount (sats + btc + cents + usd).
|
||||
#[derive(Clone, Deref, DerefMut, Traversable)]
|
||||
#[traversable(transparent)]
|
||||
pub struct LazyRollingAvgsAmountFromHeight(pub Windows<LazyRollingAvgAmountFromHeight>);
|
||||
|
||||
impl LazyRollingAvgsAmountFromHeight {
|
||||
pub fn new(
|
||||
name: &str,
|
||||
version: Version,
|
||||
cumulative_sats: &(impl ReadableCloneableVec<Height, Sats> + 'static),
|
||||
cumulative_cents: &(impl ReadableCloneableVec<Height, Cents> + 'static),
|
||||
cached_starts: &CachedWindowStarts,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Self {
|
||||
let cum_sats = cumulative_sats.read_only_boxed_clone();
|
||||
let cum_cents = cumulative_cents.read_only_boxed_clone();
|
||||
|
||||
let make_slot = |suffix: &str, cached_start: &vecdb::CachedVec<Height, Height>| {
|
||||
let full_name = format!("{name}_{suffix}");
|
||||
let cached = cached_start.clone();
|
||||
let starts_version = cached.version();
|
||||
|
||||
// Sats lazy rolling avg
|
||||
let sats_avg = LazyDeltaVec::<Height, Sats, Sats, DeltaAvg>::new(
|
||||
&format!("{full_name}_sats"),
|
||||
version,
|
||||
cum_sats.clone(),
|
||||
starts_version,
|
||||
{
|
||||
let cached = cached.clone();
|
||||
move || cached.get()
|
||||
},
|
||||
);
|
||||
let sats_resolutions = Resolutions::forced_import(
|
||||
&format!("{full_name}_sats"),
|
||||
sats_avg.read_only_boxed_clone(),
|
||||
version,
|
||||
indexes,
|
||||
);
|
||||
let sats = LazyRollingAvgFromHeight {
|
||||
height: sats_avg,
|
||||
resolutions: Box::new(sats_resolutions),
|
||||
};
|
||||
|
||||
// Btc lazy from sats
|
||||
let btc = LazyPerBlock {
|
||||
height: LazyVecFrom1::transformed::<SatsToBitcoin>(
|
||||
&full_name,
|
||||
version,
|
||||
sats.height.read_only_boxed_clone(),
|
||||
),
|
||||
resolutions: Box::new(DerivedResolutions::from_derived_computed::<SatsToBitcoin>(
|
||||
&full_name,
|
||||
version,
|
||||
&sats.resolutions,
|
||||
)),
|
||||
};
|
||||
|
||||
// Cents rolling avg
|
||||
let cents_avg = LazyDeltaVec::<Height, Cents, Cents, DeltaAvg>::new(
|
||||
&format!("{full_name}_cents"),
|
||||
version,
|
||||
cum_cents.clone(),
|
||||
starts_version,
|
||||
move || cached.get(),
|
||||
);
|
||||
let cents_resolutions = Resolutions::forced_import(
|
||||
&format!("{full_name}_cents"),
|
||||
cents_avg.read_only_boxed_clone(),
|
||||
version,
|
||||
indexes,
|
||||
);
|
||||
let cents = LazyRollingAvgFromHeight {
|
||||
height: cents_avg,
|
||||
resolutions: Box::new(cents_resolutions),
|
||||
};
|
||||
|
||||
// Usd lazy from cents
|
||||
let usd = LazyPerBlock {
|
||||
height: LazyVecFrom1::transformed::<CentsUnsignedToDollars>(
|
||||
&format!("{full_name}_usd"),
|
||||
version,
|
||||
cents.height.read_only_boxed_clone(),
|
||||
),
|
||||
resolutions: Box::new(DerivedResolutions::from_derived_computed::<
|
||||
CentsUnsignedToDollars,
|
||||
>(
|
||||
&format!("{full_name}_usd"), version, ¢s.resolutions
|
||||
)),
|
||||
};
|
||||
|
||||
LazyRollingAvgAmountFromHeight {
|
||||
btc,
|
||||
sats,
|
||||
usd,
|
||||
cents,
|
||||
}
|
||||
};
|
||||
|
||||
Self(cached_starts.0.map_with_suffix(make_slot))
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ mod cumulative_sum;
|
||||
mod full;
|
||||
mod lazy;
|
||||
mod lazy_derived_resolutions;
|
||||
mod lazy_rolling_avg;
|
||||
mod lazy_rolling_sum;
|
||||
mod rolling_distribution;
|
||||
mod with_deltas;
|
||||
@@ -14,6 +15,7 @@ pub use cumulative_sum::*;
|
||||
pub use full::*;
|
||||
pub use lazy::*;
|
||||
pub use lazy_derived_resolutions::*;
|
||||
pub use lazy_rolling_avg::*;
|
||||
pub use lazy_rolling_sum::*;
|
||||
pub use rolling_distribution::*;
|
||||
pub use with_deltas::*;
|
||||
|
||||
@@ -9,9 +9,7 @@ use vecdb::{
|
||||
|
||||
use crate::{
|
||||
indexes,
|
||||
internal::{
|
||||
CachedWindowStarts, NumericValue, PerBlock, RollingComplete, WindowStarts,
|
||||
},
|
||||
internal::{CachedWindowStarts, NumericValue, PerBlock, RollingComplete, WindowStarts},
|
||||
};
|
||||
|
||||
#[derive(Traversable)]
|
||||
@@ -70,8 +68,7 @@ where
|
||||
f64: From<T>,
|
||||
A: VecIndex + VecValue + brk_types::CheckedSub<A>,
|
||||
{
|
||||
let combined_version =
|
||||
source.version() + first_indexes.version() + count_indexes.version();
|
||||
let combined_version = source.version() + first_indexes.version() + count_indexes.version();
|
||||
|
||||
let mut index = max_from;
|
||||
index = {
|
||||
@@ -121,7 +118,7 @@ where
|
||||
);
|
||||
|
||||
self.sum.height.push(sum_val);
|
||||
cumulative_val = cumulative_val + sum_val;
|
||||
cumulative_val += sum_val;
|
||||
self.cumulative.height.push(cumulative_val);
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -17,7 +17,10 @@ use vecdb::{Database, EagerVec, Exit, PcoVec, Rw, StorageMode};
|
||||
|
||||
use crate::{
|
||||
indexes,
|
||||
internal::{CachedWindowStarts, PerBlock, LazyRollingSumsFromHeight, NumericValue},
|
||||
internal::{
|
||||
CachedWindowStarts, LazyRollingAvgsFromHeight, LazyRollingSumsFromHeight, NumericValue,
|
||||
PerBlock,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Traversable)]
|
||||
@@ -29,6 +32,7 @@ where
|
||||
pub base: PerBlock<T, M>,
|
||||
pub cumulative: PerBlock<C, M>,
|
||||
pub sum: LazyRollingSumsFromHeight<C>,
|
||||
pub average: LazyRollingAvgsFromHeight<C>,
|
||||
}
|
||||
|
||||
impl<T, C> PerBlockCumulativeWithSums<T, C>
|
||||
@@ -53,11 +57,19 @@ where
|
||||
cached_starts,
|
||||
indexes,
|
||||
);
|
||||
let average = LazyRollingAvgsFromHeight::new(
|
||||
&format!("{name}_average"),
|
||||
version,
|
||||
&cumulative.height,
|
||||
cached_starts,
|
||||
indexes,
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
base,
|
||||
cumulative,
|
||||
sum,
|
||||
average,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ where
|
||||
{
|
||||
pub base: M::Stored<EagerVec<PcoVec<Height, T>>>,
|
||||
#[traversable(hidden)]
|
||||
pub cumulative: M::Stored<EagerVec<PcoVec<Height, f64>>>,
|
||||
pub cumulative: M::Stored<EagerVec<PcoVec<Height, T>>>,
|
||||
#[traversable(flatten)]
|
||||
pub average: LazyRollingAvgsFromHeight<T>,
|
||||
}
|
||||
@@ -38,8 +38,8 @@ where
|
||||
cached_starts: &CachedWindowStarts,
|
||||
) -> Result<Self> {
|
||||
let base: EagerVec<PcoVec<Height, T>> = EagerVec::forced_import(db, name, version)?;
|
||||
let cumulative: EagerVec<PcoVec<Height, f64>> =
|
||||
EagerVec::forced_import(db, &format!("{name}_cumulative"), version)?;
|
||||
let cumulative: EagerVec<PcoVec<Height, T>> =
|
||||
EagerVec::forced_import(db, &format!("{name}_cumulative"), version + Version::ONE)?;
|
||||
let average = LazyRollingAvgsFromHeight::new(
|
||||
&format!("{name}_average"),
|
||||
version + Version::ONE,
|
||||
|
||||
@@ -12,7 +12,7 @@ pub struct LazyRollingAvgFromHeight<T>
|
||||
where
|
||||
T: NumericValue + JsonSchema,
|
||||
{
|
||||
pub height: LazyDeltaVec<Height, f64, T, DeltaAvg>,
|
||||
pub height: LazyDeltaVec<Height, T, T, DeltaAvg>,
|
||||
#[traversable(flatten)]
|
||||
pub resolutions: Box<Resolutions<T>>,
|
||||
}
|
||||
|
||||
@@ -12,10 +12,11 @@ use crate::{
|
||||
use super::LazyRollingAvgFromHeight;
|
||||
|
||||
/// Lazy rolling averages for all 4 window durations (24h, 1w, 1m, 1y),
|
||||
/// derived from an f64 cumulative vec + cached window starts.
|
||||
/// derived from a cumulative vec + cached window starts.
|
||||
///
|
||||
/// Nothing is stored on disk — all values are computed on-the-fly via
|
||||
/// `LazyDeltaVec<Height, f64, T, DeltaAvg>`: `(cum[h] - cum[start-1]) / (h - start + 1)`.
|
||||
/// `LazyDeltaVec<Height, T, T, DeltaAvg>`: `(cum[h] - cum[start-1]) / (h - start + 1)`.
|
||||
/// T is converted to f64 internally for division, then back to T.
|
||||
#[derive(Clone, Deref, DerefMut, Traversable)]
|
||||
#[traversable(transparent)]
|
||||
pub struct LazyRollingAvgsFromHeight<T>(pub Windows<LazyRollingAvgFromHeight<T>>)
|
||||
@@ -29,7 +30,7 @@ where
|
||||
pub fn new(
|
||||
name: &str,
|
||||
version: Version,
|
||||
cumulative: &(impl ReadableCloneableVec<Height, f64> + 'static),
|
||||
cumulative: &(impl ReadableCloneableVec<Height, T> + 'static),
|
||||
cached_starts: &CachedWindowStarts,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Self {
|
||||
@@ -39,7 +40,7 @@ where
|
||||
let full_name = format!("{name}_{suffix}");
|
||||
let cached = cached_start.clone();
|
||||
let starts_version = cached.version();
|
||||
let avg = LazyDeltaVec::<Height, f64, T, DeltaAvg>::new(
|
||||
let avg = LazyDeltaVec::<Height, T, T, DeltaAvg>::new(
|
||||
&full_name,
|
||||
version,
|
||||
cum_source.clone(),
|
||||
|
||||
@@ -10,18 +10,19 @@ use vecdb::{Database, Exit, ReadableCloneableVec, ReadableVec, Rw, StorageMode};
|
||||
use crate::{
|
||||
indexes,
|
||||
internal::{
|
||||
CachedWindowStarts, NumericValue, LazyRollingSumsFromHeight, RollingDistribution,
|
||||
WindowStarts,
|
||||
CachedWindowStarts, NumericValue, LazyRollingAvgsFromHeight, LazyRollingSumsFromHeight,
|
||||
RollingDistribution, WindowStarts,
|
||||
},
|
||||
};
|
||||
|
||||
/// Lazy rolling sums + stored rolling distribution (8 stats × 4 windows).
|
||||
/// Lazy rolling sums + lazy rolling averages + stored rolling distribution (7 stats × 4 windows).
|
||||
#[derive(Traversable)]
|
||||
pub struct RollingComplete<T, M: StorageMode = Rw>
|
||||
where
|
||||
T: NumericValue + JsonSchema,
|
||||
{
|
||||
pub sum: LazyRollingSumsFromHeight<T>,
|
||||
pub average: LazyRollingAvgsFromHeight<T>,
|
||||
#[traversable(flatten)]
|
||||
pub distribution: RollingDistribution<T, M>,
|
||||
}
|
||||
@@ -45,9 +46,20 @@ where
|
||||
cached_starts,
|
||||
indexes,
|
||||
);
|
||||
let average = LazyRollingAvgsFromHeight::new(
|
||||
&format!("{name}_average"),
|
||||
version,
|
||||
cumulative,
|
||||
cached_starts,
|
||||
indexes,
|
||||
);
|
||||
let distribution = RollingDistribution::forced_import(db, name, version, indexes)?;
|
||||
|
||||
Ok(Self { sum, distribution })
|
||||
Ok(Self {
|
||||
sum,
|
||||
average,
|
||||
distribution,
|
||||
})
|
||||
}
|
||||
|
||||
/// Compute rolling distribution stats across all 4 windows.
|
||||
|
||||
@@ -6,12 +6,12 @@ use vecdb::{ReadableCloneableVec, UnaryTransform};
|
||||
use crate::{
|
||||
indexes,
|
||||
internal::{
|
||||
CachedWindowStarts, ComputedVecValue, LazyRollingDistribution, LazyRollingSumsFromHeight,
|
||||
NumericValue, RollingComplete,
|
||||
CachedWindowStarts, ComputedVecValue, LazyRollingAvgsFromHeight,
|
||||
LazyRollingDistribution, LazyRollingSumsFromHeight, NumericValue, RollingComplete,
|
||||
},
|
||||
};
|
||||
|
||||
/// Lazy analog of `RollingComplete<T>`: lazy rolling sums + lazy rolling distribution.
|
||||
/// Lazy analog of `RollingComplete<T>`: lazy rolling sums + lazy rolling averages + lazy rolling distribution.
|
||||
/// Zero stored vecs.
|
||||
#[derive(Clone, Traversable)]
|
||||
pub struct LazyRollingComplete<T, S1T>
|
||||
@@ -20,6 +20,7 @@ where
|
||||
S1T: ComputedVecValue + JsonSchema,
|
||||
{
|
||||
pub sum: LazyRollingSumsFromHeight<T>,
|
||||
pub average: LazyRollingAvgsFromHeight<T>,
|
||||
#[traversable(flatten)]
|
||||
pub distribution: LazyRollingDistribution<T, S1T>,
|
||||
}
|
||||
@@ -44,11 +45,22 @@ where
|
||||
cached_starts,
|
||||
indexes,
|
||||
);
|
||||
let average = LazyRollingAvgsFromHeight::new(
|
||||
&format!("{name}_average"),
|
||||
version,
|
||||
cumulative,
|
||||
cached_starts,
|
||||
indexes,
|
||||
);
|
||||
let distribution = LazyRollingDistribution::from_rolling_distribution::<F>(
|
||||
name,
|
||||
version,
|
||||
&source.distribution,
|
||||
);
|
||||
Self { sum, distribution }
|
||||
Self {
|
||||
sum,
|
||||
average,
|
||||
distribution,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ impl<T: VecValue> UnaryTransform<T, T> for Identity<T> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub struct HalveSats;
|
||||
|
||||
impl UnaryTransform<Sats, Sats> for HalveSats {
|
||||
|
||||
@@ -95,7 +95,7 @@ impl Vecs {
|
||||
},
|
||||
exit,
|
||||
)?;
|
||||
self.subsidy.compute(prices, starting_indexes.height, exit)?;
|
||||
self.subsidy.compute_rest(starting_indexes.height, prices, exit)?;
|
||||
|
||||
self.unclaimed.base.sats.height.compute_transform(
|
||||
starting_indexes.height,
|
||||
|
||||
@@ -36,7 +36,9 @@ impl Vecs {
|
||||
coinbase: AmountPerBlockCumulativeWithSums::forced_import(
|
||||
db, "coinbase", version, indexes, cached_starts,
|
||||
)?,
|
||||
subsidy: AmountPerBlockCumulative::forced_import(db, "subsidy", version, indexes)?,
|
||||
subsidy: AmountPerBlockCumulativeWithSums::forced_import(
|
||||
db, "subsidy", version, indexes, cached_starts,
|
||||
)?,
|
||||
fees: AmountPerBlockFull::forced_import(db, "fees", version, indexes, cached_starts)?,
|
||||
unclaimed: AmountPerBlockCumulative::forced_import(
|
||||
db,
|
||||
|
||||
@@ -11,7 +11,7 @@ use crate::internal::{
|
||||
#[derive(Traversable)]
|
||||
pub struct Vecs<M: StorageMode = Rw> {
|
||||
pub coinbase: AmountPerBlockCumulativeWithSums<M>,
|
||||
pub subsidy: AmountPerBlockCumulative<M>,
|
||||
pub subsidy: AmountPerBlockCumulativeWithSums<M>,
|
||||
pub fees: AmountPerBlockFull<M>,
|
||||
pub unclaimed: AmountPerBlockCumulative<M>,
|
||||
#[traversable(wrap = "fees", rename = "dominance")]
|
||||
|
||||
Reference in New Issue
Block a user