mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-24 06:39:58 -07:00
global: snapshot
This commit is contained in:
@@ -5,10 +5,10 @@ use brk_cohort::{
|
||||
};
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Dollars, Height, Indexes, Sats, Version};
|
||||
use brk_types::{Height, Indexes, Version};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use rayon::prelude::*;
|
||||
use vecdb::{AnyStoredVec, Database, Exit, ReadableVec, Rw, StorageMode};
|
||||
use vecdb::{AnyStoredVec, Database, Exit, Rw, StorageMode};
|
||||
|
||||
use crate::{blocks, distribution::DynCohortVecs, indexes, prices};
|
||||
|
||||
@@ -111,29 +111,15 @@ impl AddressCohorts {
|
||||
}
|
||||
|
||||
/// Second phase of post-processing: compute relative metrics.
|
||||
pub(crate) fn compute_rest_part2<HM, AS>(
|
||||
pub(crate) fn compute_rest_part2(
|
||||
&mut self,
|
||||
blocks: &blocks::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
starting_indexes: &Indexes,
|
||||
height_to_market_cap: &HM,
|
||||
all_supply_sats: &AS,
|
||||
exit: &Exit,
|
||||
) -> Result<()>
|
||||
where
|
||||
HM: ReadableVec<Height, Dollars> + Sync,
|
||||
AS: ReadableVec<Height, Sats> + Sync,
|
||||
{
|
||||
self.0.par_iter_mut().try_for_each(|v| {
|
||||
v.compute_rest_part2(
|
||||
blocks,
|
||||
prices,
|
||||
starting_indexes,
|
||||
height_to_market_cap,
|
||||
all_supply_sats,
|
||||
exit,
|
||||
)
|
||||
})
|
||||
) -> Result<()> {
|
||||
self.0
|
||||
.par_iter_mut()
|
||||
.try_for_each(|v| v.compute_rest_part2(prices, starting_indexes, exit))
|
||||
}
|
||||
|
||||
/// Returns a parallel iterator over all vecs for parallel writing.
|
||||
|
||||
@@ -3,19 +3,19 @@ use std::path::Path;
|
||||
use brk_cohort::{CohortContext, Filter, Filtered};
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Cents, Dollars, Height, Indexes, Sats, StoredF64, StoredU64, Version};
|
||||
use brk_types::{Cents, Height, Indexes, StoredF64, StoredU64, Version};
|
||||
use rayon::prelude::*;
|
||||
use vecdb::{AnyStoredVec, AnyVec, Database, Exit, ReadableVec, Rw, StorageMode, WritableVec};
|
||||
|
||||
use crate::{
|
||||
blocks,
|
||||
distribution::state::{AddressCohortState, CoreRealizedState},
|
||||
distribution::state::{AddressCohortState, MinimalRealizedState},
|
||||
indexes,
|
||||
internal::ComputedFromHeight,
|
||||
prices,
|
||||
};
|
||||
|
||||
use crate::distribution::metrics::{CoreCohortMetrics, ImportConfig};
|
||||
use crate::distribution::metrics::{ImportConfig, MinimalCohortMetrics};
|
||||
|
||||
use super::super::traits::{CohortVecs, DynCohortVecs};
|
||||
#[derive(Traversable)]
|
||||
@@ -23,10 +23,10 @@ pub struct AddressCohortVecs<M: StorageMode = Rw> {
|
||||
starting_height: Option<Height>,
|
||||
|
||||
#[traversable(skip)]
|
||||
pub state: Option<Box<AddressCohortState<CoreRealizedState>>>,
|
||||
pub state: Option<Box<AddressCohortState<MinimalRealizedState>>>,
|
||||
|
||||
#[traversable(flatten)]
|
||||
pub metrics: CoreCohortMetrics<M>,
|
||||
pub metrics: MinimalCohortMetrics<M>,
|
||||
|
||||
pub addr_count: ComputedFromHeight<StoredU64, M>,
|
||||
pub addr_count_change_1m: ComputedFromHeight<StoredF64, M>,
|
||||
@@ -56,7 +56,7 @@ impl AddressCohortVecs {
|
||||
|
||||
state: states_path.map(|path| Box::new(AddressCohortState::new(path, &full_name))),
|
||||
|
||||
metrics: CoreCohortMetrics::forced_import(&cfg)?,
|
||||
metrics: MinimalCohortMetrics::forced_import(&cfg)?,
|
||||
|
||||
addr_count: ComputedFromHeight::forced_import(
|
||||
db,
|
||||
@@ -261,21 +261,11 @@ impl CohortVecs for AddressCohortVecs {
|
||||
|
||||
fn compute_rest_part2(
|
||||
&mut self,
|
||||
blocks: &blocks::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
starting_indexes: &Indexes,
|
||||
height_to_market_cap: &impl ReadableVec<Height, Dollars>,
|
||||
all_supply_sats: &impl ReadableVec<Height, Sats>,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.metrics.compute_rest_part2(
|
||||
blocks,
|
||||
prices,
|
||||
starting_indexes,
|
||||
height_to_market_cap,
|
||||
all_supply_sats,
|
||||
exit,
|
||||
)?;
|
||||
Ok(())
|
||||
self.metrics
|
||||
.compute_rest_part2(prices, starting_indexes, exit)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use brk_error::Result;
|
||||
use brk_types::{Cents, Dollars, Height, Indexes, Sats, Version};
|
||||
use vecdb::{Exit, ReadableVec};
|
||||
use brk_types::{Cents, Height, Indexes, Version};
|
||||
use vecdb::Exit;
|
||||
|
||||
use crate::{blocks, prices};
|
||||
|
||||
@@ -65,11 +65,8 @@ pub trait CohortVecs: DynCohortVecs {
|
||||
/// Second phase of post-processing computations.
|
||||
fn compute_rest_part2(
|
||||
&mut self,
|
||||
blocks: &blocks::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
starting_indexes: &Indexes,
|
||||
height_to_market_cap: &impl ReadableVec<Height, Dollars>,
|
||||
all_supply_sats: &impl ReadableVec<Height, Sats>,
|
||||
exit: &Exit,
|
||||
) -> Result<()>;
|
||||
}
|
||||
|
||||
@@ -13,14 +13,14 @@ use vecdb::{AnyStoredVec, Database, Exit, ReadOnlyClone, ReadableVec, Rw, Storag
|
||||
use crate::{blocks, distribution::DynCohortVecs, indexes, prices};
|
||||
|
||||
use crate::distribution::metrics::{
|
||||
AllCohortMetrics, BasicCohortMetrics, CohortMetricsBase, CompleteCohortMetrics,
|
||||
AllCohortMetrics, BasicCohortMetrics, CohortMetricsBase,
|
||||
CoreCohortMetrics, ExtendedAdjustedCohortMetrics, ExtendedCohortMetrics, ImportConfig,
|
||||
MinimalCohortMetrics, SupplyMetrics,
|
||||
};
|
||||
|
||||
use super::{percentiles::PercentileCache, vecs::UTXOCohortVecs};
|
||||
|
||||
use crate::distribution::state::{CoreRealizedState, RealizedState, UTXOCohortState};
|
||||
use crate::distribution::state::{CoreRealizedState, MinimalRealizedState, RealizedState, UTXOCohortState};
|
||||
|
||||
const VERSION: Version = Version::new(0);
|
||||
|
||||
@@ -40,14 +40,14 @@ pub struct UTXOCohorts<M: StorageMode = Rw> {
|
||||
pub sth: UTXOCohortVecs<ExtendedAdjustedCohortMetrics<M>, RealizedState>,
|
||||
pub lth: UTXOCohortVecs<ExtendedCohortMetrics<M>, RealizedState>,
|
||||
pub age_range: ByAgeRange<UTXOCohortVecs<BasicCohortMetrics<M>, RealizedState>>,
|
||||
pub max_age: ByMaxAge<UTXOCohortVecs<CompleteCohortMetrics<M>, RealizedState>>,
|
||||
pub min_age: ByMinAge<UTXOCohortVecs<CompleteCohortMetrics<M>, RealizedState>>,
|
||||
pub ge_amount: ByGreatEqualAmount<UTXOCohortVecs<CoreCohortMetrics<M>, CoreRealizedState>>,
|
||||
pub amount_range: ByAmountRange<UTXOCohortVecs<CoreCohortMetrics<M>, CoreRealizedState>>,
|
||||
pub lt_amount: ByLowerThanAmount<UTXOCohortVecs<CoreCohortMetrics<M>, CoreRealizedState>>,
|
||||
pub max_age: ByMaxAge<UTXOCohortVecs<CoreCohortMetrics<M>, CoreRealizedState>>,
|
||||
pub min_age: ByMinAge<UTXOCohortVecs<CoreCohortMetrics<M>, CoreRealizedState>>,
|
||||
pub ge_amount: ByGreatEqualAmount<UTXOCohortVecs<MinimalCohortMetrics<M>, MinimalRealizedState>>,
|
||||
pub amount_range: ByAmountRange<UTXOCohortVecs<MinimalCohortMetrics<M>, MinimalRealizedState>>,
|
||||
pub lt_amount: ByLowerThanAmount<UTXOCohortVecs<MinimalCohortMetrics<M>, MinimalRealizedState>>,
|
||||
pub epoch: ByEpoch<UTXOCohortVecs<CoreCohortMetrics<M>, CoreRealizedState>>,
|
||||
pub class: ByClass<UTXOCohortVecs<CoreCohortMetrics<M>, CoreRealizedState>>,
|
||||
pub type_: BySpendableType<UTXOCohortVecs<MinimalCohortMetrics<M>, CoreRealizedState>>,
|
||||
pub type_: BySpendableType<UTXOCohortVecs<MinimalCohortMetrics<M>, MinimalRealizedState>>,
|
||||
#[traversable(skip)]
|
||||
pub(super) percentile_cache: PercentileCache,
|
||||
/// Cached partition_point positions for tick_tock boundary searches.
|
||||
@@ -121,12 +121,12 @@ impl UTXOCohorts<Rw> {
|
||||
))
|
||||
};
|
||||
|
||||
let amount_range = ByAmountRange::try_new(&core_separate)?;
|
||||
let epoch = ByEpoch::try_new(&core_separate)?;
|
||||
let class = ByClass::try_new(&core_separate)?;
|
||||
|
||||
let type_ = BySpendableType::try_new(
|
||||
&|f: Filter, name: &'static str| -> Result<UTXOCohortVecs<MinimalCohortMetrics, CoreRealizedState>> {
|
||||
// Helper for separate cohorts with MinimalCohortMetrics + MinimalRealizedState
|
||||
let minimal_separate =
|
||||
|f: Filter, name: &'static str| -> Result<UTXOCohortVecs<MinimalCohortMetrics, MinimalRealizedState>> {
|
||||
let full_name = CohortContext::Utxo.full_name(&f, name);
|
||||
let cfg = ImportConfig {
|
||||
db,
|
||||
@@ -140,8 +140,10 @@ impl UTXOCohorts<Rw> {
|
||||
state,
|
||||
MinimalCohortMetrics::forced_import(&cfg)?,
|
||||
))
|
||||
},
|
||||
)?;
|
||||
};
|
||||
|
||||
let amount_range = ByAmountRange::try_new(&minimal_separate)?;
|
||||
let type_ = BySpendableType::try_new(&minimal_separate)?;
|
||||
|
||||
// Phase 3: Import "all" cohort with pre-imported supply.
|
||||
let all = UTXOCohortVecs::new(
|
||||
@@ -179,43 +181,7 @@ impl UTXOCohorts<Rw> {
|
||||
UTXOCohortVecs::new(None, ExtendedCohortMetrics::forced_import(&cfg)?)
|
||||
};
|
||||
|
||||
// max_age: CompleteCohortMetrics (no state, aggregates from age_range)
|
||||
let max_age = {
|
||||
ByMaxAge::try_new(&|f: Filter, name: &'static str| -> Result<UTXOCohortVecs<CompleteCohortMetrics, RealizedState>> {
|
||||
let full_name = CohortContext::Utxo.full_name(&f, name);
|
||||
let cfg = ImportConfig {
|
||||
db,
|
||||
filter: &f,
|
||||
full_name: &full_name,
|
||||
version: v,
|
||||
indexes,
|
||||
};
|
||||
Ok(UTXOCohortVecs::new(
|
||||
None,
|
||||
CompleteCohortMetrics::forced_import(&cfg)?,
|
||||
))
|
||||
})?
|
||||
};
|
||||
|
||||
// min_age: CompleteCohortMetrics
|
||||
let min_age = {
|
||||
ByMinAge::try_new(&|f: Filter, name: &'static str| -> Result<UTXOCohortVecs<CompleteCohortMetrics, RealizedState>> {
|
||||
let full_name = CohortContext::Utxo.full_name(&f, name);
|
||||
let cfg = ImportConfig {
|
||||
db,
|
||||
filter: &f,
|
||||
full_name: &full_name,
|
||||
version: v,
|
||||
indexes,
|
||||
};
|
||||
Ok(UTXOCohortVecs::new(
|
||||
None,
|
||||
CompleteCohortMetrics::forced_import(&cfg)?,
|
||||
))
|
||||
})?
|
||||
};
|
||||
|
||||
// ge_amount, lt_amount: CoreCohortMetrics (no state)
|
||||
// CoreCohortMetrics without state (no state, for aggregate cohorts)
|
||||
let core_no_state =
|
||||
|f: Filter, name: &'static str| -> Result<UTXOCohortVecs<CoreCohortMetrics, CoreRealizedState>> {
|
||||
let full_name = CohortContext::Utxo.full_name(&f, name);
|
||||
@@ -232,8 +198,31 @@ impl UTXOCohorts<Rw> {
|
||||
))
|
||||
};
|
||||
|
||||
let lt_amount = ByLowerThanAmount::try_new(&core_no_state)?;
|
||||
let ge_amount = ByGreatEqualAmount::try_new(&core_no_state)?;
|
||||
// max_age: CoreCohortMetrics (no state, aggregates from age_range)
|
||||
let max_age = ByMaxAge::try_new(&core_no_state)?;
|
||||
|
||||
// min_age: CoreCohortMetrics (no state, aggregates from age_range)
|
||||
let min_age = ByMinAge::try_new(&core_no_state)?;
|
||||
|
||||
// MinimalCohortMetrics without state (for aggregate amount cohorts)
|
||||
let minimal_no_state =
|
||||
|f: Filter, name: &'static str| -> Result<UTXOCohortVecs<MinimalCohortMetrics, MinimalRealizedState>> {
|
||||
let full_name = CohortContext::Utxo.full_name(&f, name);
|
||||
let cfg = ImportConfig {
|
||||
db,
|
||||
filter: &f,
|
||||
full_name: &full_name,
|
||||
version: v,
|
||||
indexes,
|
||||
};
|
||||
Ok(UTXOCohortVecs::new(
|
||||
None,
|
||||
MinimalCohortMetrics::forced_import(&cfg)?,
|
||||
))
|
||||
};
|
||||
|
||||
let lt_amount = ByLowerThanAmount::try_new(&minimal_no_state)?;
|
||||
let ge_amount = ByGreatEqualAmount::try_new(&minimal_no_state)?;
|
||||
|
||||
Ok(Self {
|
||||
all,
|
||||
@@ -314,18 +303,18 @@ impl UTXOCohorts<Rw> {
|
||||
Box::new(|| {
|
||||
min_age.par_iter_mut().try_for_each(|vecs| {
|
||||
let sources = filter_sources_from(ar.iter(), Some(&vecs.metrics.filter));
|
||||
vecs.metrics.compute_from_sources(si, &sources, exit)
|
||||
vecs.metrics.compute_from_base_sources(si, &sources, exit)
|
||||
})
|
||||
}),
|
||||
Box::new(|| {
|
||||
max_age.par_iter_mut().try_for_each(|vecs| {
|
||||
let sources = filter_sources_from(ar.iter(), Some(&vecs.metrics.filter));
|
||||
vecs.metrics.compute_from_sources(si, &sources, exit)
|
||||
vecs.metrics.compute_from_base_sources(si, &sources, exit)
|
||||
})
|
||||
}),
|
||||
Box::new(|| {
|
||||
ge_amount.par_iter_mut().chain(lt_amount.par_iter_mut()).try_for_each(|vecs| {
|
||||
let sources = filter_core_sources_from(amr.iter(), Some(&vecs.metrics.filter));
|
||||
let sources = filter_minimal_sources_from(amr.iter(), Some(&vecs.metrics.filter));
|
||||
vecs.metrics.compute_from_sources(si, &sources, exit)
|
||||
})
|
||||
}),
|
||||
@@ -384,8 +373,8 @@ impl UTXOCohorts<Rw> {
|
||||
|
||||
// 2. Compute net_sentiment.height for aggregate cohorts (weighted average).
|
||||
// Separate cohorts already computed net_sentiment in step 1 (inside compute_rest_part1).
|
||||
// Note: min_age, max_age, epoch, class are Complete tier — no net_sentiment.
|
||||
// Note: ge_amount, lt_amount, amount_range are Core tier — no net_sentiment.
|
||||
// Note: min_age, max_age, epoch, class are Core tier — no net_sentiment.
|
||||
// Note: ge_amount, lt_amount, amount_range are Minimal tier — no net_sentiment.
|
||||
{
|
||||
let Self {
|
||||
all, sth, lth, age_range,
|
||||
@@ -481,11 +470,11 @@ impl UTXOCohorts<Rw> {
|
||||
Box::new(|| age_range.par_iter_mut().try_for_each(|v| v.metrics.compute_rest_part2(blocks, prices, starting_indexes, height_to_market_cap, ss, exit))),
|
||||
Box::new(|| max_age.par_iter_mut().try_for_each(|v| v.metrics.compute_rest_part2(blocks, prices, starting_indexes, height_to_market_cap, ss, exit))),
|
||||
Box::new(|| min_age.par_iter_mut().try_for_each(|v| v.metrics.compute_rest_part2(blocks, prices, starting_indexes, height_to_market_cap, ss, exit))),
|
||||
Box::new(|| ge_amount.par_iter_mut().try_for_each(|v| v.metrics.compute_rest_part2(blocks, prices, starting_indexes, height_to_market_cap, ss, exit))),
|
||||
Box::new(|| ge_amount.par_iter_mut().try_for_each(|v| v.metrics.compute_rest_part2(prices, starting_indexes, exit))),
|
||||
Box::new(|| epoch.par_iter_mut().try_for_each(|v| v.metrics.compute_rest_part2(blocks, prices, starting_indexes, height_to_market_cap, ss, exit))),
|
||||
Box::new(|| class.par_iter_mut().try_for_each(|v| v.metrics.compute_rest_part2(blocks, prices, starting_indexes, height_to_market_cap, ss, exit))),
|
||||
Box::new(|| amount_range.par_iter_mut().try_for_each(|v| v.metrics.compute_rest_part2(blocks, prices, starting_indexes, height_to_market_cap, ss, exit))),
|
||||
Box::new(|| lt_amount.par_iter_mut().try_for_each(|v| v.metrics.compute_rest_part2(blocks, prices, starting_indexes, height_to_market_cap, ss, exit))),
|
||||
Box::new(|| amount_range.par_iter_mut().try_for_each(|v| v.metrics.compute_rest_part2(prices, starting_indexes, exit))),
|
||||
Box::new(|| lt_amount.par_iter_mut().try_for_each(|v| v.metrics.compute_rest_part2(prices, starting_indexes, exit))),
|
||||
Box::new(|| type_.par_iter_mut().try_for_each(|v| v.metrics.compute_rest_part2(prices, starting_indexes, exit))),
|
||||
];
|
||||
|
||||
@@ -612,11 +601,11 @@ fn filter_sources_from<'a, M: CohortMetricsBase + 'a>(
|
||||
}
|
||||
}
|
||||
|
||||
/// Filter CoreCohortMetrics source cohorts by an optional filter.
|
||||
fn filter_core_sources_from<'a>(
|
||||
sources: impl Iterator<Item = &'a UTXOCohortVecs<CoreCohortMetrics, CoreRealizedState>>,
|
||||
/// Filter MinimalCohortMetrics source cohorts by an optional filter.
|
||||
fn filter_minimal_sources_from<'a>(
|
||||
sources: impl Iterator<Item = &'a UTXOCohortVecs<MinimalCohortMetrics, MinimalRealizedState>>,
|
||||
filter: Option<&Filter>,
|
||||
) -> Vec<&'a CoreCohortMetrics> {
|
||||
) -> Vec<&'a MinimalCohortMetrics> {
|
||||
match filter {
|
||||
Some(f) => sources
|
||||
.filter(|v| f.includes(&v.metrics.filter))
|
||||
|
||||
@@ -7,9 +7,9 @@ use vecdb::{Exit, ReadableVec};
|
||||
use crate::{blocks, distribution::state::UTXOCohortState, prices};
|
||||
|
||||
use crate::distribution::metrics::{
|
||||
CohortMetricsBase, CompleteCohortMetrics, CoreCohortMetrics, MinimalCohortMetrics,
|
||||
CohortMetricsBase, CoreCohortMetrics, MinimalCohortMetrics,
|
||||
};
|
||||
use crate::distribution::state::{CoreRealizedState, RealizedOps, RealizedState};
|
||||
use crate::distribution::state::{CoreRealizedState, MinimalRealizedState, RealizedOps, RealizedState};
|
||||
|
||||
use super::super::traits::DynCohortVecs;
|
||||
|
||||
@@ -225,15 +225,15 @@ macro_rules! impl_import_state {
|
||||
};
|
||||
}
|
||||
|
||||
// --- MinimalCohortMetrics: uses CoreRealizedState ---
|
||||
// --- MinimalCohortMetrics: uses MinimalRealizedState ---
|
||||
|
||||
impl Filtered for UTXOCohortVecs<MinimalCohortMetrics, CoreRealizedState> {
|
||||
impl Filtered for UTXOCohortVecs<MinimalCohortMetrics, MinimalRealizedState> {
|
||||
fn filter(&self) -> &Filter {
|
||||
&self.metrics.filter
|
||||
}
|
||||
}
|
||||
|
||||
impl DynCohortVecs for UTXOCohortVecs<MinimalCohortMetrics, CoreRealizedState> {
|
||||
impl DynCohortVecs for UTXOCohortVecs<MinimalCohortMetrics, MinimalRealizedState> {
|
||||
fn min_stateful_height_len(&self) -> usize {
|
||||
self.metrics.min_stateful_height_len()
|
||||
}
|
||||
@@ -260,9 +260,10 @@ impl DynCohortVecs for UTXOCohortVecs<MinimalCohortMetrics, CoreRealizedState> {
|
||||
self.metrics
|
||||
.outputs
|
||||
.truncate_push(height, state.supply.utxo_count)?;
|
||||
self.metrics.activity.truncate_push(height, state.sent)?;
|
||||
self.metrics
|
||||
.realized
|
||||
.truncate_push(height, state.realized.cap())?;
|
||||
.truncate_push(height, &state.realized)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -392,94 +393,3 @@ impl DynCohortVecs for UTXOCohortVecs<CoreCohortMetrics, CoreRealizedState> {
|
||||
}
|
||||
}
|
||||
|
||||
// --- CompleteCohortMetrics: uses full RealizedState ---
|
||||
|
||||
impl Filtered for UTXOCohortVecs<CompleteCohortMetrics, RealizedState> {
|
||||
fn filter(&self) -> &Filter {
|
||||
&self.metrics.filter
|
||||
}
|
||||
}
|
||||
|
||||
impl DynCohortVecs for UTXOCohortVecs<CompleteCohortMetrics, RealizedState> {
|
||||
fn min_stateful_height_len(&self) -> usize {
|
||||
self.metrics.min_stateful_height_len()
|
||||
}
|
||||
|
||||
fn reset_state_starting_height(&mut self) {
|
||||
self.reset_state_impl();
|
||||
}
|
||||
|
||||
impl_import_state!();
|
||||
|
||||
fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> {
|
||||
self.metrics.validate_computed_versions(base_version)
|
||||
}
|
||||
|
||||
fn truncate_push(&mut self, height: Height) -> Result<()> {
|
||||
if self.state_starting_height.is_some_and(|h| h > height) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some(state) = self.state.as_ref() {
|
||||
self.metrics
|
||||
.supply
|
||||
.truncate_push(height, state.supply.value)?;
|
||||
self.metrics
|
||||
.outputs
|
||||
.truncate_push(height, state.supply.utxo_count)?;
|
||||
self.metrics.activity.truncate_push(
|
||||
height,
|
||||
state.sent,
|
||||
state.satblocks_destroyed,
|
||||
state.satdays_destroyed,
|
||||
)?;
|
||||
self.metrics
|
||||
.realized
|
||||
.truncate_push(height, &state.realized)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compute_then_truncate_push_unrealized_states(
|
||||
&mut self,
|
||||
height: Height,
|
||||
height_price: Cents,
|
||||
_is_day_boundary: bool,
|
||||
) -> Result<()> {
|
||||
if let Some(state) = self.state.as_mut() {
|
||||
state.apply_pending();
|
||||
self.metrics
|
||||
.cost_basis
|
||||
.truncate_push_minmax(height, state)?;
|
||||
let unrealized_state = state.compute_unrealized_state(height_price);
|
||||
self.metrics
|
||||
.unrealized
|
||||
.truncate_push(height, &unrealized_state)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compute_rest_part1(
|
||||
&mut self,
|
||||
blocks: &blocks::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
starting_indexes: &Indexes,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.metrics
|
||||
.compute_rest_part1(blocks, prices, starting_indexes, exit)
|
||||
}
|
||||
|
||||
fn write_state(&mut self, height: Height, cleanup: bool) -> Result<()> {
|
||||
self.write_state_impl(height, cleanup)
|
||||
}
|
||||
|
||||
fn reset_cost_basis_data_if_needed(&mut self) -> Result<()> {
|
||||
self.reset_cost_basis_impl()
|
||||
}
|
||||
|
||||
fn reset_single_iteration_values(&mut self) {
|
||||
self.reset_iteration_impl();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,14 +70,7 @@ where
|
||||
exit,
|
||||
)?;
|
||||
|
||||
address_cohorts.compute_rest_part2(
|
||||
blocks,
|
||||
prices,
|
||||
starting_indexes,
|
||||
height_to_market_cap,
|
||||
&utxo_cohorts.all.metrics.supply.total.sats.height,
|
||||
exit,
|
||||
)?;
|
||||
address_cohorts.compute_rest_part2(prices, starting_indexes, exit)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
use brk_cohort::Filter;
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Cents, Dollars, Height, Indexes};
|
||||
use brk_types::{Bitcoin, Cents, Dollars, Height, Indexes, StoredF32, Version};
|
||||
use vecdb::{Exit, ReadableVec, Rw, StorageMode};
|
||||
|
||||
use crate::{blocks, prices};
|
||||
|
||||
use crate::internal::ComputedFromHeight;
|
||||
|
||||
use crate::distribution::metrics::{
|
||||
ActivityMetrics, CostBasisWithExtended, ImportConfig, OutputsMetrics, RealizedAdjusted,
|
||||
RealizedWithExtended, RelativeForAll, SupplyMetrics, UnrealizedFull,
|
||||
@@ -26,6 +28,8 @@ pub struct AllCohortMetrics<M: StorageMode = Rw> {
|
||||
pub unrealized: Box<UnrealizedFull<M>>,
|
||||
pub adjusted: Box<RealizedAdjusted<M>>,
|
||||
pub relative: Box<RelativeForAll<M>>,
|
||||
pub dormancy: ComputedFromHeight<StoredF32, M>,
|
||||
pub velocity: ComputedFromHeight<StoredF32, M>,
|
||||
}
|
||||
|
||||
impl_cohort_metrics_base!(AllCohortMetrics, extended_cost_basis);
|
||||
@@ -55,6 +59,8 @@ impl AllCohortMetrics {
|
||||
unrealized: Box::new(unrealized),
|
||||
adjusted: Box::new(adjusted),
|
||||
relative: Box::new(relative),
|
||||
dormancy: cfg.import_computed("dormancy", Version::ONE)?,
|
||||
velocity: cfg.import_computed("velocity", Version::ONE)?,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -97,6 +103,36 @@ impl AllCohortMetrics {
|
||||
exit,
|
||||
)?;
|
||||
|
||||
self.dormancy.height.compute_transform2(
|
||||
starting_indexes.height,
|
||||
&self.activity.coindays_destroyed.height,
|
||||
&self.activity.sent.base.sats.height,
|
||||
|(i, cdd, sent_sats, ..)| {
|
||||
let sent_btc = f64::from(Bitcoin::from(sent_sats));
|
||||
if sent_btc == 0.0 {
|
||||
(i, StoredF32::from(0.0f32))
|
||||
} else {
|
||||
(i, StoredF32::from((f64::from(cdd) / sent_btc) as f32))
|
||||
}
|
||||
},
|
||||
exit,
|
||||
)?;
|
||||
|
||||
self.velocity.height.compute_transform2(
|
||||
starting_indexes.height,
|
||||
&self.activity.sent.base.sats.height,
|
||||
&self.supply.total.sats.height,
|
||||
|(i, sent_sats, supply_sats, ..)| {
|
||||
let supply = supply_sats.as_u128() as f64;
|
||||
if supply == 0.0 {
|
||||
(i, StoredF32::from(0.0f32))
|
||||
} else {
|
||||
(i, StoredF32::from((sent_sats.as_u128() as f64 / supply) as f32))
|
||||
}
|
||||
},
|
||||
exit,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,196 +0,0 @@
|
||||
use brk_cohort::Filter;
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Dollars, Height, Indexes, Sats, Version};
|
||||
use vecdb::{AnyStoredVec, Exit, ReadableVec, Rw, StorageMode};
|
||||
|
||||
use crate::{blocks, prices};
|
||||
|
||||
use crate::distribution::metrics::{
|
||||
ActivityMetrics, CohortMetricsBase, CostBasisBase, ImportConfig, OutputsMetrics,
|
||||
RealizedComplete, RelativeCompleteWithRelToAll, SupplyMetrics, UnrealizedComplete,
|
||||
};
|
||||
|
||||
/// Complete cohort metrics (Tier C): ~216 stored vecs.
|
||||
///
|
||||
/// Used for epoch, class, min_age, max_age cohorts.
|
||||
/// Everything in Core, plus cost basis, CDD, value created/destroyed,
|
||||
/// sent in profit/loss, net PnL change, etc.
|
||||
///
|
||||
/// Does NOT include source-only fields (peak_regret, invested_capital,
|
||||
/// raw BytesVecs) or extended-only fields (investor_price, sell_side_risk,
|
||||
/// pain/greed/net_sentiment).
|
||||
///
|
||||
/// Does NOT implement CohortMetricsBase — standalone, not usable as Source.
|
||||
#[derive(Traversable)]
|
||||
pub struct CompleteCohortMetrics<M: StorageMode = Rw> {
|
||||
#[traversable(skip)]
|
||||
pub filter: Filter,
|
||||
pub supply: Box<SupplyMetrics<M>>,
|
||||
pub outputs: Box<OutputsMetrics<M>>,
|
||||
pub activity: Box<ActivityMetrics<M>>,
|
||||
pub realized: Box<RealizedComplete<M>>,
|
||||
pub cost_basis: Box<CostBasisBase<M>>,
|
||||
pub unrealized: Box<UnrealizedComplete<M>>,
|
||||
pub relative: Box<RelativeCompleteWithRelToAll<M>>,
|
||||
}
|
||||
|
||||
impl CompleteCohortMetrics {
|
||||
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
|
||||
Ok(Self {
|
||||
filter: cfg.filter.clone(),
|
||||
supply: Box::new(SupplyMetrics::forced_import(cfg)?),
|
||||
outputs: Box::new(OutputsMetrics::forced_import(cfg)?),
|
||||
activity: Box::new(ActivityMetrics::forced_import(cfg)?),
|
||||
realized: Box::new(RealizedComplete::forced_import(cfg)?),
|
||||
cost_basis: Box::new(CostBasisBase::forced_import(cfg)?),
|
||||
unrealized: Box::new(UnrealizedComplete::forced_import(cfg)?),
|
||||
relative: Box::new(RelativeCompleteWithRelToAll::forced_import(cfg)?),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn min_stateful_height_len(&self) -> usize {
|
||||
self.supply
|
||||
.min_len()
|
||||
.min(self.outputs.min_len())
|
||||
.min(self.activity.min_len())
|
||||
.min(self.realized.min_stateful_height_len())
|
||||
.min(self.unrealized.min_stateful_height_len())
|
||||
.min(self.cost_basis.min_stateful_height_len())
|
||||
}
|
||||
|
||||
pub(crate) fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> {
|
||||
self.supply.validate_computed_versions(base_version)?;
|
||||
self.activity.validate_computed_versions(base_version)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn collect_all_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
|
||||
let mut vecs: Vec<&mut dyn AnyStoredVec> = Vec::new();
|
||||
vecs.extend(self.supply.collect_vecs_mut());
|
||||
vecs.extend(self.outputs.collect_vecs_mut());
|
||||
vecs.extend(self.activity.collect_vecs_mut());
|
||||
vecs.extend(self.realized.collect_vecs_mut());
|
||||
vecs.extend(self.cost_basis.collect_vecs_mut());
|
||||
vecs.extend(self.unrealized.collect_vecs_mut());
|
||||
vecs
|
||||
}
|
||||
|
||||
/// Aggregate Complete-tier metrics from Source cohort refs.
|
||||
pub(crate) fn compute_from_sources<T: CohortMetricsBase>(
|
||||
&mut self,
|
||||
starting_indexes: &Indexes,
|
||||
others: &[&T],
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
// Supply, outputs, activity: use their existing compute_from_stateful
|
||||
self.supply.compute_from_stateful(
|
||||
starting_indexes,
|
||||
&others.iter().map(|v| v.supply()).collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
self.outputs.compute_from_stateful(
|
||||
starting_indexes,
|
||||
&others.iter().map(|v| v.outputs()).collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
self.activity.compute_from_stateful(
|
||||
starting_indexes,
|
||||
&others.iter().map(|v| v.activity()).collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
|
||||
// Realized: aggregate only Complete-tier fields from Source's RealizedFull
|
||||
let realized_complete_refs: Vec<&RealizedComplete> = others
|
||||
.iter()
|
||||
.map(|v| &v.realized_full().complete)
|
||||
.collect();
|
||||
self.realized
|
||||
.compute_from_stateful(starting_indexes, &realized_complete_refs, exit)?;
|
||||
|
||||
// Unrealized: aggregate only Complete-tier fields
|
||||
let unrealized_complete_refs: Vec<&UnrealizedComplete> = others
|
||||
.iter()
|
||||
.map(|v| &v.unrealized_full().complete)
|
||||
.collect();
|
||||
self.unrealized
|
||||
.compute_from_stateful(starting_indexes, &unrealized_complete_refs, exit)?;
|
||||
|
||||
// Cost basis: use existing aggregation
|
||||
self.cost_basis.compute_from_stateful(
|
||||
starting_indexes,
|
||||
&others
|
||||
.iter()
|
||||
.map(|v| v.cost_basis_base())
|
||||
.collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// First phase: compute index transforms.
|
||||
pub(crate) fn compute_rest_part1(
|
||||
&mut self,
|
||||
blocks: &blocks::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
starting_indexes: &Indexes,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.supply
|
||||
.compute(prices, starting_indexes.height, exit)?;
|
||||
self.supply
|
||||
.compute_rest_part1(blocks, starting_indexes, exit)?;
|
||||
self.outputs
|
||||
.compute_rest(blocks, starting_indexes, exit)?;
|
||||
self.activity
|
||||
.sent
|
||||
.compute(prices, starting_indexes.height, exit)?;
|
||||
self.activity
|
||||
.compute_rest_part1(blocks, prices, starting_indexes, exit)?;
|
||||
|
||||
self.realized
|
||||
.sent_in_profit
|
||||
.compute(prices, starting_indexes.height, exit)?;
|
||||
self.realized
|
||||
.sent_in_loss
|
||||
.compute(prices, starting_indexes.height, exit)?;
|
||||
self.realized
|
||||
.compute_rest_part1(starting_indexes, exit)?;
|
||||
|
||||
self.unrealized.compute_rest(starting_indexes, exit)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Second phase: compute relative metrics and remaining.
|
||||
pub(crate) fn compute_rest_part2(
|
||||
&mut self,
|
||||
blocks: &blocks::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
starting_indexes: &Indexes,
|
||||
height_to_market_cap: &impl ReadableVec<Height, Dollars>,
|
||||
all_supply_sats: &impl ReadableVec<Height, Sats>,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.realized.compute_rest_part2(
|
||||
blocks,
|
||||
prices,
|
||||
starting_indexes,
|
||||
&self.supply.total.btc.height,
|
||||
height_to_market_cap,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
self.relative.compute(
|
||||
starting_indexes.height,
|
||||
&self.unrealized,
|
||||
&self.supply.total.sats.height,
|
||||
height_to_market_cap,
|
||||
all_supply_sats,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ use vecdb::{AnyStoredVec, Exit, ReadableVec, Rw, StorageMode};
|
||||
use crate::{blocks, prices};
|
||||
|
||||
use crate::distribution::metrics::{
|
||||
ActivityCore, RealizedCore, ImportConfig, OutputsMetrics,
|
||||
ActivityCore, CohortMetricsBase, RealizedCore, ImportConfig, OutputsMetrics,
|
||||
RelativeCompleteWithRelToAll, SupplyMetrics, UnrealizedComplete,
|
||||
};
|
||||
|
||||
@@ -61,35 +61,36 @@ impl CoreCohortMetrics {
|
||||
vecs
|
||||
}
|
||||
|
||||
pub(crate) fn compute_from_sources(
|
||||
/// Aggregate Core-tier fields from CohortMetricsBase sources (e.g. age_range → max_age/min_age).
|
||||
pub(crate) fn compute_from_base_sources<T: CohortMetricsBase>(
|
||||
&mut self,
|
||||
starting_indexes: &Indexes,
|
||||
others: &[&CoreCohortMetrics],
|
||||
others: &[&T],
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.supply.compute_from_stateful(
|
||||
starting_indexes,
|
||||
&others.iter().map(|v| v.supply.as_ref()).collect::<Vec<_>>(),
|
||||
&others.iter().map(|v| v.supply()).collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
self.outputs.compute_from_stateful(
|
||||
starting_indexes,
|
||||
&others.iter().map(|v| v.outputs.as_ref()).collect::<Vec<_>>(),
|
||||
&others.iter().map(|v| v.outputs()).collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
self.activity.compute_from_stateful(
|
||||
starting_indexes,
|
||||
&others.iter().map(|v| v.activity.as_ref()).collect::<Vec<_>>(),
|
||||
&others.iter().map(|v| &v.activity().core).collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
self.realized.compute_from_stateful(
|
||||
starting_indexes,
|
||||
&others.iter().map(|v| v.realized.as_ref()).collect::<Vec<_>>(),
|
||||
&others.iter().map(|v| &v.realized_full().core).collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
self.unrealized.compute_from_stateful(
|
||||
starting_indexes,
|
||||
&others.iter().map(|v| v.unrealized.as_ref()).collect::<Vec<_>>(),
|
||||
&others.iter().map(|v| &v.unrealized_full().complete).collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
use brk_cohort::Filter;
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Dollars, Height, Indexes, Sats};
|
||||
use brk_types::{Bitcoin, Dollars, Height, Indexes, Sats, StoredF32, Version};
|
||||
use vecdb::{Exit, ReadableVec, Rw, StorageMode};
|
||||
|
||||
use crate::{blocks, prices};
|
||||
|
||||
use crate::internal::ComputedFromHeight;
|
||||
|
||||
use crate::distribution::metrics::{
|
||||
ActivityMetrics, CostBasisWithExtended, ImportConfig, OutputsMetrics,
|
||||
RealizedWithExtended, RelativeWithExtended, SupplyMetrics, UnrealizedFull,
|
||||
@@ -24,6 +26,8 @@ pub struct ExtendedCohortMetrics<M: StorageMode = Rw> {
|
||||
pub cost_basis: Box<CostBasisWithExtended<M>>,
|
||||
pub unrealized: Box<UnrealizedFull<M>>,
|
||||
pub relative: Box<RelativeWithExtended<M>>,
|
||||
pub dormancy: ComputedFromHeight<StoredF32, M>,
|
||||
pub velocity: ComputedFromHeight<StoredF32, M>,
|
||||
}
|
||||
|
||||
impl_cohort_metrics_base!(ExtendedCohortMetrics, extended_cost_basis);
|
||||
@@ -45,6 +49,8 @@ impl ExtendedCohortMetrics {
|
||||
cost_basis: Box::new(CostBasisWithExtended::forced_import(cfg)?),
|
||||
unrealized: Box::new(unrealized),
|
||||
relative: Box::new(relative),
|
||||
dormancy: cfg.import_computed("dormancy", Version::ONE)?,
|
||||
velocity: cfg.import_computed("velocity", Version::ONE)?,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -77,6 +83,36 @@ impl ExtendedCohortMetrics {
|
||||
exit,
|
||||
)?;
|
||||
|
||||
self.dormancy.height.compute_transform2(
|
||||
starting_indexes.height,
|
||||
&self.activity.coindays_destroyed.height,
|
||||
&self.activity.sent.base.sats.height,
|
||||
|(i, cdd, sent_sats, ..)| {
|
||||
let sent_btc = f64::from(Bitcoin::from(sent_sats));
|
||||
if sent_btc == 0.0 {
|
||||
(i, StoredF32::from(0.0f32))
|
||||
} else {
|
||||
(i, StoredF32::from((f64::from(cdd) / sent_btc) as f32))
|
||||
}
|
||||
},
|
||||
exit,
|
||||
)?;
|
||||
|
||||
self.velocity.height.compute_transform2(
|
||||
starting_indexes.height,
|
||||
&self.activity.sent.base.sats.height,
|
||||
&self.supply.total.sats.height,
|
||||
|(i, sent_sats, supply_sats, ..)| {
|
||||
let supply = supply_sats.as_u128() as f64;
|
||||
if supply == 0.0 {
|
||||
(i, StoredF32::from(0.0f32))
|
||||
} else {
|
||||
(i, StoredF32::from((sent_sats.as_u128() as f64 / supply) as f32))
|
||||
}
|
||||
},
|
||||
exit,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,19 +10,22 @@ use vecdb::{AnyStoredVec, AnyVec, Exit, ReadableCloneableVec, ReadableVec, Rw, S
|
||||
use crate::{blocks, prices};
|
||||
|
||||
use crate::internal::{
|
||||
CentsUnsignedToDollars, ComputedFromHeight, ComputedFromHeightRatio, Identity, LazyFromHeight,
|
||||
PercentFromHeight, Price, RatioSatsBp16, ValueFromHeight,
|
||||
CentsUnsignedToDollars, ComputedFromHeight, ComputedFromHeightCumulative,
|
||||
ComputedFromHeightRatio, Identity, LazyFromHeight, PercentFromHeight, Price, RatioSatsBp16,
|
||||
ValueFromHeight,
|
||||
};
|
||||
|
||||
use crate::distribution::{
|
||||
metrics::{ImportConfig, OutputsMetrics, SupplyMetrics},
|
||||
state::UnrealizedState,
|
||||
metrics::{ActivityCore, ImportConfig, OutputsMetrics, SupplyMetrics},
|
||||
state::{RealizedOps, UnrealizedState},
|
||||
};
|
||||
|
||||
/// Minimal realized metrics: realized cap, realized price, and MVRV ratio.
|
||||
/// Minimal realized metrics: realized cap, realized price, MVRV, and realized P/L.
|
||||
#[derive(Traversable)]
|
||||
pub struct MinimalRealized<M: StorageMode = Rw> {
|
||||
pub realized_cap_cents: ComputedFromHeight<Cents, M>,
|
||||
pub realized_profit: ComputedFromHeightCumulative<Cents, M>,
|
||||
pub realized_loss: ComputedFromHeightCumulative<Cents, M>,
|
||||
pub realized_cap: LazyFromHeight<Dollars, Cents>,
|
||||
pub realized_price: Price<ComputedFromHeight<Cents, M>>,
|
||||
pub realized_price_ratio: ComputedFromHeightRatio<M>,
|
||||
@@ -43,16 +46,18 @@ pub struct MinimalRelative<M: StorageMode = Rw> {
|
||||
pub supply_in_loss_rel_to_own_supply: PercentFromHeight<BasisPoints16, M>,
|
||||
}
|
||||
|
||||
/// MinimalCohortMetrics (Tier A): ~15 stored vecs.
|
||||
/// MinimalCohortMetrics: supply, outputs, sent+ema, realized cap/price/mvrv/profit/loss,
|
||||
/// supply in profit/loss, relative to own supply.
|
||||
///
|
||||
/// Used for type_ cohorts where most metrics are irrelevant.
|
||||
/// Does NOT implement CohortMetricsBase — standalone, not aggregatable.
|
||||
/// Used for type_, amount, and address cohorts.
|
||||
/// Does NOT implement CohortMetricsBase — standalone, not aggregatable via trait.
|
||||
#[derive(Traversable)]
|
||||
pub struct MinimalCohortMetrics<M: StorageMode = Rw> {
|
||||
#[traversable(skip)]
|
||||
pub filter: Filter,
|
||||
pub supply: Box<SupplyMetrics<M>>,
|
||||
pub outputs: Box<OutputsMetrics<M>>,
|
||||
pub activity: Box<ActivityCore<M>>,
|
||||
pub realized: Box<MinimalRealized<M>>,
|
||||
pub unrealized: Box<MinimalUnrealized<M>>,
|
||||
pub relative: Box<MinimalRelative<M>>,
|
||||
@@ -68,6 +73,9 @@ impl MinimalRealized {
|
||||
&realized_cap_cents,
|
||||
);
|
||||
|
||||
let realized_profit = cfg.import_cumulative("realized_profit", Version::ZERO)?;
|
||||
let realized_loss = cfg.import_cumulative("realized_loss", Version::ZERO)?;
|
||||
|
||||
let realized_price = cfg.import_price("realized_price", Version::ONE)?;
|
||||
let realized_price_ratio = cfg.import_ratio("realized_price", Version::ONE)?;
|
||||
let mvrv = LazyFromHeight::from_lazy::<Identity<StoredF32>, BasisPoints32>(
|
||||
@@ -78,6 +86,8 @@ impl MinimalRealized {
|
||||
|
||||
Ok(Self {
|
||||
realized_cap_cents,
|
||||
realized_profit,
|
||||
realized_loss,
|
||||
realized_cap,
|
||||
realized_price,
|
||||
realized_price_ratio,
|
||||
@@ -86,18 +96,60 @@ impl MinimalRealized {
|
||||
}
|
||||
|
||||
pub(crate) fn min_stateful_height_len(&self) -> usize {
|
||||
self.realized_cap_cents.height.len()
|
||||
}
|
||||
|
||||
pub(crate) fn truncate_push(&mut self, height: Height, cap: Cents) -> Result<()> {
|
||||
self.realized_cap_cents
|
||||
.height
|
||||
.truncate_push(height, cap)?;
|
||||
.len()
|
||||
.min(self.realized_profit.height.len())
|
||||
.min(self.realized_loss.height.len())
|
||||
}
|
||||
|
||||
pub(crate) fn truncate_push(
|
||||
&mut self,
|
||||
height: Height,
|
||||
state: &impl RealizedOps,
|
||||
) -> Result<()> {
|
||||
self.realized_cap_cents
|
||||
.height
|
||||
.truncate_push(height, state.cap())?;
|
||||
self.realized_profit
|
||||
.height
|
||||
.truncate_push(height, state.profit())?;
|
||||
self.realized_loss
|
||||
.height
|
||||
.truncate_push(height, state.loss())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
|
||||
vec![&mut self.realized_cap_cents.height as &mut dyn AnyStoredVec]
|
||||
vec![
|
||||
&mut self.realized_cap_cents.height as &mut dyn AnyStoredVec,
|
||||
&mut self.realized_profit.height,
|
||||
&mut self.realized_loss.height,
|
||||
]
|
||||
}
|
||||
|
||||
pub(crate) fn compute_from_sources(
|
||||
&mut self,
|
||||
starting_indexes: &Indexes,
|
||||
others: &[&Self],
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
sum_others!(self, starting_indexes, others, exit; realized_cap_cents.height);
|
||||
sum_others!(self, starting_indexes, others, exit; realized_profit.height);
|
||||
sum_others!(self, starting_indexes, others, exit; realized_loss.height);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn compute_rest_part1(
|
||||
&mut self,
|
||||
starting_indexes: &Indexes,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.realized_profit
|
||||
.compute_rest(starting_indexes.height, exit)?;
|
||||
self.realized_loss
|
||||
.compute_rest(starting_indexes.height, exit)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn compute_rest_part2(
|
||||
@@ -175,6 +227,17 @@ impl MinimalUnrealized {
|
||||
]
|
||||
}
|
||||
|
||||
pub(crate) fn compute_from_sources(
|
||||
&mut self,
|
||||
starting_indexes: &Indexes,
|
||||
others: &[&Self],
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
sum_others!(self, starting_indexes, others, exit; supply_in_profit.base.sats.height);
|
||||
sum_others!(self, starting_indexes, others, exit; supply_in_loss.base.sats.height);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn compute_rest(
|
||||
&mut self,
|
||||
prices: &prices::Vecs,
|
||||
@@ -229,6 +292,7 @@ impl MinimalCohortMetrics {
|
||||
filter: cfg.filter.clone(),
|
||||
supply: Box::new(SupplyMetrics::forced_import(cfg)?),
|
||||
outputs: Box::new(OutputsMetrics::forced_import(cfg)?),
|
||||
activity: Box::new(ActivityCore::forced_import(cfg)?),
|
||||
realized: Box::new(MinimalRealized::forced_import(cfg)?),
|
||||
unrealized: Box::new(MinimalUnrealized::forced_import(cfg)?),
|
||||
relative: Box::new(MinimalRelative::forced_import(cfg)?),
|
||||
@@ -239,6 +303,7 @@ impl MinimalCohortMetrics {
|
||||
self.supply
|
||||
.min_len()
|
||||
.min(self.outputs.min_len())
|
||||
.min(self.activity.min_len())
|
||||
.min(self.realized.min_stateful_height_len())
|
||||
.min(self.unrealized.min_stateful_height_len())
|
||||
}
|
||||
@@ -252,11 +317,53 @@ impl MinimalCohortMetrics {
|
||||
let mut vecs: Vec<&mut dyn AnyStoredVec> = Vec::new();
|
||||
vecs.extend(self.supply.collect_vecs_mut());
|
||||
vecs.extend(self.outputs.collect_vecs_mut());
|
||||
vecs.extend(self.activity.collect_vecs_mut());
|
||||
vecs.extend(self.realized.collect_vecs_mut());
|
||||
vecs.extend(self.unrealized.collect_vecs_mut());
|
||||
vecs
|
||||
}
|
||||
|
||||
/// Aggregate Minimal-tier metrics from other MinimalCohortMetrics sources.
|
||||
pub(crate) fn compute_from_sources(
|
||||
&mut self,
|
||||
starting_indexes: &Indexes,
|
||||
others: &[&MinimalCohortMetrics],
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.supply.compute_from_stateful(
|
||||
starting_indexes,
|
||||
&others.iter().map(|v| v.supply.as_ref()).collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
self.outputs.compute_from_stateful(
|
||||
starting_indexes,
|
||||
&others.iter().map(|v| v.outputs.as_ref()).collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
self.activity.compute_from_stateful(
|
||||
starting_indexes,
|
||||
&others.iter().map(|v| v.activity.as_ref()).collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
self.realized.compute_from_sources(
|
||||
starting_indexes,
|
||||
&others
|
||||
.iter()
|
||||
.map(|v| v.realized.as_ref())
|
||||
.collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
self.unrealized.compute_from_sources(
|
||||
starting_indexes,
|
||||
&others
|
||||
.iter()
|
||||
.map(|v| v.unrealized.as_ref())
|
||||
.collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn compute_rest_part1(
|
||||
&mut self,
|
||||
blocks: &blocks::Vecs,
|
||||
@@ -270,6 +377,10 @@ impl MinimalCohortMetrics {
|
||||
.compute_rest_part1(blocks, starting_indexes, exit)?;
|
||||
self.outputs
|
||||
.compute_rest(blocks, starting_indexes, exit)?;
|
||||
self.activity
|
||||
.compute_rest_part1(blocks, prices, starting_indexes, exit)?;
|
||||
self.realized
|
||||
.compute_rest_part1(starting_indexes, exit)?;
|
||||
self.unrealized
|
||||
.compute_rest(prices, starting_indexes.height, exit)?;
|
||||
Ok(())
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
mod all;
|
||||
mod basic;
|
||||
mod complete;
|
||||
mod core;
|
||||
mod extended;
|
||||
mod extended_adjusted;
|
||||
@@ -8,7 +7,6 @@ mod minimal;
|
||||
|
||||
pub use all::*;
|
||||
pub use basic::*;
|
||||
pub use complete::*;
|
||||
pub use core::*;
|
||||
pub use extended::*;
|
||||
pub use extended_adjusted::*;
|
||||
|
||||
@@ -76,6 +76,8 @@ macro_rules! impl_cohort_metrics_base {
|
||||
vecs.extend(self.realized.collect_vecs_mut());
|
||||
vecs.extend(self.cost_basis.collect_vecs_mut());
|
||||
vecs.extend(self.unrealized.collect_vecs_mut());
|
||||
vecs.push(&mut self.dormancy.height);
|
||||
vecs.push(&mut self.velocity.height);
|
||||
vecs
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{
|
||||
BasisPointsSigned32, Bitcoin, Cents, CentsSigned, Dollars, Height, Indexes, StoredF64, Version,
|
||||
BasisPointsSigned32, Bitcoin, Cents, CentsSigned, Dollars, Height, Indexes, Version,
|
||||
};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use vecdb::{
|
||||
@@ -12,9 +12,9 @@ use crate::{
|
||||
blocks,
|
||||
distribution::state::RealizedState,
|
||||
internal::{
|
||||
CentsPlus, CentsUnsignedToDollars, ComputedFromHeight, LazyFromHeight, PercentFromHeight,
|
||||
RatioCents64, RatioCentsSignedCentsBps32, RatioCentsSignedDollarsBps32, RollingEmas1w1m,
|
||||
RollingEmas2w, RollingWindows, ValueFromHeightCumulative,
|
||||
CentsUnsignedToDollars, ComputedFromHeight, LazyFromHeight, PercentFromHeight,
|
||||
RatioCentsSignedCentsBps32, RatioCentsSignedDollarsBps32, RollingEmas2w, RollingWindows,
|
||||
ValueFromHeightCumulative,
|
||||
},
|
||||
prices,
|
||||
};
|
||||
@@ -35,24 +35,15 @@ pub struct RealizedComplete<M: StorageMode = Rw> {
|
||||
pub loss_value_created: ComputedFromHeight<Cents, M>,
|
||||
pub loss_value_destroyed: ComputedFromHeight<Cents, M>,
|
||||
|
||||
pub value_created: ComputedFromHeight<Cents, M>,
|
||||
pub value_destroyed: ComputedFromHeight<Cents, M>,
|
||||
|
||||
pub capitulation_flow: LazyFromHeight<Dollars, Cents>,
|
||||
pub profit_flow: LazyFromHeight<Dollars, Cents>,
|
||||
|
||||
pub value_created_sum: RollingWindows<Cents, M>,
|
||||
pub value_destroyed_sum: RollingWindows<Cents, M>,
|
||||
|
||||
pub gross_pnl_sum: RollingWindows<Cents, M>,
|
||||
|
||||
pub net_pnl_change_1m: ComputedFromHeight<CentsSigned, M>,
|
||||
pub net_pnl_change_1m_rel_to_realized_cap: PercentFromHeight<BasisPointsSigned32, M>,
|
||||
pub net_pnl_change_1m_rel_to_market_cap: PercentFromHeight<BasisPointsSigned32, M>,
|
||||
|
||||
pub sopr: RollingWindows<StoredF64, M>,
|
||||
pub sopr_24h_ema: RollingEmas1w1m<StoredF64, M>,
|
||||
|
||||
pub sent_in_profit: ValueFromHeightCumulative<M>,
|
||||
pub sent_in_profit_ema: RollingEmas2w<M>,
|
||||
pub sent_in_loss: ValueFromHeightCumulative<M>,
|
||||
@@ -69,8 +60,6 @@ impl RealizedComplete {
|
||||
let profit_value_destroyed = cfg.import_computed("profit_value_destroyed", v0)?;
|
||||
let loss_value_created = cfg.import_computed("loss_value_created", v0)?;
|
||||
let loss_value_destroyed = cfg.import_computed("loss_value_destroyed", v0)?;
|
||||
let value_created = cfg.import_computed("value_created", v0)?;
|
||||
let value_destroyed = cfg.import_computed("value_destroyed", v0)?;
|
||||
|
||||
let capitulation_flow = LazyFromHeight::from_computed::<CentsUnsignedToDollars>(
|
||||
&cfg.name("capitulation_flow"),
|
||||
@@ -85,28 +74,17 @@ impl RealizedComplete {
|
||||
&profit_value_destroyed,
|
||||
);
|
||||
|
||||
let value_created_sum = cfg.import_rolling("value_created", Version::ONE)?;
|
||||
let value_destroyed_sum = cfg.import_rolling("value_destroyed", Version::ONE)?;
|
||||
let gross_pnl_sum = cfg.import_rolling("gross_pnl_sum", Version::ONE)?;
|
||||
|
||||
let sopr = cfg.import_rolling("sopr", Version::ONE)?;
|
||||
let sopr_24h_ema = cfg.import_emas_1w_1m("sopr_24h", Version::ONE)?;
|
||||
|
||||
Ok(Self {
|
||||
core,
|
||||
profit_value_created,
|
||||
profit_value_destroyed,
|
||||
loss_value_created,
|
||||
loss_value_destroyed,
|
||||
value_created,
|
||||
value_destroyed,
|
||||
capitulation_flow,
|
||||
profit_flow,
|
||||
value_created_sum,
|
||||
value_destroyed_sum,
|
||||
gross_pnl_sum,
|
||||
sopr,
|
||||
sopr_24h_ema,
|
||||
net_pnl_change_1m: cfg.import_computed("net_pnl_change_1m", Version::new(3))?,
|
||||
net_pnl_change_1m_rel_to_realized_cap: cfg
|
||||
.import_percent_bps32("net_pnl_change_1m_rel_to_realized_cap", Version::new(4))?,
|
||||
@@ -216,34 +194,7 @@ impl RealizedComplete {
|
||||
exit,
|
||||
)?;
|
||||
|
||||
self.value_created
|
||||
.compute_binary::<Cents, Cents, CentsPlus>(
|
||||
starting_indexes.height,
|
||||
&self.profit_value_created.height,
|
||||
&self.loss_value_created.height,
|
||||
exit,
|
||||
)?;
|
||||
self.value_destroyed
|
||||
.compute_binary::<Cents, Cents, CentsPlus>(
|
||||
starting_indexes.height,
|
||||
&self.profit_value_destroyed.height,
|
||||
&self.loss_value_destroyed.height,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
let window_starts = blocks.count.window_starts();
|
||||
self.value_created_sum.compute_rolling_sum(
|
||||
starting_indexes.height,
|
||||
&window_starts,
|
||||
&self.value_created.height,
|
||||
exit,
|
||||
)?;
|
||||
self.value_destroyed_sum.compute_rolling_sum(
|
||||
starting_indexes.height,
|
||||
&window_starts,
|
||||
&self.value_destroyed.height,
|
||||
exit,
|
||||
)?;
|
||||
self.gross_pnl_sum.compute_rolling_sum(
|
||||
starting_indexes.height,
|
||||
&window_starts,
|
||||
@@ -251,29 +202,6 @@ impl RealizedComplete {
|
||||
exit,
|
||||
)?;
|
||||
|
||||
for ((sopr, vc), vd) in self
|
||||
.sopr
|
||||
.as_mut_array()
|
||||
.into_iter()
|
||||
.zip(self.value_created_sum.as_array())
|
||||
.zip(self.value_destroyed_sum.as_array())
|
||||
{
|
||||
sopr.compute_binary::<Cents, Cents, RatioCents64>(
|
||||
starting_indexes.height,
|
||||
&vc.height,
|
||||
&vd.height,
|
||||
exit,
|
||||
)?;
|
||||
}
|
||||
|
||||
self.sopr_24h_ema.compute_from_24h(
|
||||
starting_indexes.height,
|
||||
&blocks.count.height_1w_ago,
|
||||
&blocks.count.height_1m_ago,
|
||||
&self.sopr._24h.height,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
self.sent_in_profit_ema.compute(
|
||||
starting_indexes.height,
|
||||
&blocks.count.height_2w_ago,
|
||||
|
||||
@@ -2,7 +2,7 @@ use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{
|
||||
BasisPoints32, BasisPointsSigned32, Bitcoin, Cents, CentsSigned, Dollars, Height, Indexes,
|
||||
Sats, StoredF32, Version,
|
||||
Sats, StoredF32, StoredF64, Version,
|
||||
};
|
||||
use vecdb::{
|
||||
AnyStoredVec, AnyVec, Exit, ReadableCloneableVec, ReadableVec, Rw, StorageMode, WritableVec,
|
||||
@@ -14,8 +14,8 @@ use crate::{
|
||||
internal::{
|
||||
CentsUnsignedToDollars, ComputedFromHeight, ComputedFromHeightCumulative,
|
||||
ComputedFromHeightRatio, FiatFromHeight, Identity, LazyFromHeight,
|
||||
NegCentsUnsignedToDollars, PercentFromHeight, Price, RatioCentsBp32,
|
||||
RatioCentsSignedCentsBps32,
|
||||
NegCentsUnsignedToDollars, PercentFromHeight, Price, RatioCents64, RatioCentsBp32,
|
||||
RatioCentsSignedCentsBps32, RollingEmas1w1m, RollingWindows,
|
||||
},
|
||||
prices,
|
||||
};
|
||||
@@ -46,6 +46,13 @@ pub struct RealizedCore<M: StorageMode = Rw> {
|
||||
pub realized_profit_rel_to_realized_cap: PercentFromHeight<BasisPoints32, M>,
|
||||
pub realized_loss_rel_to_realized_cap: PercentFromHeight<BasisPoints32, M>,
|
||||
pub net_realized_pnl_rel_to_realized_cap: PercentFromHeight<BasisPointsSigned32, M>,
|
||||
|
||||
pub value_created: ComputedFromHeight<Cents, M>,
|
||||
pub value_destroyed: ComputedFromHeight<Cents, M>,
|
||||
pub value_created_sum: RollingWindows<Cents, M>,
|
||||
pub value_destroyed_sum: RollingWindows<Cents, M>,
|
||||
pub sopr: RollingWindows<StoredF64, M>,
|
||||
pub sopr_24h_ema: RollingEmas1w1m<StoredF64, M>,
|
||||
}
|
||||
|
||||
impl RealizedCore {
|
||||
@@ -93,6 +100,13 @@ impl RealizedCore {
|
||||
&realized_price_ratio.ratio,
|
||||
);
|
||||
|
||||
let value_created = cfg.import_computed("value_created", v0)?;
|
||||
let value_destroyed = cfg.import_computed("value_destroyed", v0)?;
|
||||
let value_created_sum = cfg.import_rolling("value_created", v1)?;
|
||||
let value_destroyed_sum = cfg.import_rolling("value_destroyed", v1)?;
|
||||
let sopr = cfg.import_rolling("sopr", v1)?;
|
||||
let sopr_24h_ema = cfg.import_emas_1w_1m("sopr_24h", v1)?;
|
||||
|
||||
Ok(Self {
|
||||
realized_cap_cents,
|
||||
realized_cap,
|
||||
@@ -111,6 +125,12 @@ impl RealizedCore {
|
||||
realized_profit_rel_to_realized_cap,
|
||||
realized_loss_rel_to_realized_cap,
|
||||
net_realized_pnl_rel_to_realized_cap,
|
||||
value_created,
|
||||
value_destroyed,
|
||||
value_created_sum,
|
||||
value_destroyed_sum,
|
||||
sopr,
|
||||
sopr_24h_ema,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -120,6 +140,8 @@ impl RealizedCore {
|
||||
.len()
|
||||
.min(self.realized_profit.height.len())
|
||||
.min(self.realized_loss.height.len())
|
||||
.min(self.value_created.height.len())
|
||||
.min(self.value_destroyed.height.len())
|
||||
}
|
||||
|
||||
pub(crate) fn truncate_push(&mut self, height: Height, state: &impl RealizedOps) -> Result<()> {
|
||||
@@ -132,6 +154,12 @@ impl RealizedCore {
|
||||
self.realized_loss
|
||||
.height
|
||||
.truncate_push(height, state.loss())?;
|
||||
self.value_created
|
||||
.height
|
||||
.truncate_push(height, state.value_created())?;
|
||||
self.value_destroyed
|
||||
.height
|
||||
.truncate_push(height, state.value_destroyed())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -140,6 +168,8 @@ impl RealizedCore {
|
||||
&mut self.realized_cap_cents.height as &mut dyn AnyStoredVec,
|
||||
&mut self.realized_profit.height,
|
||||
&mut self.realized_loss.height,
|
||||
&mut self.value_created.height,
|
||||
&mut self.value_destroyed.height,
|
||||
]
|
||||
}
|
||||
|
||||
@@ -152,6 +182,8 @@ impl RealizedCore {
|
||||
sum_others!(self, starting_indexes, others, exit; realized_cap_cents.height);
|
||||
sum_others!(self, starting_indexes, others, exit; realized_profit.height);
|
||||
sum_others!(self, starting_indexes, others, exit; realized_loss.height);
|
||||
sum_others!(self, starting_indexes, others, exit; value_created.height);
|
||||
sum_others!(self, starting_indexes, others, exit; value_destroyed.height);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -272,6 +304,44 @@ impl RealizedCore {
|
||||
exit,
|
||||
)?;
|
||||
|
||||
// SOPR: rolling sums of stateful value_created/destroyed, then ratio, then EMAs
|
||||
let window_starts = blocks.count.window_starts();
|
||||
self.value_created_sum.compute_rolling_sum(
|
||||
starting_indexes.height,
|
||||
&window_starts,
|
||||
&self.value_created.height,
|
||||
exit,
|
||||
)?;
|
||||
self.value_destroyed_sum.compute_rolling_sum(
|
||||
starting_indexes.height,
|
||||
&window_starts,
|
||||
&self.value_destroyed.height,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
for ((sopr, vc), vd) in self
|
||||
.sopr
|
||||
.as_mut_array()
|
||||
.into_iter()
|
||||
.zip(self.value_created_sum.as_array())
|
||||
.zip(self.value_destroyed_sum.as_array())
|
||||
{
|
||||
sopr.compute_binary::<Cents, Cents, RatioCents64>(
|
||||
starting_indexes.height,
|
||||
&vc.height,
|
||||
&vd.height,
|
||||
exit,
|
||||
)?;
|
||||
}
|
||||
|
||||
self.sopr_24h_ema.compute_from_24h(
|
||||
starting_indexes.height,
|
||||
&blocks.count.height_1w_ago,
|
||||
&blocks.count.height_1m_ago,
|
||||
&self.sopr._24h.height,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,17 @@ use std::cmp::Ordering;
|
||||
|
||||
use brk_types::{Cents, CentsSats, CentsSquaredSats, Sats};
|
||||
|
||||
/// Trait for realized state operations, implemented by both Core and Full variants.
|
||||
/// Core skips extra fields (value_created/destroyed, peak_regret, sent_in_profit/loss, investor_cap).
|
||||
/// Trait for realized state operations, implemented by Minimal, Core, and Full variants.
|
||||
pub trait RealizedOps: Default + Clone + Send + Sync + 'static {
|
||||
fn cap(&self) -> Cents;
|
||||
fn profit(&self) -> Cents;
|
||||
fn loss(&self) -> Cents;
|
||||
fn value_created(&self) -> Cents {
|
||||
Cents::ZERO
|
||||
}
|
||||
fn value_destroyed(&self) -> Cents {
|
||||
Cents::ZERO
|
||||
}
|
||||
fn set_cap_raw(&mut self, cap_raw: CentsSats);
|
||||
fn set_investor_cap_raw(&mut self, investor_cap_raw: CentsSquaredSats);
|
||||
fn reset_single_iteration_values(&mut self);
|
||||
@@ -27,17 +32,16 @@ pub trait RealizedOps: Default + Clone + Send + Sync + 'static {
|
||||
);
|
||||
}
|
||||
|
||||
/// Core realized state: only cap, profit, loss (48 bytes).
|
||||
/// Used by CoreCohortMetrics and MinimalCohortMetrics cohorts
|
||||
/// (epoch, class, amount_range, type_ — ~50 separate cohorts).
|
||||
/// Minimal realized state: only cap, profit, loss.
|
||||
/// Used by MinimalCohortMetrics cohorts (amount_range, type_, address — ~135 separate cohorts).
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct CoreRealizedState {
|
||||
pub struct MinimalRealizedState {
|
||||
cap_raw: u128,
|
||||
profit_raw: u128,
|
||||
loss_raw: u128,
|
||||
}
|
||||
|
||||
impl RealizedOps for CoreRealizedState {
|
||||
impl RealizedOps for MinimalRealizedState {
|
||||
#[inline]
|
||||
fn cap(&self) -> Cents {
|
||||
if self.cap_raw == 0 {
|
||||
@@ -68,9 +72,7 @@ impl RealizedOps for CoreRealizedState {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_investor_cap_raw(&mut self, _investor_cap_raw: CentsSquaredSats) {
|
||||
// no-op for Core
|
||||
}
|
||||
fn set_investor_cap_raw(&mut self, _investor_cap_raw: CentsSquaredSats) {}
|
||||
|
||||
#[inline]
|
||||
fn reset_single_iteration_values(&mut self) {
|
||||
@@ -119,9 +121,102 @@ impl RealizedOps for CoreRealizedState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Core realized state: cap, profit, loss + value_created/destroyed for SOPR.
|
||||
/// Used by CoreCohortMetrics cohorts (epoch, class, max_age, min_age — ~59 separate cohorts).
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct CoreRealizedState {
|
||||
minimal: MinimalRealizedState,
|
||||
value_created_raw: u128,
|
||||
value_destroyed_raw: u128,
|
||||
}
|
||||
|
||||
impl RealizedOps for CoreRealizedState {
|
||||
#[inline]
|
||||
fn cap(&self) -> Cents {
|
||||
self.minimal.cap()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn profit(&self) -> Cents {
|
||||
self.minimal.profit()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn loss(&self) -> Cents {
|
||||
self.minimal.loss()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn value_created(&self) -> Cents {
|
||||
if self.value_created_raw == 0 {
|
||||
return Cents::ZERO;
|
||||
}
|
||||
Cents::new((self.value_created_raw / Sats::ONE_BTC_U128) as u64)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn value_destroyed(&self) -> Cents {
|
||||
if self.value_destroyed_raw == 0 {
|
||||
return Cents::ZERO;
|
||||
}
|
||||
Cents::new((self.value_destroyed_raw / Sats::ONE_BTC_U128) as u64)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_cap_raw(&mut self, cap_raw: CentsSats) {
|
||||
self.minimal.set_cap_raw(cap_raw);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_investor_cap_raw(&mut self, _investor_cap_raw: CentsSquaredSats) {}
|
||||
|
||||
#[inline]
|
||||
fn reset_single_iteration_values(&mut self) {
|
||||
self.minimal.reset_single_iteration_values();
|
||||
self.value_created_raw = 0;
|
||||
self.value_destroyed_raw = 0;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn increment(&mut self, price: Cents, sats: Sats) {
|
||||
self.minimal.increment(price, sats);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn increment_snapshot(&mut self, price_sats: CentsSats, _investor_cap: CentsSquaredSats) {
|
||||
self.minimal.increment_snapshot(price_sats, _investor_cap);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn decrement_snapshot(&mut self, price_sats: CentsSats, _investor_cap: CentsSquaredSats) {
|
||||
self.minimal.decrement_snapshot(price_sats, _investor_cap);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn send(
|
||||
&mut self,
|
||||
sats: Sats,
|
||||
current_ps: CentsSats,
|
||||
prev_ps: CentsSats,
|
||||
ath_ps: CentsSats,
|
||||
prev_investor_cap: CentsSquaredSats,
|
||||
) {
|
||||
self.minimal
|
||||
.send(sats, current_ps, prev_ps, ath_ps, prev_investor_cap);
|
||||
self.value_created_raw += current_ps.as_u128();
|
||||
self.value_destroyed_raw += prev_ps.as_u128();
|
||||
}
|
||||
}
|
||||
|
||||
impl CoreRealizedState {
|
||||
#[inline(always)]
|
||||
pub(super) fn cap_raw_u128(&self) -> u128 {
|
||||
self.minimal.cap_raw
|
||||
}
|
||||
}
|
||||
|
||||
/// Full realized state (~160 bytes).
|
||||
/// Used by BasicCohortMetrics and CompleteCohortMetrics cohorts
|
||||
/// (age_range — 21 separate cohorts).
|
||||
/// Used by BasicCohortMetrics cohorts (age_range — 21 separate cohorts).
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct RealizedState {
|
||||
core: CoreRealizedState,
|
||||
@@ -159,6 +254,24 @@ impl RealizedOps for RealizedState {
|
||||
self.core.loss()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn value_created(&self) -> Cents {
|
||||
let raw = self.profit_value_created_raw + self.loss_value_created_raw;
|
||||
if raw == 0 {
|
||||
return Cents::ZERO;
|
||||
}
|
||||
Cents::new((raw / Sats::ONE_BTC_U128) as u64)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn value_destroyed(&self) -> Cents {
|
||||
let raw = self.profit_value_destroyed_raw + self.loss_value_destroyed_raw;
|
||||
if raw == 0 {
|
||||
return Cents::ZERO;
|
||||
}
|
||||
Cents::new((raw / Sats::ONE_BTC_U128) as u64)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_cap_raw(&mut self, cap_raw: CentsSats) {
|
||||
self.core.set_cap_raw(cap_raw);
|
||||
@@ -183,23 +296,21 @@ impl RealizedOps for RealizedState {
|
||||
|
||||
#[inline]
|
||||
fn increment(&mut self, price: Cents, sats: Sats) {
|
||||
if sats.is_zero() {
|
||||
return;
|
||||
self.core.increment(price, sats);
|
||||
if sats.is_not_zero() {
|
||||
self.investor_cap_raw += CentsSats::from_price_sats(price, sats).to_investor_cap(price);
|
||||
}
|
||||
let price_sats = CentsSats::from_price_sats(price, sats);
|
||||
self.core.cap_raw += price_sats.as_u128();
|
||||
self.investor_cap_raw += price_sats.to_investor_cap(price);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn increment_snapshot(&mut self, price_sats: CentsSats, investor_cap: CentsSquaredSats) {
|
||||
self.core.cap_raw += price_sats.as_u128();
|
||||
self.core.increment_snapshot(price_sats, investor_cap);
|
||||
self.investor_cap_raw += investor_cap;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn decrement_snapshot(&mut self, price_sats: CentsSats, investor_cap: CentsSquaredSats) {
|
||||
self.core.cap_raw -= price_sats.as_u128();
|
||||
self.core.decrement_snapshot(price_sats, investor_cap);
|
||||
self.investor_cap_raw -= investor_cap;
|
||||
}
|
||||
|
||||
@@ -212,32 +323,32 @@ impl RealizedOps for RealizedState {
|
||||
ath_ps: CentsSats,
|
||||
prev_investor_cap: CentsSquaredSats,
|
||||
) {
|
||||
// Delegate cap/profit/loss + value_created/destroyed to core
|
||||
self.core
|
||||
.send(sats, current_ps, prev_ps, ath_ps, prev_investor_cap);
|
||||
|
||||
// Per-component value flow tracking
|
||||
let current = current_ps.as_u128();
|
||||
let prev = prev_ps.as_u128();
|
||||
match current_ps.cmp(&prev_ps) {
|
||||
Ordering::Greater => {
|
||||
self.core.profit_raw += (current_ps - prev_ps).as_u128();
|
||||
self.profit_value_created_raw += current_ps.as_u128();
|
||||
self.profit_value_destroyed_raw += prev_ps.as_u128();
|
||||
self.profit_value_created_raw += current;
|
||||
self.profit_value_destroyed_raw += prev;
|
||||
self.sent_in_profit += sats;
|
||||
}
|
||||
Ordering::Less => {
|
||||
self.core.loss_raw += (prev_ps - current_ps).as_u128();
|
||||
self.loss_value_created_raw += current_ps.as_u128();
|
||||
self.loss_value_destroyed_raw += prev_ps.as_u128();
|
||||
self.loss_value_created_raw += current;
|
||||
self.loss_value_destroyed_raw += prev;
|
||||
self.sent_in_loss += sats;
|
||||
}
|
||||
Ordering::Equal => {
|
||||
// Break-even: count as profit side (arbitrary but consistent)
|
||||
self.profit_value_created_raw += current_ps.as_u128();
|
||||
self.profit_value_destroyed_raw += prev_ps.as_u128();
|
||||
self.profit_value_created_raw += current;
|
||||
self.profit_value_destroyed_raw += prev;
|
||||
self.sent_in_profit += sats;
|
||||
}
|
||||
}
|
||||
|
||||
// Track peak regret: (peak - sell_price) × sats
|
||||
self.peak_regret_raw += (ath_ps - current_ps).as_u128();
|
||||
|
||||
// Inline decrement to avoid recomputation
|
||||
self.core.cap_raw -= prev_ps.as_u128();
|
||||
self.investor_cap_raw -= prev_investor_cap;
|
||||
}
|
||||
}
|
||||
@@ -247,16 +358,17 @@ impl RealizedState {
|
||||
/// investor_price = Σ(price² × sats) / Σ(price × sats)
|
||||
#[inline]
|
||||
pub(crate) fn investor_price(&self) -> Cents {
|
||||
if self.core.cap_raw == 0 {
|
||||
let cap_raw = self.core.cap_raw_u128();
|
||||
if cap_raw == 0 {
|
||||
return Cents::ZERO;
|
||||
}
|
||||
Cents::new((self.investor_cap_raw / self.core.cap_raw) as u64)
|
||||
Cents::new((self.investor_cap_raw / cap_raw) as u64)
|
||||
}
|
||||
|
||||
/// Get raw realized cap for aggregation.
|
||||
#[inline]
|
||||
pub(crate) fn cap_raw(&self) -> CentsSats {
|
||||
CentsSats::new(self.core.cap_raw)
|
||||
CentsSats::new(self.core.cap_raw_u128())
|
||||
}
|
||||
|
||||
/// Get raw investor cap for aggregation.
|
||||
|
||||
@@ -69,15 +69,6 @@ impl UnaryTransform<Cents, Sats> for CentsUnsignedToSats {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CentsPlus;
|
||||
|
||||
impl BinaryTransform<Cents, Cents, Cents> for CentsPlus {
|
||||
#[inline(always)]
|
||||
fn apply(lhs: Cents, rhs: Cents) -> Cents {
|
||||
lhs + rhs
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CentsSubtractToCentsSigned;
|
||||
|
||||
impl BinaryTransform<Cents, Cents, CentsSigned> for CentsSubtractToCentsSigned {
|
||||
|
||||
@@ -14,7 +14,7 @@ pub use bps::{
|
||||
Bps32ToPercent,
|
||||
};
|
||||
pub use currency::{
|
||||
CentsPlus, CentsSignedToDollars, CentsSubtractToCentsSigned, CentsTimesTenths,
|
||||
CentsSignedToDollars, CentsSubtractToCentsSigned, CentsTimesTenths,
|
||||
CentsUnsignedToDollars, CentsUnsignedToSats, DollarsToSatsFract, NegCentsUnsignedToDollars,
|
||||
SatsSignedToBitcoin, SatsToBitcoin, SatsToCents,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user