global: snapshot

This commit is contained in:
nym21
2026-03-10 11:22:17 +01:00
parent 64ef63a056
commit 5ede3dc416
40 changed files with 408 additions and 259 deletions

View File

@@ -15,8 +15,8 @@ impl Vecs {
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
// Block count height + cumulative
self.block_count.height.compute_range(
// Block count raw + cumulative
self.block_count.raw.height.compute_range(
starting_indexes.height,
&indexer.vecs.blocks.weight,
|h| (h, StoredU32::from(1_u32)),
@@ -24,7 +24,7 @@ impl Vecs {
)?;
self.block_count.cumulative.height.compute_cumulative(
starting_indexes.height,
&self.block_count.height,
&self.block_count.raw.height,
exit,
)?;
@@ -33,7 +33,7 @@ impl Vecs {
self.block_count.sum.compute_rolling_sum(
starting_indexes.height,
&ws,
&self.block_count.height,
&self.block_count.raw.height,
exit,
)?;

View File

@@ -33,8 +33,8 @@ impl Vecs {
.compute(starting_indexes.height, &window_starts, exit, |vec| {
vec.compute_subtract(
starting_indexes.height,
&self.coinblocks_created.height,
&all_metrics.activity.coinblocks_destroyed.raw.height,
&self.coinblocks_created.raw.height,
&distribution.coinblocks_destroyed.raw.height,
exit,
)?;
Ok(())
@@ -42,7 +42,7 @@ impl Vecs {
self.liveliness.height.compute_divide(
starting_indexes.height,
&all_metrics.activity.coinblocks_destroyed.cumulative.height,
&distribution.coinblocks_destroyed.cumulative.height,
&self.coinblocks_created.cumulative.height,
exit,
)?;

View File

@@ -17,7 +17,7 @@ impl Vecs {
self.vocdd_median_1y.compute_rolling_median_from_starts(
starting_indexes.height,
&blocks.lookback.height_1y_ago,
&value.vocdd.height,
&value.vocdd.raw.height,
exit,
)?;

View File

@@ -19,7 +19,7 @@ impl Vecs {
let window_starts = blocks.lookback.window_starts();
let all_metrics = &distribution.utxo_cohorts.all.metrics;
let coinblocks_destroyed = &all_metrics.activity.coinblocks_destroyed;
let coinblocks_destroyed = &distribution.coinblocks_destroyed;
let coindays_destroyed = &all_metrics.activity.coindays_destroyed;
let circulating_supply = &all_metrics.supply.total.btc.height;
@@ -39,7 +39,7 @@ impl Vecs {
vec.compute_multiply(
starting_indexes.height,
&prices.price.usd.height,
&activity.coinblocks_created.height,
&activity.coinblocks_created.raw.height,
exit,
)?;
Ok(())
@@ -50,7 +50,7 @@ impl Vecs {
vec.compute_multiply(
starting_indexes.height,
&prices.price.usd.height,
&activity.coinblocks_stored.height,
&activity.coinblocks_stored.raw.height,
exit,
)?;
Ok(())

View File

@@ -47,8 +47,7 @@ pub(crate) fn process_sent(
for (receive_height, by_type) in sent_data.into_iter() {
let prev_price = height_to_price[receive_height.to_usize()];
let prev_timestamp = height_to_timestamp[receive_height.to_usize()];
let blocks_old = current_height.to_usize() - receive_height.to_usize();
let age = Age::new(current_timestamp, prev_timestamp, blocks_old);
let age = Age::new(current_timestamp, prev_timestamp);
// Compute peak price during holding period for peak regret
// This is the max HIGH price between receive and send heights

View File

@@ -43,8 +43,7 @@ impl UTXOCohorts<Rw> {
let block_state = &chain_state[receive_height.to_usize()];
let prev_price = block_state.price;
let blocks_old = chain_len - 1 - receive_height.to_usize();
let age = Age::new(last_timestamp, block_state.timestamp, blocks_old);
let age = Age::new(last_timestamp, block_state.timestamp);
// Compute peak price during holding period for peak regret
// This is the max price between receive and send heights

View File

@@ -2,12 +2,12 @@ use brk_cohort::ByAddressType;
use brk_error::Result;
use brk_indexer::Indexer;
use brk_types::{
Cents, Date, Height, ONE_DAY_IN_SEC, OutputType, Sats, Timestamp, TxIndex, TypeIndex,
Cents, Date, Height, ONE_DAY_IN_SEC, OutputType, Sats, StoredF64, Timestamp, TxIndex, TypeIndex,
};
use rayon::prelude::*;
use rustc_hash::FxHashSet;
use tracing::{debug, info};
use vecdb::{AnyVec, Exit, ReadableVec, VecIndex};
use vecdb::{AnyVec, Exit, ReadableVec, VecIndex, WritableVec};
use crate::{
distribution::{
@@ -66,7 +66,7 @@ pub(crate) fn process_blocks(
let height_to_first_txindex = &indexer.vecs.transactions.first_txindex;
let height_to_first_txoutindex = &indexer.vecs.outputs.first_txoutindex;
let height_to_first_txinindex = &indexer.vecs.inputs.first_txinindex;
let height_to_tx_count = &transactions.count.tx_count.height;
let height_to_tx_count = &transactions.count.tx_count.raw.height;
let height_to_output_count = &outputs.count.total_count.full.sum;
let height_to_input_count = &inputs.count.full.sum;
let txindex_to_output_count = &indexes.txindex.output_count;
@@ -353,6 +353,23 @@ pub(crate) fn process_blocks(
timestamp,
});
// Compute total coinblocks destroyed (once globally, before send() consumes height_to_sent)
{
let h = height.to_usize();
let total_satblocks: u128 = height_to_sent
.iter()
.filter(|(rh, _)| rh.to_usize() < h)
.map(|(rh, sent)| {
let blocks_old = h - rh.to_usize();
blocks_old as u128 * u64::from(sent.spendable_supply.value) as u128
})
.sum();
vecs.coinblocks_destroyed.raw.height.truncate_push(
height,
StoredF64::from(total_satblocks as f64 / Sats::ONE_BTC_U128 as f64),
)?;
}
// Record maturation (sats crossing age boundaries)
vecs.utxo_cohorts.push_maturation(height, &matured)?;

View File

@@ -79,9 +79,13 @@ pub(crate) fn write(
.chain(vecs.addr_count.par_iter_height_mut())
.chain(vecs.empty_addr_count.par_iter_height_mut())
.chain(vecs.address_activity.par_iter_height_mut())
.chain(rayon::iter::once(
&mut vecs.supply_state as &mut dyn AnyStoredVec,
))
.chain(
[
&mut vecs.supply_state as &mut dyn AnyStoredVec,
&mut vecs.coinblocks_destroyed.raw.height,
]
.into_par_iter(),
)
.chain(vecs.utxo_cohorts.par_iter_vecs_mut())
.chain(vecs.address_cohorts.par_iter_vecs_mut())
.try_for_each(|v| v.any_stamped_write_maybe_with_changes(stamp, with_changes))?;

View File

@@ -2,7 +2,7 @@ use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Bitcoin, Height, Indexes, Sats, StoredF32, StoredF64, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{AnyStoredVec, AnyVec, Exit, ReadableVec, Rw, StorageMode, WritableVec};
use vecdb::{AnyStoredVec, Exit, ReadableVec, Rw, StorageMode};
use crate::internal::{ComputedPerBlock, RollingWindowsFrom1w};
@@ -10,12 +10,6 @@ use crate::{blocks, distribution::{metrics::ImportConfig, state::{CohortState, R
use super::ActivityCore;
#[derive(Traversable)]
pub struct ActivityCoinblocks<M: StorageMode = Rw> {
pub raw: ComputedPerBlock<StoredF64, M>,
pub cumulative: ComputedPerBlock<StoredF64, M>,
}
#[derive(Deref, DerefMut, Traversable)]
pub struct ActivityFull<M: StorageMode = Rw> {
#[deref]
@@ -23,8 +17,6 @@ pub struct ActivityFull<M: StorageMode = Rw> {
#[traversable(flatten)]
pub inner: ActivityCore<M>,
pub coinblocks_destroyed: ActivityCoinblocks<M>,
#[traversable(wrap = "coindays_destroyed", rename = "cumulative")]
pub coindays_destroyed_cumulative: ComputedPerBlock<StoredF64, M>,
#[traversable(wrap = "coindays_destroyed", rename = "sum")]
@@ -42,10 +34,6 @@ impl ActivityFull {
let v1 = Version::ONE;
Ok(Self {
inner: ActivityCore::forced_import(cfg)?,
coinblocks_destroyed: ActivityCoinblocks {
raw: cfg.import("coinblocks_destroyed", v1)?,
cumulative: cfg.import("coinblocks_destroyed_cumulative", v1)?,
},
coindays_destroyed_cumulative: cfg.import("coindays_destroyed_cumulative", v1)?,
coindays_destroyed_sum: cfg.import("coindays_destroyed", v1)?,
sent_sum_extended: cfg.import("sent", v1)?,
@@ -55,9 +43,7 @@ impl ActivityFull {
}
pub(crate) fn full_min_len(&self) -> usize {
self.inner
.min_len()
.min(self.coinblocks_destroyed.raw.height.len())
self.inner.min_len()
}
pub(crate) fn full_truncate_push(
@@ -65,17 +51,11 @@ impl ActivityFull {
height: Height,
state: &CohortState<impl RealizedOps>,
) -> Result<()> {
self.inner.truncate_push(height, state)?;
self.coinblocks_destroyed.raw.height.truncate_push(
height,
StoredF64::from(Bitcoin::from(state.satblocks_destroyed)),
)?;
Ok(())
self.inner.truncate_push(height, state)
}
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
let mut vecs = self.inner.collect_vecs_mut();
vecs.push(&mut self.coinblocks_destroyed.raw.height as &mut dyn AnyStoredVec);
vecs.push(&mut self.dormancy.height);
vecs.push(&mut self.velocity.height);
vecs
@@ -100,15 +80,6 @@ impl ActivityFull {
self.inner
.compute_rest_part1(blocks, starting_indexes, exit)?;
self.coinblocks_destroyed
.cumulative
.height
.compute_cumulative(
starting_indexes.height,
&self.coinblocks_destroyed.raw.height,
exit,
)?;
self.coindays_destroyed_cumulative
.height
.compute_cumulative(

View File

@@ -289,7 +289,7 @@ impl RealizedFull {
.min(self.investor.price.cents.height.len())
.min(self.cap_raw.len())
.min(self.investor.cap_raw.len())
.min(self.peak_regret.value.height.len())
.min(self.peak_regret.value.raw.height.len())
}
pub(crate) fn truncate_push(
@@ -326,6 +326,7 @@ impl RealizedFull {
.truncate_push(height, state.realized.investor_cap_raw())?;
self.peak_regret
.value
.raw
.height
.truncate_push(height, state.realized.peak_regret())?;
@@ -341,7 +342,7 @@ impl RealizedFull {
vecs.push(&mut self.investor.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.peak_regret.value.height);
vecs.push(&mut self.peak_regret.value.raw.height);
vecs
}
@@ -400,6 +401,7 @@ impl RealizedFull {
self.peak_regret
.value
.raw
.height
.truncate_push(height, accum.peak_regret)?;
@@ -609,7 +611,7 @@ impl RealizedFull {
.rel_to_rcap
.compute_binary::<Cents, Cents, RatioCentsBp32>(
starting_indexes.height,
&self.peak_regret.value.height,
&self.peak_regret.value.raw.height,
&self.core.minimal.cap.cents.height,
exit,
)?;

View File

@@ -0,0 +1,96 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Height, Indexes, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{AnyStoredVec, AnyVec, Exit, Rw, StorageMode, WritableVec};
use crate::{distribution::state::UnrealizedState, prices};
use crate::internal::AmountPerBlock;
use crate::distribution::metrics::ImportConfig;
use super::SupplyBase;
/// Core supply metrics: total + halved + in_profit/in_loss (4 stored vecs).
#[derive(Deref, DerefMut, Traversable)]
pub struct SupplyCore<M: StorageMode = Rw> {
#[deref]
#[deref_mut]
#[traversable(flatten)]
pub base: SupplyBase<M>,
pub in_profit: AmountPerBlock<M>,
pub in_loss: AmountPerBlock<M>,
}
impl SupplyCore {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
let v0 = Version::ZERO;
let base = SupplyBase::forced_import(cfg)?;
Ok(Self {
base,
in_profit: cfg.import("supply_in_profit", v0)?,
in_loss: cfg.import("supply_in_loss", v0)?,
})
}
pub(crate) fn min_len(&self) -> usize {
self.base
.min_len()
.min(self.in_profit.sats.height.len())
.min(self.in_loss.sats.height.len())
}
pub(crate) fn truncate_push_profitability(
&mut self,
height: Height,
state: &UnrealizedState,
) -> Result<()> {
self.in_profit
.sats
.height
.truncate_push(height, state.supply_in_profit)?;
self.in_loss
.sats
.height
.truncate_push(height, state.supply_in_loss)?;
Ok(())
}
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
let mut vecs = self.base.collect_vecs_mut();
vecs.push(&mut self.in_profit.sats.height as &mut dyn AnyStoredVec);
vecs.push(&mut self.in_profit.cents.height);
vecs.push(&mut self.in_loss.sats.height);
vecs.push(&mut self.in_loss.cents.height);
vecs
}
pub(crate) fn compute(
&mut self,
prices: &prices::Vecs,
max_from: Height,
exit: &Exit,
) -> Result<()> {
self.base.compute(prices, max_from, exit)?;
self.in_profit.compute(prices, max_from, exit)?;
self.in_loss.compute(prices, max_from, exit)?;
Ok(())
}
pub(crate) fn compute_from_stateful(
&mut self,
starting_indexes: &Indexes,
others: &[&Self],
exit: &Exit,
) -> Result<()> {
let base_refs: Vec<&SupplyBase> = others.iter().map(|o| &o.base).collect();
self.base
.compute_from_stateful(starting_indexes, &base_refs, exit)?;
sum_others!(self, starting_indexes, others, exit; in_profit.sats.height);
sum_others!(self, starting_indexes, others, exit; in_loss.sats.height);
Ok(())
}
}

View File

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

View File

@@ -28,7 +28,6 @@ impl<R: RealizedOps> AddressCohortState<R> {
self.addr_count = 0;
self.inner.supply = SupplyState::default();
self.inner.sent = Sats::ZERO;
self.inner.satblocks_destroyed = Sats::ZERO;
self.inner.satdays_destroyed = Sats::ZERO;
self.inner.realized = R::default();
}

View File

@@ -53,7 +53,6 @@ pub struct CohortState<R: RealizedOps> {
pub supply: SupplyState,
pub realized: R,
pub sent: Sats,
pub satblocks_destroyed: Sats,
pub satdays_destroyed: Sats,
cost_basis_data: CostBasisData,
}
@@ -64,7 +63,6 @@ impl<R: RealizedOps> CohortState<R> {
supply: SupplyState::default(),
realized: R::default(),
sent: Sats::ZERO,
satblocks_destroyed: Sats::ZERO,
satdays_destroyed: Sats::ZERO,
cost_basis_data: CostBasisData::create(path, name),
}
@@ -104,7 +102,6 @@ impl<R: RealizedOps> CohortState<R> {
pub(crate) fn reset_single_iteration_values(&mut self) {
self.sent = Sats::ZERO;
self.satdays_destroyed = Sats::ZERO;
self.satblocks_destroyed = Sats::ZERO;
self.realized.reset_single_iteration_values();
}
@@ -202,7 +199,6 @@ impl<R: RealizedOps> CohortState<R> {
) {
self.supply -= supply;
self.sent += pre.sats;
self.satblocks_destroyed += pre.age.satblocks_destroyed(pre.sats);
self.satdays_destroyed += pre.age.satdays_destroyed(pre.sats);
self.realized
@@ -246,7 +242,6 @@ impl<R: RealizedOps> CohortState<R> {
if supply.value > Sats::ZERO {
self.sent += supply.value;
self.satblocks_destroyed += age.satblocks_destroyed(supply.value);
self.satdays_destroyed += age.satdays_destroyed(supply.value);
let sats = supply.value;

View File

@@ -23,7 +23,6 @@ impl<R: RealizedOps> UTXOCohortState<R> {
pub(crate) fn reset(&mut self) {
self.0.supply = SupplyState::default();
self.0.sent = Sats::ZERO;
self.0.satblocks_destroyed = Sats::ZERO;
self.0.satdays_destroyed = Sats::ZERO;
self.0.realized = R::default();
}

View File

@@ -5,7 +5,7 @@ use brk_indexer::Indexer;
use brk_traversable::Traversable;
use brk_types::{
Cents, EmptyAddressData, EmptyAddressIndex, FundedAddressData, FundedAddressIndex, Height,
Indexes, SupplyState, Timestamp, TxIndex, Version,
Indexes, StoredF64, SupplyState, Timestamp, TxIndex, Version,
};
use tracing::{debug, info};
use vecdb::{
@@ -23,7 +23,7 @@ use crate::{
state::BlockState,
},
indexes, inputs,
internal::{finalize_db, open_db},
internal::{finalize_db, open_db, ComputedPerBlockCumulative},
outputs, prices, transactions,
};
@@ -49,6 +49,8 @@ pub struct Vecs<M: StorageMode = Rw> {
pub utxo_cohorts: UTXOCohorts<M>,
pub address_cohorts: AddressCohorts<M>,
pub coinblocks_destroyed: ComputedPerBlockCumulative<StoredF64, M>,
pub addr_count: AddrCountsVecs<M>,
pub empty_addr_count: AddrCountsVecs<M>,
pub address_activity: AddressActivityVecs<M>,
@@ -159,6 +161,13 @@ impl Vecs {
utxo_cohorts,
address_cohorts,
coinblocks_destroyed: ComputedPerBlockCumulative::forced_import(
&db,
"coinblocks_destroyed",
version + Version::TWO,
indexes,
)?,
any_address_indexes: AnyAddressIndexesVecs::forced_import(&db, version)?,
addresses_data: AddressesDataVecs {
funded: fundedaddressindex_to_fundedaddressdata,
@@ -390,6 +399,10 @@ impl Vecs {
exit,
)?;
// 5b. Compute coinblocks_destroyed cumulative from raw
self.coinblocks_destroyed
.compute_rest(starting_indexes.height, exit)?;
// 6. Compute rest part1 (day1 mappings)
aggregates::compute_rest_part1(
&mut self.utxo_cohorts,
@@ -474,5 +487,6 @@ impl Vecs {
.min(Height::from(self.addr_count.min_stateful_height()))
.min(Height::from(self.empty_addr_count.min_stateful_height()))
.min(Height::from(self.address_activity.min_stateful_height()))
.min(Height::from(self.coinblocks_destroyed.raw.height.len()))
}
}

View File

@@ -0,0 +1,78 @@
use brk_error::Result;
use brk_types::{Dollars, Indexes};
use vecdb::Exit;
use super::{gini, Vecs};
use crate::{distribution, internal::RatioDollarsBp32, mining, transactions};
impl Vecs {
pub(crate) fn compute(
&mut self,
mining: &mining::Vecs,
distribution: &distribution::Vecs,
transactions: &transactions::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
// Puell Multiple: daily_subsidy_usd / sma_365d_subsidy_usd
self.puell_multiple
.bps
.compute_binary::<Dollars, Dollars, RatioDollarsBp32>(
starting_indexes.height,
&mining.rewards.subsidy.base.usd.height,
&mining.rewards.subsidy_sma_1y.usd.height,
exit,
)?;
// Gini coefficient (UTXO distribution inequality)
gini::compute(&mut self.gini, distribution, starting_indexes, exit)?;
// RHODL Ratio: 1d-1w realized cap / 1y-2y realized cap
self.rhodl_ratio
.bps
.compute_binary::<Dollars, Dollars, RatioDollarsBp32>(
starting_indexes.height,
&distribution
.utxo_cohorts
.age_range
._1d_to_1w
.metrics
.realized
.cap
.usd
.height,
&distribution
.utxo_cohorts
.age_range
._1y_to_2y
.metrics
.realized
.cap
.usd
.height,
exit,
)?;
// NVT: market_cap / tx_volume_24h
let market_cap = &distribution
.utxo_cohorts
.all
.metrics
.supply
.total
.usd
.height;
self.nvt
.bps
.compute_binary::<Dollars, Dollars, RatioDollarsBp32>(
starting_indexes.height,
market_cap,
&transactions.volume.sent_sum.rolling._24h.usd.height,
exit,
)?;
let _lock = exit.lock();
self.db.compact()?;
Ok(())
}
}

View File

@@ -0,0 +1,38 @@
use std::path::Path;
use brk_error::Result;
use brk_types::Version;
use super::Vecs;
use crate::{
indexes,
internal::{finalize_db, open_db, PercentPerBlock, RatioPerBlock},
};
const VERSION: Version = Version::new(1);
impl Vecs {
pub(crate) fn forced_import(
parent_path: &Path,
parent_version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
let db = open_db(parent_path, super::DB_NAME, 100_000)?;
let v = parent_version + VERSION;
let puell_multiple = RatioPerBlock::forced_import_raw(&db, "puell_multiple", v, indexes)?;
let nvt = RatioPerBlock::forced_import_raw(&db, "nvt", v, indexes)?;
let gini = PercentPerBlock::forced_import(&db, "gini", v, indexes)?;
let rhodl_ratio = RatioPerBlock::forced_import_raw(&db, "rhodl_ratio", v, indexes)?;
let this = Self {
db,
puell_multiple,
nvt,
gini,
rhodl_ratio,
};
finalize_db(&this.db, &this)?;
Ok(this)
}
}

View File

@@ -0,0 +1,8 @@
mod compute;
mod gini;
mod import;
mod vecs;
pub use vecs::Vecs;
pub const DB_NAME: &str = "indicators";

View File

@@ -0,0 +1,15 @@
use brk_traversable::Traversable;
use brk_types::{BasisPoints16, BasisPoints32};
use vecdb::{Database, Rw, StorageMode};
use crate::internal::{PercentPerBlock, RatioPerBlock};
#[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw> {
#[traversable(skip)]
pub(crate) db: Database,
pub puell_multiple: RatioPerBlock<BasisPoints32, M>,
pub nvt: RatioPerBlock<BasisPoints32, M>,
pub gini: PercentPerBlock<BasisPoints16, M>,
pub rhodl_ratio: RatioPerBlock<BasisPoints32, M>,
}

View File

@@ -1,14 +1,13 @@
//! ComputedPerBlockCumulative - stored height + LazyAggVec + cumulative (from height).
//! ComputedPerBlockCumulative - raw ComputedPerBlock + cumulative ComputedPerBlock.
//!
//! Like ComputedPerBlockCumulativeSum but without RollingWindows.
//! Used for distribution metrics where rolling is optional per cohort.
//! Cumulative gets its own ComputedPerBlock so it has LazyAggVec index views.
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Height, Version};
use schemars::JsonSchema;
use vecdb::{Database, EagerVec, Exit, ImportableVec, PcoVec, Rw, StorageMode};
use vecdb::{Database, EagerVec, Exit, PcoVec, Rw, StorageMode};
use crate::{
indexes,
@@ -20,7 +19,7 @@ pub struct ComputedPerBlockCumulative<T, M: StorageMode = Rw>
where
T: NumericValue + JsonSchema,
{
pub height: M::Stored<EagerVec<PcoVec<Height, T>>>,
pub raw: ComputedPerBlock<T, M>,
pub cumulative: ComputedPerBlock<T, M>,
}
@@ -34,35 +33,35 @@ where
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
let height: EagerVec<PcoVec<Height, T>> = EagerVec::forced_import(db, name, version)?;
let raw = ComputedPerBlock::forced_import(db, name, version, indexes)?;
let cumulative =
ComputedPerBlock::forced_import(db, &format!("{name}_cumulative"), version, indexes)?;
Ok(Self { height, cumulative })
Ok(Self { raw, cumulative })
}
/// Compute height data via closure, then cumulative only (no rolling).
/// Compute raw data via closure, then cumulative only (no rolling).
pub(crate) fn compute(
&mut self,
max_from: Height,
exit: &Exit,
compute_height: impl FnOnce(&mut EagerVec<PcoVec<Height, T>>) -> Result<()>,
compute_raw: impl FnOnce(&mut EagerVec<PcoVec<Height, T>>) -> Result<()>,
) -> Result<()>
where
T: Default,
{
compute_height(&mut self.height)?;
compute_raw(&mut self.raw.height)?;
self.compute_rest(max_from, exit)
}
/// Compute cumulative from already-filled height vec.
/// Compute cumulative from already-filled raw vec.
pub(crate) fn compute_rest(&mut self, max_from: Height, exit: &Exit) -> Result<()>
where
T: Default,
{
self.cumulative
.height
.compute_cumulative(max_from, &self.height, exit)?;
.compute_cumulative(max_from, &self.raw.height, exit)?;
Ok(())
}
}

View File

@@ -1,8 +1,7 @@
//! ComputedPerBlockCumulativeSum - stored height + LazyAggVec + cumulative (from height) + RollingWindows (sum).
//! ComputedPerBlockCumulativeSum - raw ComputedPerBlock + cumulative ComputedPerBlock + RollingWindows (sum).
//!
//! Like ComputedPerBlockFull but with rolling sum only (no distribution).
//! Used for count metrics where distribution stats aren't meaningful.
//! Cumulative gets its own ComputedPerBlock so it has LazyAggVec index views too.
use std::ops::SubAssign;
@@ -10,7 +9,7 @@ use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Height, Version};
use schemars::JsonSchema;
use vecdb::{Database, EagerVec, Exit, ImportableVec, PcoVec, Rw, StorageMode};
use vecdb::{Database, EagerVec, Exit, PcoVec, Rw, StorageMode};
use crate::{
indexes,
@@ -22,7 +21,7 @@ pub struct ComputedPerBlockCumulativeSum<T, M: StorageMode = Rw>
where
T: NumericValue + JsonSchema,
{
pub height: M::Stored<EagerVec<PcoVec<Height, T>>>,
pub raw: ComputedPerBlock<T, M>,
pub cumulative: ComputedPerBlock<T, M>,
pub sum: RollingWindows<T, M>,
}
@@ -37,34 +36,34 @@ where
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
let height: EagerVec<PcoVec<Height, T>> = EagerVec::forced_import(db, name, version)?;
let raw = ComputedPerBlock::forced_import(db, name, version, indexes)?;
let cumulative =
ComputedPerBlock::forced_import(db, &format!("{name}_cumulative"), version, indexes)?;
let rolling = RollingWindows::forced_import(db, name, version, indexes)?;
let sum = RollingWindows::forced_import(db, name, version, indexes)?;
Ok(Self {
height,
raw,
cumulative,
sum: rolling,
sum,
})
}
/// Compute height data via closure, then cumulative + rolling sum.
/// Compute raw data via closure, then cumulative + rolling sum.
pub(crate) fn compute(
&mut self,
max_from: Height,
windows: &WindowStarts<'_>,
exit: &Exit,
compute_height: impl FnOnce(&mut EagerVec<PcoVec<Height, T>>) -> Result<()>,
compute_raw: impl FnOnce(&mut EagerVec<PcoVec<Height, T>>) -> Result<()>,
) -> Result<()>
where
T: Default + SubAssign,
{
compute_height(&mut self.height)?;
compute_raw(&mut self.raw.height)?;
self.compute_rest(max_from, windows, exit)
}
/// Compute cumulative + rolling sum from already-populated height data.
/// Compute cumulative + rolling sum from already-populated raw data.
pub(crate) fn compute_rest(
&mut self,
max_from: Height,
@@ -76,9 +75,9 @@ where
{
self.cumulative
.height
.compute_cumulative(max_from, &self.height, exit)?;
.compute_cumulative(max_from, &self.raw.height, exit)?;
self.sum
.compute_rolling_sum(max_from, windows, &self.height, exit)?;
.compute_rolling_sum(max_from, windows, &self.raw.height, exit)?;
Ok(())
}
}

View File

@@ -1,7 +1,6 @@
//! ComputedPerBlockFull - stored height + LazyAggVec + cumulative (from height) + RollingFull.
//! ComputedPerBlockFull - raw ComputedPerBlock + cumulative ComputedPerBlock + RollingFull.
//!
//! For metrics with stored per-block data, cumulative sums, and rolling windows.
//! Cumulative gets its own ComputedPerBlock so it has LazyAggVec index views too.
use std::ops::SubAssign;
@@ -9,7 +8,7 @@ use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Height, Version};
use schemars::JsonSchema;
use vecdb::{Database, EagerVec, Exit, ImportableVec, PcoVec, Rw, StorageMode};
use vecdb::{Database, EagerVec, Exit, PcoVec, Rw, StorageMode};
use crate::{
indexes,
@@ -21,7 +20,7 @@ pub struct ComputedPerBlockFull<T, M: StorageMode = Rw>
where
T: NumericValue + JsonSchema,
{
pub height: M::Stored<EagerVec<PcoVec<Height, T>>>,
pub raw: ComputedPerBlock<T, M>,
pub cumulative: ComputedPerBlock<T, M>,
#[traversable(flatten)]
pub rolling: RollingFull<T, M>,
@@ -37,36 +36,36 @@ where
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
let height: EagerVec<PcoVec<Height, T>> = EagerVec::forced_import(db, name, version)?;
let raw = ComputedPerBlock::forced_import(db, name, version, indexes)?;
let cumulative =
ComputedPerBlock::forced_import(db, &format!("{name}_cumulative"), version, indexes)?;
let rolling = RollingFull::forced_import(db, name, version, indexes)?;
Ok(Self {
height,
raw,
cumulative,
rolling,
})
}
/// Compute height data via closure, then cumulative + rolling.
/// Compute raw data via closure, then cumulative + rolling.
pub(crate) fn compute(
&mut self,
max_from: Height,
windows: &WindowStarts<'_>,
exit: &Exit,
compute_height: impl FnOnce(&mut EagerVec<PcoVec<Height, T>>) -> Result<()>,
compute_raw: impl FnOnce(&mut EagerVec<PcoVec<Height, T>>) -> Result<()>,
) -> Result<()>
where
T: From<f64> + Default + SubAssign + Copy + Ord,
f64: From<T>,
{
compute_height(&mut self.height)?;
compute_raw(&mut self.raw.height)?;
self.cumulative
.height
.compute_cumulative(max_from, &self.height, exit)?;
.compute_cumulative(max_from, &self.raw.height, exit)?;
self.rolling
.compute(max_from, windows, &self.height, exit)?;
.compute(max_from, windows, &self.raw.height, exit)?;
Ok(())
}
}

View File

@@ -1,4 +1,4 @@
//! ComputedPerBlockSum - stored height + RollingWindows (sum only).
//! ComputedPerBlockSum - raw ComputedPerBlock + RollingWindows (sum only).
//!
//! Like ComputedPerBlockCumulativeSum but without the cumulative vec.
@@ -8,11 +8,11 @@ use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Height, Version};
use schemars::JsonSchema;
use vecdb::{Database, EagerVec, Exit, ImportableVec, PcoVec, Rw, StorageMode};
use vecdb::{Database, EagerVec, Exit, PcoVec, Rw, StorageMode};
use crate::{
indexes,
internal::{NumericValue, RollingWindows, WindowStarts},
internal::{ComputedPerBlock, NumericValue, RollingWindows, WindowStarts},
};
#[derive(Traversable)]
@@ -20,7 +20,7 @@ pub struct ComputedPerBlockSum<T, M: StorageMode = Rw>
where
T: NumericValue + JsonSchema,
{
pub height: M::Stored<EagerVec<PcoVec<Height, T>>>,
pub raw: ComputedPerBlock<T, M>,
pub sum: RollingWindows<T, M>,
}
@@ -34,26 +34,26 @@ where
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
let height: EagerVec<PcoVec<Height, T>> = EagerVec::forced_import(db, name, version)?;
let raw = ComputedPerBlock::forced_import(db, name, version, indexes)?;
let sum = RollingWindows::forced_import(db, &format!("{name}_sum"), version, indexes)?;
Ok(Self { height, sum })
Ok(Self { raw, sum })
}
/// Compute height data via closure, then rolling sum.
/// Compute raw data via closure, then rolling sum.
pub(crate) fn compute(
&mut self,
max_from: Height,
windows: &WindowStarts<'_>,
exit: &Exit,
compute_height: impl FnOnce(&mut EagerVec<PcoVec<Height, T>>) -> Result<()>,
compute_raw: impl FnOnce(&mut EagerVec<PcoVec<Height, T>>) -> Result<()>,
) -> Result<()>
where
T: Default + SubAssign,
{
compute_height(&mut self.height)?;
compute_raw(&mut self.raw.height)?;
self.sum
.compute_rolling_sum(max_from, windows, &self.height, exit)?;
.compute_rolling_sum(max_from, windows, &self.raw.height, exit)?;
Ok(())
}
}

View File

@@ -14,6 +14,7 @@ mod blocks;
mod cointime;
mod constants;
mod distribution;
mod indicators;
pub mod indexes;
mod inputs;
mod internal;
@@ -37,6 +38,7 @@ pub struct Computer<M: StorageMode = Rw> {
pub cointime: Box<cointime::Vecs<M>>,
pub constants: Box<constants::Vecs>,
pub indexes: Box<indexes::Vecs<M>>,
pub indicators: Box<indicators::Vecs<M>>,
pub market: Box<market::Vecs<M>>,
pub pools: Box<pools::Vecs<M>>,
pub prices: Box<prices::Vecs<M>>,
@@ -174,28 +176,38 @@ impl Computer {
})
})?;
// Market and distribution are independent; import in parallel.
// Market, indicators, and distribution are independent; import in parallel.
// Supply depends on distribution so it runs after.
let (distribution, market) = timed("Imported distribution/market", || {
thread::scope(|s| -> Result<_> {
let market_handle = big_thread().spawn_scoped(s, || -> Result<_> {
Ok(Box::new(market::Vecs::forced_import(
let (distribution, market, indicators) =
timed("Imported distribution/market/indicators", || {
thread::scope(|s| -> Result<_> {
let market_handle = big_thread().spawn_scoped(s, || -> Result<_> {
Ok(Box::new(market::Vecs::forced_import(
&computed_path,
VERSION,
&indexes,
)?))
})?;
let indicators_handle = big_thread().spawn_scoped(s, || -> Result<_> {
Ok(Box::new(indicators::Vecs::forced_import(
&computed_path,
VERSION,
&indexes,
)?))
})?;
let distribution = Box::new(distribution::Vecs::forced_import(
&computed_path,
VERSION,
&indexes,
)?))
})?;
)?);
let distribution = Box::new(distribution::Vecs::forced_import(
&computed_path,
VERSION,
&indexes,
)?);
let market = market_handle.join().unwrap()?;
Ok((distribution, market))
})
})?;
let market = market_handle.join().unwrap()?;
let indicators = indicators_handle.join().unwrap()?;
Ok((distribution, market, indicators))
})
})?;
let supply = timed("Imported supply", || -> Result<_> {
Ok(Box::new(supply::Vecs::forced_import(
@@ -214,6 +226,7 @@ impl Computer {
transactions,
scripts,
constants,
indicators,
market,
distribution,
supply,
@@ -240,6 +253,7 @@ impl Computer {
scripts::DB_NAME,
positions::DB_NAME,
cointime::DB_NAME,
indicators::DB_NAME,
indexes::DB_NAME,
market::DB_NAME,
pools::DB_NAME,
@@ -408,6 +422,15 @@ impl Computer {
&self.indexes,
&self.prices,
&self.blocks,
&starting_indexes,
exit,
)
})
});
let indicators = scope.spawn(|| {
timed("Computed indicators", || {
self.indicators.compute(
&self.mining,
&self.distribution,
&self.transactions,
@@ -431,6 +454,7 @@ impl Computer {
})?;
market.join().unwrap()?;
indicators.join().unwrap()?;
Ok(())
})?;
@@ -473,6 +497,7 @@ impl Computer<Ro> {
positions,
cointime,
constants,
indicators,
indexes,
market,
pools,

View File

@@ -2,20 +2,16 @@ use brk_error::Result;
use brk_types::Indexes;
use vecdb::Exit;
use crate::{blocks, distribution, indexes, mining, prices, transactions};
use crate::{blocks, indexes, prices};
use super::Vecs;
impl Vecs {
#[allow(clippy::too_many_arguments)]
pub(crate) fn compute(
&mut self,
indexes: &indexes::Vecs,
prices: &prices::Vecs,
blocks: &blocks::Vecs,
mining: &mining::Vecs,
distribution: &distribution::Vecs,
transactions: &transactions::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
@@ -63,14 +59,11 @@ impl Vecs {
r4?;
// Phase 3: Depends on returns, range, moving_average
self.indicators.compute(
&mining.rewards,
self.technical.compute(
&self.returns,
&self.range,
prices,
blocks,
distribution,
transactions,
&self.moving_average,
starting_indexes,
exit,

View File

@@ -9,7 +9,7 @@ use crate::{
};
use super::{
AthVecs, DcaVecs, IndicatorsVecs, LookbackVecs, MovingAverageVecs, RangeVecs, ReturnsVecs,
AthVecs, DcaVecs, TechnicalVecs, LookbackVecs, MovingAverageVecs, RangeVecs, ReturnsVecs,
Vecs, VolatilityVecs,
};
@@ -29,7 +29,7 @@ impl Vecs {
let range = RangeVecs::forced_import(&db, version, indexes)?;
let moving_average = MovingAverageVecs::forced_import(&db, version, indexes)?;
let dca = DcaVecs::forced_import(&db, version, indexes)?;
let indicators = IndicatorsVecs::forced_import(&db, version, indexes)?;
let technical = TechnicalVecs::forced_import(&db, version, indexes)?;
let this = Self {
db,
@@ -40,7 +40,7 @@ impl Vecs {
range,
moving_average,
dca,
indicators,
technical,
};
finalize_db(&this.db, &this)?;
Ok(this)

View File

@@ -2,7 +2,7 @@ pub mod ath;
mod compute;
pub mod dca;
mod import;
pub mod indicators;
pub mod technical;
pub mod lookback;
pub mod moving_average;
pub mod range;
@@ -14,7 +14,7 @@ use vecdb::{Database, Rw, StorageMode};
pub use ath::Vecs as AthVecs;
pub use dca::Vecs as DcaVecs;
pub use indicators::Vecs as IndicatorsVecs;
pub use technical::Vecs as TechnicalVecs;
pub use lookback::Vecs as LookbackVecs;
pub use moving_average::Vecs as MovingAverageVecs;
pub use range::Vecs as RangeVecs;
@@ -33,5 +33,5 @@ pub struct Vecs<M: StorageMode = Rw> {
pub range: RangeVecs<M>,
pub moving_average: MovingAverageVecs<M>,
pub dca: DcaVecs<M>,
pub indicators: IndicatorsVecs<M>,
pub technical: TechnicalVecs<M>,
}

View File

@@ -4,9 +4,9 @@ use vecdb::Exit;
use super::{
super::{moving_average, range, returns},
Vecs, gini, macd, rsi,
Vecs, macd, rsi,
};
use crate::{blocks, distribution, internal::RatioDollarsBp32, mining, prices, transactions};
use crate::{blocks, internal::RatioDollarsBp32, prices};
const TF_MULTIPLIERS: [usize; 4] = [1, 7, 30, 365];
@@ -14,27 +14,15 @@ impl Vecs {
#[allow(clippy::too_many_arguments)]
pub(crate) fn compute(
&mut self,
rewards: &mining::RewardsVecs,
returns: &returns::Vecs,
range: &range::Vecs,
prices: &prices::Vecs,
blocks: &blocks::Vecs,
distribution: &distribution::Vecs,
transactions: &transactions::Vecs,
moving_average: &moving_average::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
self.puell_multiple
.bps
.compute_binary::<Dollars, Dollars, RatioDollarsBp32>(
starting_indexes.height,
&rewards.subsidy.base.usd.height,
&rewards.subsidy_sma_1y.usd.height,
exit,
)?;
// Stochastic Oscillator: K = (close - low_2w) / (high_2w - low_2w), stored as ratio (01)
// Stochastic Oscillator: K = (close - low_2w) / (high_2w - low_2w), stored as ratio (0-1)
{
let price = &prices.price.usd.height;
self.stoch_k.bps.height.compute_transform3(
@@ -101,53 +89,6 @@ impl Vecs {
)?;
}
// Gini (per height)
gini::compute(&mut self.gini, distribution, starting_indexes, exit)?;
// RHODL Ratio: 1d-1w realized cap / 1y-2y realized cap
self.rhodl_ratio
.bps
.compute_binary::<Dollars, Dollars, RatioDollarsBp32>(
starting_indexes.height,
&distribution
.utxo_cohorts
.age_range
._1d_to_1w
.metrics
.realized
.cap
.usd
.height,
&distribution
.utxo_cohorts
.age_range
._1y_to_2y
.metrics
.realized
.cap
.usd
.height,
exit,
)?;
// NVT: market_cap / tx_volume_24h
let market_cap = &distribution
.utxo_cohorts
.all
.metrics
.supply
.total
.usd
.height;
self.nvt
.bps
.compute_binary::<Dollars, Dollars, RatioDollarsBp32>(
starting_indexes.height,
market_cap,
&transactions.volume.sent_sum.rolling._24h.usd.height,
exit,
)?;
// Pi Cycle: sma_111d / sma_350d_x2
self.pi_cycle
.bps

View File

@@ -5,7 +5,7 @@ use vecdb::Database;
use super::{MacdChain, RsiChain, Vecs};
use crate::{
indexes,
internal::{ComputedPerBlock, RatioPerBlock, PercentPerBlock, Windows},
internal::{ComputedPerBlock, PercentPerBlock, RatioPerBlock, Windows},
};
const VERSION: Version = Version::new(2);
@@ -106,34 +106,20 @@ impl Vecs {
) -> Result<Self> {
let v = version + VERSION;
let nvt = RatioPerBlock::forced_import_raw(db, "nvt", v, indexes)?;
let rsi = Windows::try_from_fn(|tf| RsiChain::forced_import(db, tf, v, indexes))?;
let macd = Windows::try_from_fn(|tf| MacdChain::forced_import(db, tf, v, indexes))?;
let stoch_k = PercentPerBlock::forced_import(db, "stoch_k", v, indexes)?;
let stoch_d = PercentPerBlock::forced_import(db, "stoch_d", v, indexes)?;
let gini = PercentPerBlock::forced_import(db, "gini", v, indexes)?;
let pi_cycle = RatioPerBlock::forced_import_raw(db, "pi_cycle", v, indexes)?;
let rhodl_ratio = RatioPerBlock::forced_import_raw(db, "rhodl_ratio", v, indexes)?;
Ok(Self {
puell_multiple: RatioPerBlock::forced_import_raw(
db,
"puell_multiple",
v,
indexes,
)?,
nvt,
rsi,
stoch_k,
stoch_d,
pi_cycle,
macd,
gini,
rhodl_ratio,
})
}
}

View File

@@ -1,5 +1,4 @@
mod compute;
mod gini;
mod import;
mod macd;
mod rsi;

View File

@@ -2,7 +2,7 @@ use brk_traversable::Traversable;
use brk_types::{BasisPoints16, BasisPoints32, StoredF32};
use vecdb::{Rw, StorageMode};
use crate::internal::{ComputedPerBlock, RatioPerBlock, PercentPerBlock, Windows};
use crate::internal::{ComputedPerBlock, PercentPerBlock, RatioPerBlock, Windows};
#[derive(Traversable)]
pub struct RsiChain<M: StorageMode = Rw> {
@@ -29,9 +29,6 @@ pub struct MacdChain<M: StorageMode = Rw> {
#[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw> {
pub puell_multiple: RatioPerBlock<BasisPoints32, M>,
pub nvt: RatioPerBlock<BasisPoints32, M>,
pub rsi: Windows<RsiChain<M>>,
pub stoch_k: PercentPerBlock<BasisPoints16, M>,
@@ -40,8 +37,4 @@ pub struct Vecs<M: StorageMode = Rw> {
pub pi_cycle: RatioPerBlock<BasisPoints32, M>,
pub macd: Windows<MacdChain<M>>,
pub gini: PercentPerBlock<BasisPoints16, M>,
pub rhodl_ratio: RatioPerBlock<BasisPoints32, M>,
}

View File

@@ -75,7 +75,7 @@ impl Vecs {
self.blocks_mined_sum.compute_rolling_sum(
starting_indexes.height,
&window_starts,
&self.base.blocks_mined.height,
&self.base.blocks_mined.raw.height,
exit,
)?;
@@ -101,7 +101,7 @@ impl Vecs {
|vec| {
Ok(vec.compute_transform2(
starting_indexes.height,
&self.base.blocks_mined.height,
&self.base.blocks_mined.raw.height,
&mining.rewards.coinbase.base.sats.height,
|(h, mask, val, ..)| (h, MaskSats::apply(mask, val)),
exit,

View File

@@ -38,14 +38,14 @@ impl Vecs {
) -> Result<()> {
self.taproot.compute_binary::<_, _, RatioU64Bp16>(
starting_indexes.height,
&count.p2tr.height,
&count.p2tr.raw.height,
&outputs_count.total_count.full.sum,
exit,
)?;
self.segwit.compute_binary::<_, _, RatioU64Bp16>(
starting_indexes.height,
&count.segwit.height,
&count.segwit.raw.height,
&outputs_count.total_count.full.sum,
exit,
)?;

View File

@@ -141,9 +141,9 @@ impl Vecs {
.compute(starting_indexes.height, &window_starts, exit, |v| {
Ok(v.compute_transform3(
starting_indexes.height,
&self.p2wpkh.height,
&self.p2wsh.height,
&self.p2tr.height,
&self.p2wpkh.raw.height,
&self.p2wsh.raw.height,
&self.p2tr.raw.height,
|(h, p2wpkh, p2wsh, p2tr, ..)| (h, StoredU64::from(*p2wpkh + *p2wsh + *p2tr)),
exit,
)?)

View File

@@ -71,7 +71,7 @@ impl Vecs {
.height
.compute_binary::<_, Timestamp, PerSec>(
starting_indexes.height,
&count_vecs.tx_count.height,
&count_vecs.tx_count.raw.height,
&blocks.interval.height,
exit,
)?;

View File

@@ -6,8 +6,6 @@ use crate::{Sats, Term, Timestamp};
pub struct Age {
/// Age in hours (primary internal unit for cohort boundaries)
hours: usize,
/// Age in blocks (for satblocks_destroyed calculation)
blocks: usize,
/// Age in days as float (for satdays_destroyed - established terminology)
days: f64,
}
@@ -15,10 +13,9 @@ pub struct Age {
impl Age {
/// Create from timestamps and block count
#[inline]
pub fn new(current_timestamp: Timestamp, prev_timestamp: Timestamp, blocks: usize) -> Self {
pub fn new(current_timestamp: Timestamp, prev_timestamp: Timestamp) -> Self {
Self {
hours: current_timestamp.difference_in_hours_between(prev_timestamp),
blocks,
days: current_timestamp.difference_in_days_between_float(prev_timestamp),
}
}
@@ -29,12 +26,6 @@ impl Age {
self.hours
}
/// Blocks old (for satblocks_destroyed calculation)
#[inline]
pub fn blocks(&self) -> usize {
self.blocks
}
/// Days old as float (for satdays_destroyed - established terminology)
#[inline]
pub fn days(&self) -> f64 {
@@ -51,21 +42,9 @@ impl Age {
}
}
/// Calculate satblocks destroyed for given supply
#[inline]
pub fn satblocks_destroyed(&self, supply: Sats) -> Sats {
if self.blocks == 0 {
return Sats::ZERO;
}
Sats::from(u64::from(supply) * self.blocks as u64)
}
/// Calculate satdays destroyed for given supply
#[inline]
pub fn satdays_destroyed(&self, supply: Sats) -> Sats {
if self.blocks == 0 {
return Sats::ZERO;
}
Sats::from((u64::from(supply) as f64 * self.days).floor() as u64)
}
}