global: snapshot

This commit is contained in:
nym21
2026-03-10 01:13:52 +01:00
parent 961dea6934
commit 46ac55d950
121 changed files with 9792 additions and 5997 deletions

View File

@@ -157,7 +157,6 @@ impl DynCohortVecs for AddressCohortVecs {
self.addr_count
.height
.validate_computed_version_or_reset(base_version)?;
self.metrics.validate_computed_versions(base_version)?;
Ok(())
}
@@ -170,18 +169,9 @@ impl DynCohortVecs for AddressCohortVecs {
self.addr_count
.height
.truncate_push(height, state.addr_count.into())?;
self.metrics
.supply
.truncate_push(height, state.inner.supply.value)?;
self.metrics
.outputs
.truncate_push(height, state.inner.supply.utxo_count)?;
self.metrics
.activity
.truncate_push(height, state.inner.sent)?;
self.metrics
.realized
.truncate_push(height, &state.inner.realized)?;
self.metrics.supply.truncate_push(height, &state.inner)?;
self.metrics.outputs.truncate_push(height, &state.inner)?;
self.metrics.realized.truncate_push(height, &state.inner)?;
}
Ok(())
@@ -189,17 +179,10 @@ impl DynCohortVecs for AddressCohortVecs {
fn compute_then_truncate_push_unrealized_states(
&mut self,
height: Height,
height_price: Cents,
_height: Height,
_height_price: Cents,
_is_day_boundary: bool,
) -> Result<()> {
if let Some(state) = self.state.as_mut() {
state.inner.apply_pending();
let unrealized_state = state.inner.compute_unrealized_state(height_price);
self.metrics
.unrealized
.truncate_push(height, &unrealized_state)?;
}
Ok(())
}

View File

@@ -17,9 +17,10 @@ use crate::{
distribution::{
DynCohortVecs,
metrics::{
AllCohortMetrics, BasicCohortMetrics, CohortMetricsBase, CoreCohortMetrics,
ExtendedAdjustedCohortMetrics, ExtendedCohortMetrics, ImportConfig,
MinimalCohortMetrics, ProfitabilityMetrics, RealizedFullAccum, SupplyMetrics,
AllCohortMetrics, BasicCohortMetrics, CohortMetricsBase,
CoreCohortMetrics, ExtendedAdjustedCohortMetrics, ExtendedCohortMetrics, ImportConfig,
MinimalCohortMetrics, ProfitabilityMetrics, RealizedFullAccum, SupplyFull,
TypeCohortMetrics,
},
state::UTXOCohortState,
},
@@ -47,7 +48,7 @@ pub struct UTXOCohorts<M: StorageMode = Rw> {
pub amount_range: ByAmountRange<UTXOCohortVecs<MinimalCohortMetrics<M>>>,
pub lt_amount: ByLowerThanAmount<UTXOCohortVecs<MinimalCohortMetrics<M>>>,
#[traversable(rename = "type")]
pub type_: BySpendableType<UTXOCohortVecs<MinimalCohortMetrics<M>>>,
pub type_: BySpendableType<UTXOCohortVecs<TypeCohortMetrics<M>>>,
pub profitability: ProfitabilityMetrics<M>,
pub matured: ByAgeRange<AmountPerBlock<M>>,
#[traversable(skip)]
@@ -81,7 +82,7 @@ impl UTXOCohorts<Rw> {
version: v + Version::ONE,
indexes,
};
let all_supply = SupplyMetrics::forced_import(&all_cfg)?;
let all_supply = SupplyFull::forced_import(&all_cfg)?;
// Phase 2: Import separate (stateful) cohorts.
@@ -144,7 +145,25 @@ impl UTXOCohorts<Rw> {
};
let amount_range = ByAmountRange::try_new(&minimal_separate)?;
let type_ = BySpendableType::try_new(&minimal_separate)?;
let type_separate =
|f: Filter, name: &'static str| -> Result<UTXOCohortVecs<TypeCohortMetrics>> {
let full_name = CohortContext::Utxo.full_name(&f, name);
let cfg = ImportConfig {
db,
filter: &f,
full_name: &full_name,
version: v,
indexes,
};
let state = Some(Box::new(UTXOCohortState::new(states_path, &full_name)));
Ok(UTXOCohortVecs::new(
state,
TypeCohortMetrics::forced_import(&cfg)?,
))
};
let type_ = BySpendableType::try_new(&type_separate)?;
// Phase 3: Import "all" cohort with pre-imported supply.
let all = UTXOCohortVecs::new(
@@ -208,7 +227,6 @@ impl UTXOCohorts<Rw> {
// 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>> {
let full_name = CohortContext::Utxo.full_name(&f, name);
@@ -424,7 +442,8 @@ impl UTXOCohorts<Rw> {
.try_for_each(|vecs| {
let sources =
filter_minimal_sources_from(amr.iter(), Some(&vecs.metrics.filter));
vecs.metrics.compute_from_sources(si, &sources, exit)
vecs.metrics
.compute_from_sources(si, &sources, exit)
})
}),
];
@@ -507,7 +526,10 @@ impl UTXOCohorts<Rw> {
.up_to_1h
.metrics
.realized
.minimal
.sopr
.value_created
.raw
.height
.read_only_clone();
let up_to_1h_value_destroyed = self
@@ -515,7 +537,10 @@ impl UTXOCohorts<Rw> {
.up_to_1h
.metrics
.realized
.minimal
.sopr
.value_destroyed
.raw
.height
.read_only_clone();
@@ -746,12 +771,6 @@ impl UTXOCohorts<Rw> {
for v in self.max_age.iter_mut() {
v.metrics.validate_computed_versions(base_version)?;
}
for v in self.ge_amount.iter_mut() {
v.metrics.validate_computed_versions(base_version)?;
}
for v in self.lt_amount.iter_mut() {
v.metrics.validate_computed_versions(base_version)?;
}
Ok(())
}

View File

@@ -0,0 +1,84 @@
use brk_cohort::{Filter, Filtered};
use brk_error::Result;
use brk_types::{Cents, Height, Indexes, Version};
use vecdb::{Exit, ReadableVec};
use crate::{blocks, distribution::{cohorts::traits::DynCohortVecs, metrics::CoreCohortMetrics}, prices};
use super::UTXOCohortVecs;
impl Filtered for UTXOCohortVecs<CoreCohortMetrics> {
fn filter(&self) -> &Filter {
&self.metrics.filter
}
}
impl DynCohortVecs for UTXOCohortVecs<CoreCohortMetrics> {
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)?;
self.metrics.outputs.truncate_push(height, state)?;
self.metrics.activity.truncate_push(height, state)?;
self.metrics.realized.truncate_push(height, state)?;
}
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();
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();
}
}

View File

@@ -0,0 +1,80 @@
use brk_cohort::{Filter, Filtered};
use brk_error::Result;
use brk_types::{Cents, Height, Indexes, Version};
use vecdb::{Exit, ReadableVec};
use crate::{
blocks,
distribution::{cohorts::traits::DynCohortVecs, metrics::MinimalCohortMetrics},
prices,
};
use super::UTXOCohortVecs;
impl Filtered for UTXOCohortVecs<MinimalCohortMetrics> {
fn filter(&self) -> &Filter {
&self.metrics.filter
}
}
impl DynCohortVecs for UTXOCohortVecs<MinimalCohortMetrics> {
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<()> {
Ok(())
}
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)?;
self.metrics.outputs.truncate_push(height, state)?;
self.metrics.realized.truncate_push(height, state)?;
}
Ok(())
}
fn compute_then_truncate_push_unrealized_states(
&mut self,
_height: Height,
_height_price: Cents,
_is_day_boundary: bool,
) -> Result<()> {
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();
}
}

View File

@@ -1,17 +1,63 @@
macro_rules! impl_import_state {
() => {
fn import_state(&mut self, starting_height: Height) -> Result<Height> {
if let Some(state) = self.state.as_mut() {
if let Some(mut prev_height) = starting_height.decremented() {
prev_height = state.import_at_or_before(prev_height)?;
state.supply.value = self
.metrics
.supply
.total
.sats
.height
.collect_one(prev_height)
.unwrap();
state.supply.utxo_count = *self
.metrics
.outputs
.utxo_count
.height
.collect_one(prev_height)
.unwrap();
state.restore_realized_cap();
let result = prev_height.incremented();
self.state_starting_height = Some(result);
Ok(result)
} else {
self.state_starting_height = Some(Height::ZERO);
Ok(Height::ZERO)
}
} else {
self.state_starting_height = Some(starting_height);
Ok(starting_height)
}
}
};
}
mod core;
mod minimal;
mod r#type;
use brk_cohort::{Filter, Filtered};
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Cents, Height, Indexes, Version};
use vecdb::{Exit, ReadableVec};
use crate::{blocks, distribution::state::UTXOCohortState, prices};
use crate::distribution::metrics::{
CohortMetricsBase, CohortMetricsState, CoreCohortMetrics, MinimalCohortMetrics,
use crate::{
blocks,
distribution::{
cohorts::traits::DynCohortVecs,
metrics::{CohortMetricsBase, CohortMetricsState},
state::UTXOCohortState,
},
prices,
};
use super::super::traits::DynCohortVecs;
#[derive(Traversable)]
pub struct UTXOCohortVecs<M: CohortMetricsState> {
#[traversable(skip)]
@@ -24,8 +70,6 @@ pub struct UTXOCohortVecs<M: CohortMetricsState> {
pub metrics: M,
}
// --- Shared state helpers (identical across all DynCohortVecs impls) ---
impl<M: CohortMetricsState> UTXOCohortVecs<M> {
pub(crate) fn new(state: Option<Box<UTXOCohortState<M::Realized>>>, metrics: M) -> Self {
Self {
@@ -172,213 +216,3 @@ impl<M: CohortMetricsBase + Traversable> DynCohortVecs for UTXOCohortVecs<M> {
self.reset_iteration_impl();
}
}
// --- Shared import_state for non-blanket impls (direct field access) ---
macro_rules! impl_import_state {
() => {
fn import_state(&mut self, starting_height: Height) -> Result<Height> {
if let Some(state) = self.state.as_mut() {
if let Some(mut prev_height) = starting_height.decremented() {
prev_height = state.import_at_or_before(prev_height)?;
state.supply.value = self
.metrics
.supply
.total
.sats
.height
.collect_one(prev_height)
.unwrap();
state.supply.utxo_count = *self
.metrics
.outputs
.utxo_count
.height
.collect_one(prev_height)
.unwrap();
state.restore_realized_cap();
let result = prev_height.incremented();
self.state_starting_height = Some(result);
Ok(result)
} else {
self.state_starting_height = Some(Height::ZERO);
Ok(Height::ZERO)
}
} else {
self.state_starting_height = Some(starting_height);
Ok(starting_height)
}
}
};
}
// --- MinimalCohortMetrics: uses MinimalRealizedState ---
impl Filtered for UTXOCohortVecs<MinimalCohortMetrics> {
fn filter(&self) -> &Filter {
&self.metrics.filter
}
}
impl DynCohortVecs for UTXOCohortVecs<MinimalCohortMetrics> {
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)?;
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();
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();
}
}
// --- CoreCohortMetrics: uses CoreRealizedState ---
impl Filtered for UTXOCohortVecs<CoreCohortMetrics> {
fn filter(&self) -> &Filter {
&self.metrics.filter
}
}
impl DynCohortVecs for UTXOCohortVecs<CoreCohortMetrics> {
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)?;
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();
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();
}
}

View File

@@ -0,0 +1,83 @@
use brk_cohort::{Filter, Filtered};
use brk_error::Result;
use brk_types::{Cents, Height, Indexes, Version};
use vecdb::{Exit, ReadableVec};
use crate::{blocks, distribution::cohorts::traits::DynCohortVecs, distribution::metrics::TypeCohortMetrics, prices};
use super::UTXOCohortVecs;
impl Filtered for UTXOCohortVecs<TypeCohortMetrics> {
fn filter(&self) -> &Filter {
&self.metrics.filter
}
}
impl DynCohortVecs for UTXOCohortVecs<TypeCohortMetrics> {
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<()> {
Ok(())
}
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)?;
self.metrics.outputs.truncate_push(height, state)?;
self.metrics.realized.truncate_push(height, state)?;
}
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();
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();
}
}

View File

@@ -1,97 +0,0 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Bitcoin, Height, Indexes, Sats, StoredF64, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{AnyStoredVec, AnyVec, Exit, Rw, StorageMode, WritableVec};
use crate::internal::ComputedPerBlock;
use crate::{blocks, distribution::metrics::ImportConfig};
use super::ActivityCore;
#[derive(Deref, DerefMut, Traversable)]
pub struct ActivityBase<M: StorageMode = Rw> {
#[deref]
#[deref_mut]
#[traversable(flatten)]
pub core: ActivityCore<M>,
pub coinblocks_destroyed: ComputedPerBlock<StoredF64, M>,
pub coindays_destroyed: ComputedPerBlock<StoredF64, M>,
}
impl ActivityBase {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
let v1 = Version::ONE;
Ok(Self {
core: ActivityCore::forced_import(cfg)?,
coinblocks_destroyed: cfg.import("coinblocks_destroyed", v1)?,
coindays_destroyed: cfg.import("coindays_destroyed", v1)?,
})
}
pub(crate) fn min_len(&self) -> usize {
self.core
.min_len()
.min(self.coinblocks_destroyed.height.len())
.min(self.coindays_destroyed.height.len())
}
pub(crate) fn truncate_push(
&mut self,
height: Height,
sent: Sats,
satblocks_destroyed: Sats,
satdays_destroyed: Sats,
) -> Result<()> {
self.core.truncate_push(height, sent)?;
self.coinblocks_destroyed.height.truncate_push(
height,
StoredF64::from(Bitcoin::from(satblocks_destroyed)),
)?;
self.coindays_destroyed.height.truncate_push(
height,
StoredF64::from(Bitcoin::from(satdays_destroyed)),
)?;
Ok(())
}
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
vec![
&mut self.core.sent.height as &mut dyn AnyStoredVec,
&mut self.coinblocks_destroyed.height as &mut dyn AnyStoredVec,
&mut self.coindays_destroyed.height as &mut dyn AnyStoredVec,
]
}
pub(crate) fn validate_computed_versions(&mut self, _base_version: Version) -> Result<()> {
Ok(())
}
pub(crate) fn compute_from_stateful(
&mut self,
starting_indexes: &Indexes,
others: &[&Self],
exit: &Exit,
) -> Result<()> {
let core_refs: Vec<&ActivityCore> = others.iter().map(|o| &o.core).collect();
self.core
.compute_from_stateful(starting_indexes, &core_refs, exit)?;
sum_others!(self, starting_indexes, others, exit; coinblocks_destroyed.height);
sum_others!(self, starting_indexes, others, exit; coindays_destroyed.height);
Ok(())
}
pub(crate) fn compute_rest_part1(
&mut self,
blocks: &blocks::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
self.core
.compute_rest_part1(blocks, starting_indexes, exit)?;
Ok(())
}
}

View File

@@ -1,16 +1,18 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Height, Indexes, Sats, Version};
use brk_types::{Bitcoin, Height, Indexes, Sats, StoredF64, Version};
use vecdb::{AnyStoredVec, AnyVec, Exit, Rw, StorageMode, WritableVec};
use crate::internal::{ComputedPerBlock, RollingWindow24h};
use crate::{blocks, distribution::metrics::ImportConfig};
use crate::{
blocks,
distribution::{metrics::ImportConfig, state::{CohortState, RealizedOps}},
internal::PerBlockWithSum24h,
};
#[derive(Traversable)]
pub struct ActivityCore<M: StorageMode = Rw> {
pub sent: ComputedPerBlock<Sats, M>,
pub sent_sum: RollingWindow24h<Sats, M>,
pub sent: PerBlockWithSum24h<Sats, M>,
pub coindays_destroyed: PerBlockWithSum24h<StoredF64, M>,
}
impl ActivityCore {
@@ -18,21 +20,36 @@ impl ActivityCore {
let v1 = Version::ONE;
Ok(Self {
sent: cfg.import("sent", v1)?,
sent_sum: cfg.import("sent", v1)?,
coindays_destroyed: cfg.import("coindays_destroyed", v1)?,
})
}
pub(crate) fn min_len(&self) -> usize {
self.sent.height.len()
self.sent
.raw
.height
.len()
.min(self.coindays_destroyed.raw.height.len())
}
pub(crate) fn truncate_push(&mut self, height: Height, sent: Sats) -> Result<()> {
self.sent.height.truncate_push(height, sent)?;
pub(crate) fn truncate_push(
&mut self,
height: Height,
state: &CohortState<impl RealizedOps>,
) -> Result<()> {
self.sent.raw.height.truncate_push(height, state.sent)?;
self.coindays_destroyed.raw.height.truncate_push(
height,
StoredF64::from(Bitcoin::from(state.satdays_destroyed)),
)?;
Ok(())
}
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
vec![&mut self.sent.height as &mut dyn AnyStoredVec]
vec![
&mut self.sent.raw.height as &mut dyn AnyStoredVec,
&mut self.coindays_destroyed.raw.height,
]
}
pub(crate) fn validate_computed_versions(&mut self, _base_version: Version) -> Result<()> {
@@ -45,14 +62,17 @@ impl ActivityCore {
others: &[&Self],
exit: &Exit,
) -> Result<()> {
self.sent.height.compute_sum_of_others(
self.sent.raw.height.compute_sum_of_others(
starting_indexes.height,
&others
.iter()
.map(|v| &v.sent.height)
.map(|v| &v.sent.raw.height)
.collect::<Vec<_>>(),
exit,
)?;
sum_others!(self, starting_indexes, others, exit; coindays_destroyed.raw.height);
Ok(())
}
@@ -62,10 +82,16 @@ impl ActivityCore {
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
self.sent_sum.compute_rolling_sum(
self.sent.sum.compute_rolling_sum(
starting_indexes.height,
&blocks.lookback.height_24h_ago,
&self.sent.height,
&self.sent.raw.height,
exit,
)?;
self.coindays_destroyed.sum.compute_rolling_sum(
starting_indexes.height,
&blocks.lookback.height_24h_ago,
&self.coindays_destroyed.raw.height,
exit,
)?;
Ok(())

View File

@@ -1,47 +1,90 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Indexes, Sats, StoredF64, Version};
use brk_types::{Bitcoin, Height, Indexes, Sats, StoredF32, StoredF64, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{Exit, Rw, StorageMode};
use vecdb::{AnyStoredVec, AnyVec, Exit, ReadableVec, Rw, StorageMode, WritableVec};
use crate::internal::{ComputedPerBlock, RollingWindows, RollingWindowsFrom1w};
use crate::internal::{ComputedPerBlock, RollingWindowsFrom1w};
use crate::{blocks, distribution::metrics::ImportConfig};
use crate::{blocks, distribution::{metrics::ImportConfig, state::{CohortState, RealizedOps}}};
use super::ActivityBase;
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]
#[deref_mut]
#[traversable(flatten)]
pub inner: ActivityBase<M>,
pub inner: ActivityCore<M>,
pub coinblocks_destroyed_cumulative: ComputedPerBlock<StoredF64, M>,
pub coinblocks_destroyed: ActivityCoinblocks<M>,
#[traversable(wrap = "coindays_destroyed", rename = "cumulative")]
pub coindays_destroyed_cumulative: ComputedPerBlock<StoredF64, M>,
pub coindays_destroyed_sum: RollingWindows<StoredF64, M>,
#[traversable(wrap = "coindays_destroyed", rename = "sum")]
pub coindays_destroyed_sum: RollingWindowsFrom1w<StoredF64, M>,
#[traversable(rename = "sent_sum")]
#[traversable(wrap = "sent", rename = "sum")]
pub sent_sum_extended: RollingWindowsFrom1w<Sats, M>,
pub dormancy: ComputedPerBlock<StoredF32, M>,
pub velocity: ComputedPerBlock<StoredF32, M>,
}
impl ActivityFull {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
let v1 = Version::ONE;
Ok(Self {
inner: ActivityBase::forced_import(cfg)?,
coinblocks_destroyed_cumulative: cfg
.import("coinblocks_destroyed_cumulative", v1)?,
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)?,
dormancy: cfg.import("dormancy", v1)?,
velocity: cfg.import("velocity", v1)?,
})
}
pub(crate) fn full_min_len(&self) -> usize {
self.inner
.min_len()
.min(self.coinblocks_destroyed.raw.height.len())
}
pub(crate) fn full_truncate_push(
&mut self,
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(())
}
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
}
pub(crate) fn compute_from_stateful(
&mut self,
starting_indexes: &Indexes,
others: &[&ActivityBase],
others: &[&ActivityCore],
exit: &Exit,
) -> Result<()> {
self.inner
@@ -57,11 +100,12 @@ impl ActivityFull {
self.inner
.compute_rest_part1(blocks, starting_indexes, exit)?;
self.coinblocks_destroyed_cumulative
self.coinblocks_destroyed
.cumulative
.height
.compute_cumulative(
starting_indexes.height,
&self.inner.coinblocks_destroyed.height,
&self.coinblocks_destroyed.raw.height,
exit,
)?;
@@ -69,7 +113,7 @@ impl ActivityFull {
.height
.compute_cumulative(
starting_indexes.height,
&self.inner.coindays_destroyed.height,
&self.inner.coindays_destroyed.raw.height,
exit,
)?;
@@ -77,14 +121,53 @@ impl ActivityFull {
self.coindays_destroyed_sum.compute_rolling_sum(
starting_indexes.height,
&window_starts,
&self.inner.coindays_destroyed.height,
&self.inner.coindays_destroyed.raw.height,
exit,
)?;
self.sent_sum_extended.compute_rolling_sum(
starting_indexes.height,
&window_starts,
&self.inner.core.sent.height,
&self.inner.sent.raw.height,
exit,
)?;
Ok(())
}
pub(crate) fn compute_rest_part2(
&mut self,
starting_indexes: &Indexes,
supply_total_sats: &impl ReadableVec<Height, Sats>,
exit: &Exit,
) -> Result<()> {
self.dormancy.height.compute_transform2(
starting_indexes.height,
&self.inner.coindays_destroyed.raw.height,
&self.inner.sent.raw.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.inner.sent.raw.height,
supply_total_sats,
|(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,
)?;

View File

@@ -1,33 +1,29 @@
mod base;
mod core;
mod full;
pub use base::ActivityBase;
pub use self::core::ActivityCore;
pub use full::ActivityFull;
use brk_error::Result;
use brk_types::{Height, Indexes, Sats, Version};
use brk_types::{Height, Indexes, Version};
use vecdb::Exit;
use crate::blocks;
use crate::{blocks, distribution::state::{CohortState, RealizedOps}};
pub trait ActivityLike: Send + Sync {
fn as_base(&self) -> &ActivityBase;
fn as_base_mut(&mut self) -> &mut ActivityBase;
fn as_core(&self) -> &ActivityCore;
fn as_core_mut(&mut self) -> &mut ActivityCore;
fn min_len(&self) -> usize;
fn truncate_push(
fn truncate_push<R: RealizedOps>(
&mut self,
height: Height,
sent: Sats,
satblocks_destroyed: Sats,
satdays_destroyed: Sats,
state: &CohortState<R>,
) -> Result<()>;
fn validate_computed_versions(&mut self, base_version: Version) -> Result<()>;
fn compute_from_stateful(
&mut self,
starting_indexes: &Indexes,
others: &[&ActivityBase],
others: &[&ActivityCore],
exit: &Exit,
) -> Result<()>;
fn compute_rest_part1(
@@ -38,17 +34,17 @@ pub trait ActivityLike: Send + Sync {
) -> Result<()>;
}
impl ActivityLike for ActivityBase {
fn as_base(&self) -> &ActivityBase { self }
fn as_base_mut(&mut self) -> &mut ActivityBase { self }
impl ActivityLike for ActivityCore {
fn as_core(&self) -> &ActivityCore { self }
fn as_core_mut(&mut self) -> &mut ActivityCore { self }
fn min_len(&self) -> usize { self.min_len() }
fn truncate_push(&mut self, height: Height, sent: Sats, satblocks_destroyed: Sats, satdays_destroyed: Sats) -> Result<()> {
self.truncate_push(height, sent, satblocks_destroyed, satdays_destroyed)
fn truncate_push<R: RealizedOps>(&mut self, height: Height, state: &CohortState<R>) -> Result<()> {
self.truncate_push(height, state)
}
fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> {
self.validate_computed_versions(base_version)
}
fn compute_from_stateful(&mut self, starting_indexes: &Indexes, others: &[&ActivityBase], exit: &Exit) -> Result<()> {
fn compute_from_stateful(&mut self, starting_indexes: &Indexes, others: &[&ActivityCore], exit: &Exit) -> Result<()> {
self.compute_from_stateful(starting_indexes, others, exit)
}
fn compute_rest_part1(&mut self, blocks: &blocks::Vecs, starting_indexes: &Indexes, exit: &Exit) -> Result<()> {
@@ -57,16 +53,16 @@ impl ActivityLike for ActivityBase {
}
impl ActivityLike for ActivityFull {
fn as_base(&self) -> &ActivityBase { &self.inner }
fn as_base_mut(&mut self) -> &mut ActivityBase { &mut self.inner }
fn min_len(&self) -> usize { self.inner.min_len() }
fn truncate_push(&mut self, height: Height, sent: Sats, satblocks_destroyed: Sats, satdays_destroyed: Sats) -> Result<()> {
self.inner.truncate_push(height, sent, satblocks_destroyed, satdays_destroyed)
fn as_core(&self) -> &ActivityCore { &self.inner }
fn as_core_mut(&mut self) -> &mut ActivityCore { &mut self.inner }
fn min_len(&self) -> usize { self.full_min_len() }
fn truncate_push<R: RealizedOps>(&mut self, height: Height, state: &CohortState<R>) -> Result<()> {
self.full_truncate_push(height, state)
}
fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> {
self.inner.validate_computed_versions(base_version)
}
fn compute_from_stateful(&mut self, starting_indexes: &Indexes, others: &[&ActivityBase], exit: &Exit) -> Result<()> {
fn compute_from_stateful(&mut self, starting_indexes: &Indexes, others: &[&ActivityCore], exit: &Exit) -> Result<()> {
self.compute_from_stateful(starting_indexes, others, exit)
}
fn compute_rest_part1(&mut self, blocks: &blocks::Vecs, starting_indexes: &Indexes, exit: &Exit) -> Result<()> {

View File

@@ -2,7 +2,7 @@ use brk_cohort::Filter;
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{
Bitcoin, Cents, Dollars, Height, Indexes, Sats, SatsSigned, StoredF32, StoredI64, StoredU64,
Cents, Dollars, Height, Indexes, Sats, SatsSigned, StoredI64, StoredU64,
Version,
};
use vecdb::AnyStoredVec;
@@ -10,11 +10,11 @@ use vecdb::{Exit, ReadableVec, Rw, StorageMode};
use crate::{blocks, prices};
use crate::internal::{ComputedPerBlock, RollingDeltaExcept1m};
use crate::internal::RollingDeltaExcept1m;
use crate::distribution::metrics::{
ActivityFull, CohortMetricsBase, CostBasis, ImportConfig, OutputsMetrics,
RealizedAdjusted, RealizedFull, RelativeForAll, SupplyMetrics, UnrealizedFull,
ActivityFull, CohortMetricsBase, CostBasis, ImportConfig, OutputsFull,
AdjustedSopr, RealizedFull, RelativeForAll, SupplyFull, UnrealizedFull,
};
/// All-cohort metrics: extended realized + adjusted (as composable add-on),
@@ -24,16 +24,15 @@ use crate::distribution::metrics::{
pub struct AllCohortMetrics<M: StorageMode = Rw> {
#[traversable(skip)]
pub filter: Filter,
pub supply: Box<SupplyMetrics<M>>,
pub outputs: Box<OutputsMetrics<M>>,
pub supply: Box<SupplyFull<M>>,
pub outputs: Box<OutputsFull<M>>,
pub activity: Box<ActivityFull<M>>,
pub realized: Box<RealizedFull<M>>,
pub cost_basis: Box<CostBasis<M>>,
pub unrealized: Box<UnrealizedFull<M>>,
pub adjusted: Box<RealizedAdjusted<M>>,
#[traversable(wrap = "realized/sopr", rename = "adjusted")]
pub asopr: Box<AdjustedSopr<M>>,
pub relative: Box<RelativeForAll<M>>,
pub dormancy: ComputedPerBlock<StoredF32, M>,
pub velocity: ComputedPerBlock<StoredF32, M>,
#[traversable(wrap = "supply", rename = "delta")]
pub supply_delta_extended: RollingDeltaExcept1m<Sats, SatsSigned, M>,
@@ -73,8 +72,6 @@ impl CohortMetricsBase for AllCohortMetrics {
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
}
}
@@ -86,26 +83,24 @@ impl AllCohortMetrics {
/// reference for relative metric lazy vecs in other cohorts.
pub(crate) fn forced_import_with_supply(
cfg: &ImportConfig,
supply: SupplyMetrics,
supply: SupplyFull,
) -> Result<Self> {
let unrealized = UnrealizedFull::forced_import(cfg)?;
let realized = RealizedFull::forced_import(cfg)?;
let adjusted = RealizedAdjusted::forced_import(cfg)?;
let asopr = AdjustedSopr::forced_import(cfg)?;
let relative = RelativeForAll::forced_import(cfg)?;
Ok(Self {
filter: cfg.filter.clone(),
supply: Box::new(supply),
outputs: Box::new(OutputsMetrics::forced_import(cfg)?),
outputs: Box::new(OutputsFull::forced_import(cfg)?),
activity: Box::new(ActivityFull::forced_import(cfg)?),
realized: Box::new(realized),
cost_basis: Box::new(CostBasis::forced_import(cfg)?),
unrealized: Box::new(unrealized),
adjusted: Box::new(adjusted),
asopr: Box::new(asopr),
relative: Box::new(relative),
dormancy: cfg.import("dormancy", Version::ONE)?,
velocity: cfg.import("velocity", Version::ONE)?,
supply_delta_extended: cfg.import("supply_delta", Version::ONE)?,
utxo_count_delta_extended: cfg.import("utxo_count_delta", Version::ONE)?,
})
@@ -131,11 +126,11 @@ impl AllCohortMetrics {
exit,
)?;
self.adjusted.compute_rest_part2(
self.asopr.compute_rest_part2(
blocks,
starting_indexes,
&self.realized.value_created.height,
&self.realized.value_destroyed.height,
&self.realized.minimal.sopr.value_created.raw.height,
&self.realized.minimal.sopr.value_destroyed.raw.height,
up_to_1h_value_created,
up_to_1h_value_destroyed,
exit,
@@ -163,33 +158,9 @@ impl AllCohortMetrics {
exit,
)?;
self.dormancy.height.compute_transform2(
starting_indexes.height,
&self.activity.coindays_destroyed.height,
&self.activity.sent.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.height,
self.activity.compute_rest_part2(
starting_indexes,
&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,
)?;

View File

@@ -7,8 +7,8 @@ use vecdb::{AnyStoredVec, Exit, ReadableVec, Rw, StorageMode};
use crate::{blocks, prices};
use crate::distribution::metrics::{
ActivityBase, CohortMetricsBase, ImportConfig, OutputsMetrics, RealizedBase,
RelativeToAll, SupplyMetrics, UnrealizedBase,
ActivityCore, CohortMetricsBase, ImportConfig, OutputsFull, RealizedCore,
RelativeToAll, SupplyFull, UnrealizedBase,
};
/// Basic cohort metrics: no extensions, with relative (rel_to_all).
@@ -17,17 +17,17 @@ use crate::distribution::metrics::{
pub struct BasicCohortMetrics<M: StorageMode = Rw> {
#[traversable(skip)]
pub filter: Filter,
pub supply: Box<SupplyMetrics<M>>,
pub outputs: Box<OutputsMetrics<M>>,
pub activity: Box<ActivityBase<M>>,
pub realized: Box<RealizedBase<M>>,
pub supply: Box<SupplyFull<M>>,
pub outputs: Box<OutputsFull<M>>,
pub activity: Box<ActivityCore<M>>,
pub realized: Box<RealizedCore<M>>,
pub unrealized: Box<UnrealizedBase<M>>,
pub relative: Box<RelativeToAll<M>>,
}
impl CohortMetricsBase for BasicCohortMetrics {
type ActivityVecs = ActivityBase;
type RealizedVecs = RealizedBase;
type ActivityVecs = ActivityCore;
type RealizedVecs = RealizedCore;
type UnrealizedVecs = UnrealizedBase;
impl_cohort_accessors!();
@@ -45,17 +45,17 @@ impl CohortMetricsBase for BasicCohortMetrics {
impl BasicCohortMetrics {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
let supply = SupplyMetrics::forced_import(cfg)?;
let supply = SupplyFull::forced_import(cfg)?;
let unrealized = UnrealizedBase::forced_import(cfg)?;
let realized = RealizedBase::forced_import(cfg)?;
let realized = RealizedCore::forced_import(cfg)?;
let relative = RelativeToAll::forced_import(cfg)?;
Ok(Self {
filter: cfg.filter.clone(),
supply: Box::new(supply),
outputs: Box::new(OutputsMetrics::forced_import(cfg)?),
activity: Box::new(ActivityBase::forced_import(cfg)?),
outputs: Box::new(OutputsFull::forced_import(cfg)?),
activity: Box::new(ActivityCore::forced_import(cfg)?),
realized: Box::new(realized),
unrealized: Box::new(unrealized),
relative: Box::new(relative),

View File

@@ -7,16 +7,16 @@ use vecdb::{AnyStoredVec, Exit, ReadableVec, Rw, StorageMode};
use crate::{blocks, prices};
use crate::distribution::metrics::{
ActivityCore, CohortMetricsBase, RealizedCore, ImportConfig, OutputsMetrics,
RelativeToAll, SupplyMetrics, UnrealizedCore,
ActivityCore, CohortMetricsBase, RealizedCore, ImportConfig, OutputsFull,
RelativeToAll, SupplyFull, UnrealizedCore,
};
#[derive(Traversable)]
pub struct CoreCohortMetrics<M: StorageMode = Rw> {
#[traversable(skip)]
pub filter: Filter,
pub supply: Box<SupplyMetrics<M>>,
pub outputs: Box<OutputsMetrics<M>>,
pub supply: Box<SupplyFull<M>>,
pub outputs: Box<OutputsFull<M>>,
pub activity: Box<ActivityCore<M>>,
pub realized: Box<RealizedCore<M>>,
pub unrealized: Box<UnrealizedCore<M>>,
@@ -27,8 +27,8 @@ impl CoreCohortMetrics {
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)?),
supply: Box::new(SupplyFull::forced_import(cfg)?),
outputs: Box::new(OutputsFull::forced_import(cfg)?),
activity: Box::new(ActivityCore::forced_import(cfg)?),
realized: Box::new(RealizedCore::forced_import(cfg)?),
unrealized: Box::new(UnrealizedCore::forced_import(cfg)?),
@@ -80,12 +80,12 @@ impl CoreCohortMetrics {
)?;
self.activity.compute_from_stateful(
starting_indexes,
&others.iter().map(|v| &v.activity_base().core).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_base().core).collect::<Vec<_>>(),
&others.iter().map(|v| v.realized_core()).collect::<Vec<_>>(),
exit,
)?;
self.unrealized.compute_from_stateful(
@@ -117,7 +117,7 @@ impl CoreCohortMetrics {
self.realized
.compute_rest_part1(blocks, starting_indexes, exit)?;
self.unrealized.compute_rest(prices, starting_indexes, exit)?;
self.unrealized.compute_rest(blocks, prices, starting_indexes, exit)?;
Ok(())
}

View File

@@ -2,18 +2,18 @@ use brk_cohort::Filter;
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{
Bitcoin, Dollars, Height, Indexes, Sats, SatsSigned, StoredF32, StoredI64, StoredU64, Version,
Dollars, Height, Indexes, Sats, SatsSigned, StoredI64, StoredU64, Version,
};
use vecdb::AnyStoredVec;
use vecdb::{Exit, ReadableVec, Rw, StorageMode};
use crate::{blocks, prices};
use crate::internal::{ComputedPerBlock, RollingDeltaExcept1m};
use crate::internal::RollingDeltaExcept1m;
use crate::distribution::metrics::{
ActivityFull, CohortMetricsBase, CostBasis, ImportConfig, OutputsMetrics,
RealizedFull, RelativeWithExtended, SupplyMetrics, UnrealizedFull,
ActivityFull, CohortMetricsBase, CostBasis, ImportConfig, OutputsFull,
RealizedFull, RelativeWithExtended, SupplyFull, UnrealizedFull,
};
/// Cohort metrics with extended realized + extended cost basis (no adjusted).
@@ -22,15 +22,13 @@ use crate::distribution::metrics::{
pub struct ExtendedCohortMetrics<M: StorageMode = Rw> {
#[traversable(skip)]
pub filter: Filter,
pub supply: Box<SupplyMetrics<M>>,
pub outputs: Box<OutputsMetrics<M>>,
pub supply: Box<SupplyFull<M>>,
pub outputs: Box<OutputsFull<M>>,
pub activity: Box<ActivityFull<M>>,
pub realized: Box<RealizedFull<M>>,
pub cost_basis: Box<CostBasis<M>>,
pub unrealized: Box<UnrealizedFull<M>>,
pub relative: Box<RelativeWithExtended<M>>,
pub dormancy: ComputedPerBlock<StoredF32, M>,
pub velocity: ComputedPerBlock<StoredF32, M>,
#[traversable(wrap = "supply", rename = "delta")]
pub supply_delta_extended: RollingDeltaExcept1m<Sats, SatsSigned, M>,
@@ -70,15 +68,13 @@ impl CohortMetricsBase for ExtendedCohortMetrics {
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
}
}
impl ExtendedCohortMetrics {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
let supply = SupplyMetrics::forced_import(cfg)?;
let supply = SupplyFull::forced_import(cfg)?;
let unrealized = UnrealizedFull::forced_import(cfg)?;
let realized = RealizedFull::forced_import(cfg)?;
@@ -87,14 +83,12 @@ impl ExtendedCohortMetrics {
Ok(Self {
filter: cfg.filter.clone(),
supply: Box::new(supply),
outputs: Box::new(OutputsMetrics::forced_import(cfg)?),
outputs: Box::new(OutputsFull::forced_import(cfg)?),
activity: Box::new(ActivityFull::forced_import(cfg)?),
realized: Box::new(realized),
cost_basis: Box::new(CostBasis::forced_import(cfg)?),
unrealized: Box::new(unrealized),
relative: Box::new(relative),
dormancy: cfg.import("dormancy", Version::ONE)?,
velocity: cfg.import("velocity", Version::ONE)?,
supply_delta_extended: cfg.import("supply_delta", Version::ONE)?,
utxo_count_delta_extended: cfg.import("utxo_count_delta", Version::ONE)?,
})
@@ -142,33 +136,9 @@ impl ExtendedCohortMetrics {
exit,
)?;
self.dormancy.height.compute_transform2(
starting_indexes.height,
&self.activity.coindays_destroyed.height,
&self.activity.sent.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.height,
self.activity.compute_rest_part2(
starting_indexes,
&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,
)?;

View File

@@ -7,7 +7,7 @@ use vecdb::{AnyStoredVec, Exit, ReadableVec, Rw, StorageMode};
use crate::{blocks, prices};
use crate::distribution::metrics::{
ActivityFull, CohortMetricsBase, ImportConfig, RealizedAdjusted,
ActivityFull, CohortMetricsBase, ImportConfig, AdjustedSopr,
RealizedFull, UnrealizedFull,
};
@@ -22,7 +22,8 @@ pub struct ExtendedAdjustedCohortMetrics<M: StorageMode = Rw> {
#[deref_mut]
#[traversable(flatten)]
pub inner: ExtendedCohortMetrics<M>,
pub adjusted: Box<RealizedAdjusted<M>>,
#[traversable(wrap = "realized/sopr", rename = "adjusted")]
pub asopr: Box<AdjustedSopr<M>>,
}
impl CohortMetricsBase for ExtendedAdjustedCohortMetrics {
@@ -48,10 +49,10 @@ impl CohortMetricsBase for ExtendedAdjustedCohortMetrics {
impl ExtendedAdjustedCohortMetrics {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
let inner = ExtendedCohortMetrics::forced_import(cfg)?;
let adjusted = RealizedAdjusted::forced_import(cfg)?;
let asopr = AdjustedSopr::forced_import(cfg)?;
Ok(Self {
inner,
adjusted: Box::new(adjusted),
asopr: Box::new(asopr),
})
}
@@ -76,11 +77,11 @@ impl ExtendedAdjustedCohortMetrics {
exit,
)?;
self.adjusted.compute_rest_part2(
self.asopr.compute_rest_part2(
blocks,
starting_indexes,
&self.inner.realized.value_created.height,
&self.inner.realized.value_destroyed.height,
&self.inner.realized.minimal.sopr.value_created.raw.height,
&self.inner.realized.minimal.sopr.value_destroyed.raw.height,
up_to_1h_value_created,
up_to_1h_value_destroyed,
exit,

View File

@@ -1,41 +1,35 @@
use brk_cohort::Filter;
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Indexes, Version};
use brk_types::Indexes;
use vecdb::{AnyStoredVec, Exit, Rw, StorageMode};
use crate::distribution::metrics::unrealized::UnrealizedMinimal;
use crate::{blocks, prices};
use crate::distribution::metrics::{
ActivityCore, ImportConfig, OutputsMetrics, RealizedMinimal, SupplyMetrics,
ImportConfig, OutputsBase, RealizedMinimal, SupplyBase,
};
/// MinimalCohortMetrics: supply, outputs, sent+ema, realized cap/price/mvrv/profit/loss,
/// supply in profit/loss.
/// MinimalCohortMetrics: supply, outputs, realized cap/price/mvrv/profit/loss + value_created/destroyed.
///
/// Used for type_, amount, and address cohorts.
/// Used for amount_range 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 supply: Box<SupplyBase<M>>,
pub outputs: Box<OutputsBase<M>>,
pub realized: Box<RealizedMinimal<M>>,
pub unrealized: Box<UnrealizedMinimal<M>>,
}
impl MinimalCohortMetrics {
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(ActivityCore::forced_import(cfg)?),
supply: Box::new(SupplyBase::forced_import(cfg)?),
outputs: Box::new(OutputsBase::forced_import(cfg)?),
realized: Box::new(RealizedMinimal::forced_import(cfg)?),
unrealized: Box::new(UnrealizedMinimal::forced_import(cfg)?),
})
}
@@ -43,23 +37,14 @@ 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())
}
pub(crate) fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> {
self.supply.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.unrealized.collect_vecs_mut());
vecs
}
@@ -83,14 +68,6 @@ impl MinimalCohortMetrics {
.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_stateful(
starting_indexes,
&others
@@ -99,14 +76,6 @@ impl MinimalCohortMetrics {
.collect::<Vec<_>>(),
exit,
)?;
self.unrealized.compute_from_sources(
starting_indexes,
&others
.iter()
.map(|v| v.unrealized.as_ref())
.collect::<Vec<_>>(),
exit,
)?;
Ok(())
}
@@ -118,15 +87,8 @@ impl MinimalCohortMetrics {
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
.compute_rest_part1(blocks, starting_indexes, exit)?;
self.realized
.compute_rest_part1(blocks, starting_indexes, exit)?;
self.unrealized
.compute_rest(prices, starting_indexes.height, exit)?;
Ok(())
}

View File

@@ -4,6 +4,7 @@ mod core;
mod extended;
mod extended_adjusted;
mod minimal;
mod r#type;
pub use all::AllCohortMetrics;
pub use basic::BasicCohortMetrics;
@@ -11,3 +12,4 @@ pub use core::CoreCohortMetrics;
pub use extended::ExtendedCohortMetrics;
pub use extended_adjusted::ExtendedAdjustedCohortMetrics;
pub use minimal::MinimalCohortMetrics;
pub use r#type::TypeCohortMetrics;

View File

@@ -0,0 +1,83 @@
use brk_cohort::Filter;
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::Indexes;
use vecdb::{AnyStoredVec, Exit, Rw, StorageMode};
use crate::{blocks, prices};
use crate::distribution::metrics::{
ImportConfig, OutputsBase, RealizedMinimal, SupplyBase, UnrealizedBasic,
};
/// TypeCohortMetrics: supply(base), outputs(base), realized(minimal), unrealized(basic).
///
/// Used for type_ cohorts (p2pkh, p2sh, etc.).
#[derive(Traversable)]
pub struct TypeCohortMetrics<M: StorageMode = Rw> {
#[traversable(skip)]
pub filter: Filter,
pub supply: Box<SupplyBase<M>>,
pub outputs: Box<OutputsBase<M>>,
pub realized: Box<RealizedMinimal<M>>,
pub unrealized: Box<UnrealizedBasic<M>>,
}
impl TypeCohortMetrics {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
Ok(Self {
filter: cfg.filter.clone(),
supply: Box::new(SupplyBase::forced_import(cfg)?),
outputs: Box::new(OutputsBase::forced_import(cfg)?),
realized: Box::new(RealizedMinimal::forced_import(cfg)?),
unrealized: Box::new(UnrealizedBasic::forced_import(cfg)?),
})
}
pub(crate) fn min_stateful_height_len(&self) -> usize {
self.supply
.min_len()
.min(self.outputs.min_len())
.min(self.realized.min_stateful_height_len())
.min(self.unrealized.min_stateful_height_len())
}
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.realized.collect_vecs_mut());
vecs.extend(self.unrealized.collect_vecs_mut());
vecs
}
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.realized
.compute_rest_part1(blocks, starting_indexes, exit)?;
self.unrealized
.compute_rest(blocks, prices, starting_indexes.height, exit)?;
Ok(())
}
pub(crate) fn compute_rest_part2(
&mut self,
prices: &prices::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
self.realized.compute_rest_part2(
prices,
starting_indexes,
&self.supply.total.btc.height,
exit,
)?;
Ok(())
}
}

View File

@@ -7,11 +7,13 @@ use vecdb::{BytesVec, BytesVecValue, Database, ImportableVec};
use crate::{
indexes,
internal::{
AmountPerBlock, AmountPerBlockCumulative, CentsType, ComputedPerBlock,
ComputedPerBlockCumulative, ComputedPerBlockCumulativeSum, RatioPerBlock,
AmountPerBlock, AmountPerBlockCumulative, AmountPerBlockWithSum24h, CentsType, ComputedPerBlock,
ComputedPerBlockCumulative, ComputedPerBlockCumulativeSum, FiatPerBlockWithSum24h,
PerBlockWithSum24h, RatioPerBlock, RollingWindow24hAmountPerBlock,
RollingWindow24hFiatPerBlock, RollingWindow24hPerBlock,
FiatPerBlock, FiatRollingDelta1m, FiatRollingDeltaExcept1m, NumericValue,
PercentPerBlock, PercentRollingWindows, Price, RollingDelta1m, RollingDeltaExcept1m,
RollingWindow24h, RollingWindows, RollingWindowsFrom1w,
RollingWindows, RollingWindowsFrom1w,
},
};
@@ -37,7 +39,9 @@ macro_rules! impl_config_import {
impl_config_import!(
AmountPerBlock,
AmountPerBlockCumulative,
RatioPerBlock,
RollingWindow24hAmountPerBlock,
RatioPerBlock<BasisPoints32>,
RatioPerBlock<BasisPointsSigned32>,
PercentPerBlock<BasisPoints16>,
PercentPerBlock<BasisPoints32>,
PercentPerBlock<BasisPointsSigned32>,
@@ -66,11 +70,40 @@ impl<T: NumericValue + JsonSchema> ConfigImport for RollingWindows<T> {
Self::forced_import(cfg.db, &cfg.name(suffix), cfg.version + offset, cfg.indexes)
}
}
impl<T: NumericValue + JsonSchema> ConfigImport for RollingWindow24h<T> {
impl<T: NumericValue + JsonSchema> ConfigImport for RollingWindow24hPerBlock<T> {
fn config_import(cfg: &ImportConfig, suffix: &str, offset: Version) -> Result<Self> {
Self::forced_import(cfg.db, &cfg.name(suffix), cfg.version + offset, cfg.indexes)
}
}
impl<T: NumericValue + JsonSchema> ConfigImport for PerBlockWithSum24h<T> {
fn config_import(cfg: &ImportConfig, suffix: &str, offset: Version) -> Result<Self> {
Ok(Self {
raw: ComputedPerBlock::config_import(cfg, suffix, offset)?,
sum: RollingWindow24hPerBlock::config_import(cfg, suffix, offset)?,
})
}
}
impl ConfigImport for AmountPerBlockWithSum24h {
fn config_import(cfg: &ImportConfig, suffix: &str, offset: Version) -> Result<Self> {
Ok(Self {
raw: AmountPerBlock::config_import(cfg, suffix, offset)?,
sum: RollingWindow24hAmountPerBlock::config_import(cfg, suffix, offset)?,
})
}
}
impl<C: CentsType> ConfigImport for RollingWindow24hFiatPerBlock<C> {
fn config_import(cfg: &ImportConfig, suffix: &str, offset: Version) -> Result<Self> {
Self::forced_import(cfg.db, &cfg.name(suffix), cfg.version + offset, cfg.indexes)
}
}
impl<C: CentsType> ConfigImport for FiatPerBlockWithSum24h<C> {
fn config_import(cfg: &ImportConfig, suffix: &str, offset: Version) -> Result<Self> {
Ok(Self {
raw: FiatPerBlock::config_import(cfg, suffix, offset)?,
sum: RollingWindow24hFiatPerBlock::config_import(cfg, suffix, offset)?,
})
}
}
impl<T: NumericValue + JsonSchema> ConfigImport for RollingWindowsFrom1w<T> {
fn config_import(cfg: &ImportConfig, suffix: &str, offset: Version) -> Result<Self> {
Self::forced_import(cfg.db, &cfg.name(suffix), cfg.version + offset, cfg.indexes)

View File

@@ -19,10 +19,10 @@ mod activity;
macro_rules! impl_cohort_accessors {
() => {
fn filter(&self) -> &brk_cohort::Filter { &self.filter }
fn supply(&self) -> &$crate::distribution::metrics::SupplyMetrics { &self.supply }
fn supply_mut(&mut self) -> &mut $crate::distribution::metrics::SupplyMetrics { &mut self.supply }
fn outputs(&self) -> &$crate::distribution::metrics::OutputsMetrics { &self.outputs }
fn outputs_mut(&mut self) -> &mut $crate::distribution::metrics::OutputsMetrics { &mut self.outputs }
fn supply(&self) -> &$crate::distribution::metrics::SupplyFull { &self.supply }
fn supply_mut(&mut self) -> &mut $crate::distribution::metrics::SupplyFull { &mut self.supply }
fn outputs(&self) -> &$crate::distribution::metrics::OutputsFull { &self.outputs }
fn outputs_mut(&mut self) -> &mut $crate::distribution::metrics::OutputsFull { &mut self.outputs }
fn activity(&self) -> &Self::ActivityVecs { &self.activity }
fn activity_mut(&mut self) -> &mut Self::ActivityVecs { &mut self.activity }
fn realized(&self) -> &Self::RealizedVecs { &self.realized }
@@ -42,24 +42,24 @@ mod relative;
mod supply;
mod unrealized;
pub use activity::{ActivityBase, ActivityCore, ActivityFull, ActivityLike};
pub use activity::{ActivityCore, ActivityFull, ActivityLike};
pub use cohort::{
AllCohortMetrics, BasicCohortMetrics, CoreCohortMetrics, ExtendedAdjustedCohortMetrics,
ExtendedCohortMetrics, MinimalCohortMetrics,
AllCohortMetrics, BasicCohortMetrics, CoreCohortMetrics,
ExtendedAdjustedCohortMetrics, ExtendedCohortMetrics, MinimalCohortMetrics, TypeCohortMetrics,
};
pub use config::ImportConfig;
pub use cost_basis::CostBasis;
pub use profitability::ProfitabilityMetrics;
pub use outputs::OutputsMetrics;
pub use outputs::{OutputsBase, OutputsFull};
pub use realized::{
RealizedAdjusted, RealizedBase, RealizedCore, RealizedFull, RealizedFullAccum, RealizedLike,
AdjustedSopr, RealizedCore, RealizedFull, RealizedFullAccum, RealizedLike,
RealizedMinimal,
};
pub use relative::{
RelativeForAll, RelativeToAll, RelativeWithExtended,
};
pub use supply::SupplyMetrics;
pub use unrealized::{UnrealizedBase, UnrealizedCore, UnrealizedFull, UnrealizedLike};
pub use supply::{SupplyBase, SupplyFull};
pub use unrealized::{UnrealizedBase, UnrealizedBasic, UnrealizedCore, UnrealizedFull, UnrealizedLike};
use brk_cohort::Filter;
use brk_error::Result;
@@ -72,6 +72,9 @@ pub trait CohortMetricsState {
type Realized: RealizedOps;
}
impl<M: StorageMode> CohortMetricsState for TypeCohortMetrics<M> {
type Realized = MinimalRealizedState;
}
impl<M: StorageMode> CohortMetricsState for MinimalCohortMetrics<M> {
type Realized = MinimalRealizedState;
}
@@ -97,10 +100,10 @@ pub trait CohortMetricsBase: CohortMetricsState<Realized = RealizedState> + Send
type UnrealizedVecs: UnrealizedLike;
fn filter(&self) -> &Filter;
fn supply(&self) -> &SupplyMetrics;
fn supply_mut(&mut self) -> &mut SupplyMetrics;
fn outputs(&self) -> &OutputsMetrics;
fn outputs_mut(&mut self) -> &mut OutputsMetrics;
fn supply(&self) -> &SupplyFull;
fn supply_mut(&mut self) -> &mut SupplyFull;
fn outputs(&self) -> &OutputsFull;
fn outputs_mut(&mut self) -> &mut OutputsFull;
fn activity(&self) -> &Self::ActivityVecs;
fn activity_mut(&mut self) -> &mut Self::ActivityVecs;
fn realized(&self) -> &Self::RealizedVecs;
@@ -108,13 +111,13 @@ pub trait CohortMetricsBase: CohortMetricsState<Realized = RealizedState> + Send
fn unrealized(&self) -> &Self::UnrealizedVecs;
fn unrealized_mut(&mut self) -> &mut Self::UnrealizedVecs;
/// Convenience: access activity as `&ActivityBase` (via `ActivityLike::as_base`).
fn activity_base(&self) -> &ActivityBase { self.activity().as_base() }
fn activity_base_mut(&mut self) -> &mut ActivityBase { self.activity_mut().as_base_mut() }
/// Convenience: access activity as `&ActivityCore` (via `ActivityLike::as_core`).
fn activity_core(&self) -> &ActivityCore { self.activity().as_core() }
fn activity_core_mut(&mut self) -> &mut ActivityCore { self.activity_mut().as_core_mut() }
/// Convenience: access realized as `&RealizedBase` (via `RealizedLike::as_base`).
fn realized_base(&self) -> &RealizedBase { self.realized().as_base() }
fn realized_base_mut(&mut self) -> &mut RealizedBase { self.realized_mut().as_base_mut() }
/// Convenience: access realized as `&RealizedCore` (via `RealizedLike::as_core`).
fn realized_core(&self) -> &RealizedCore { self.realized().as_core() }
fn realized_core_mut(&mut self) -> &mut RealizedCore { self.realized_mut().as_core_mut() }
/// Convenience: access unrealized as `&UnrealizedBase` (via `UnrealizedLike::as_base`).
fn unrealized_base(&self) -> &UnrealizedBase { self.unrealized().as_base() }
@@ -152,18 +155,10 @@ pub trait CohortMetricsBase: CohortMetricsState<Realized = RealizedState> + Send
}
fn truncate_push(&mut self, height: Height, state: &CohortState<RealizedState>) -> Result<()> {
self.supply_mut()
.truncate_push(height, state.supply.value)?;
self.outputs_mut()
.truncate_push(height, state.supply.utxo_count)?;
self.activity_mut().truncate_push(
height,
state.sent,
state.satblocks_destroyed,
state.satdays_destroyed,
)?;
self.realized_mut()
.truncate_push(height, &state.realized)?;
self.supply_mut().truncate_push(height, state)?;
self.outputs_mut().truncate_push(height, state)?;
self.activity_mut().truncate_push(height, state)?;
self.realized_mut().truncate_push(height, state)?;
Ok(())
}
@@ -188,7 +183,7 @@ pub trait CohortMetricsBase: CohortMetricsState<Realized = RealizedState> + Send
.compute_rest_part1(blocks, starting_indexes, exit)?;
self.unrealized_mut()
.compute_rest(prices, starting_indexes, exit)?;
.compute_rest(blocks, prices, starting_indexes, exit)?;
self.unrealized_mut()
.compute_net_sentiment_height(starting_indexes, exit)?;
@@ -215,12 +210,12 @@ pub trait CohortMetricsBase: CohortMetricsState<Realized = RealizedState> + Send
)?;
self.activity_mut().compute_from_stateful(
starting_indexes,
&others.iter().map(|v| v.activity_base()).collect::<Vec<_>>(),
&others.iter().map(|v| v.activity_core()).collect::<Vec<_>>(),
exit,
)?;
self.realized_mut().compute_from_stateful(
starting_indexes,
&others.iter().map(|v| v.realized_base()).collect::<Vec<_>>(),
&others.iter().map(|v| v.realized_core()).collect::<Vec<_>>(),
exit,
)?;
self.unrealized_base_mut().compute_from_stateful(

View File

@@ -1,77 +0,0 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Height, Indexes, StoredI64, StoredU64, Version};
use vecdb::{AnyStoredVec, AnyVec, Exit, Rw, StorageMode, WritableVec};
use crate::{blocks, internal::{ComputedPerBlock, RollingDelta1m}};
use super::ImportConfig;
/// Output metrics for a cohort.
#[derive(Traversable)]
pub struct OutputsMetrics<M: StorageMode = Rw> {
pub utxo_count: ComputedPerBlock<StoredU64, M>,
pub utxo_count_delta: RollingDelta1m<StoredU64, StoredI64, M>,
}
impl OutputsMetrics {
/// Import output metrics from database.
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
Ok(Self {
utxo_count: cfg.import("utxo_count", Version::ZERO)?,
utxo_count_delta: cfg.import("utxo_count_delta", Version::ONE)?,
})
}
/// Get minimum length across height-indexed vectors.
pub(crate) fn min_len(&self) -> usize {
self.utxo_count.height.len()
}
/// Push utxo count to height-indexed vector.
pub(crate) fn truncate_push(&mut self, height: Height, utxo_count: u64) -> Result<()> {
self.utxo_count
.height
.truncate_push(height, StoredU64::from(utxo_count))?;
Ok(())
}
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
vec![&mut self.utxo_count.height as &mut dyn AnyStoredVec]
}
/// Compute aggregate values from separate cohorts.
pub(crate) fn compute_from_stateful(
&mut self,
starting_indexes: &Indexes,
others: &[&Self],
exit: &Exit,
) -> Result<()> {
self.utxo_count.height.compute_sum_of_others(
starting_indexes.height,
&others
.iter()
.map(|v| &v.utxo_count.height)
.collect::<Vec<_>>(),
exit,
)?;
Ok(())
}
/// Compute derived metrics.
pub(crate) fn compute_rest(
&mut self,
blocks: &blocks::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
self.utxo_count_delta.compute(
starting_indexes.height,
&blocks.lookback.height_1m_ago,
&self.utxo_count.height,
exit,
)?;
Ok(())
}
}

View File

@@ -0,0 +1,54 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Height, Indexes, StoredU64, Version};
use vecdb::{AnyStoredVec, AnyVec, Exit, Rw, StorageMode, WritableVec};
use crate::{distribution::state::{CohortState, RealizedOps}, internal::ComputedPerBlock};
use crate::distribution::metrics::ImportConfig;
/// Base output metrics: utxo_count only (1 stored vec).
#[derive(Traversable)]
pub struct OutputsBase<M: StorageMode = Rw> {
pub utxo_count: ComputedPerBlock<StoredU64, M>,
}
impl OutputsBase {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
Ok(Self {
utxo_count: cfg.import("utxo_count", Version::ZERO)?,
})
}
pub(crate) fn min_len(&self) -> usize {
self.utxo_count.height.len()
}
pub(crate) fn truncate_push(&mut self, height: Height, state: &CohortState<impl RealizedOps>) -> Result<()> {
self.utxo_count
.height
.truncate_push(height, StoredU64::from(state.supply.utxo_count))?;
Ok(())
}
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
vec![&mut self.utxo_count.height as &mut dyn AnyStoredVec]
}
pub(crate) fn compute_from_stateful(
&mut self,
starting_indexes: &Indexes,
others: &[&Self],
exit: &Exit,
) -> Result<()> {
self.utxo_count.height.compute_sum_of_others(
starting_indexes.height,
&others
.iter()
.map(|v| &v.utxo_count.height)
.collect::<Vec<_>>(),
exit,
)?;
Ok(())
}
}

View File

@@ -0,0 +1,64 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Indexes, StoredI64, StoredU64, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{AnyStoredVec, Exit, Rw, StorageMode};
use crate::{blocks, internal::RollingDelta1m};
use crate::distribution::metrics::ImportConfig;
use super::OutputsBase;
/// Full output metrics: utxo_count + delta (3 stored vecs).
#[derive(Deref, DerefMut, Traversable)]
pub struct OutputsFull<M: StorageMode = Rw> {
#[deref]
#[deref_mut]
#[traversable(flatten)]
pub base: OutputsBase<M>,
pub utxo_count_delta: RollingDelta1m<StoredU64, StoredI64, M>,
}
impl OutputsFull {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
let base = OutputsBase::forced_import(cfg)?;
let utxo_count_delta = cfg.import("utxo_count_delta", Version::ONE)?;
Ok(Self {
base,
utxo_count_delta,
})
}
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
self.base.collect_vecs_mut()
}
pub(crate) fn compute_from_stateful(
&mut self,
starting_indexes: &Indexes,
others: &[&Self],
exit: &Exit,
) -> Result<()> {
let base_refs: Vec<&OutputsBase> = others.iter().map(|o| &o.base).collect();
self.base.compute_from_stateful(starting_indexes, &base_refs, exit)
}
pub(crate) fn compute_rest(
&mut self,
blocks: &blocks::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
self.utxo_count_delta.compute(
starting_indexes.height,
&blocks.lookback.height_1m_ago,
&self.base.utxo_count.height,
exit,
)?;
Ok(())
}
}

View File

@@ -0,0 +1,5 @@
mod base;
mod full;
pub use base::OutputsBase;
pub use full::OutputsFull;

View File

@@ -11,22 +11,22 @@ use crate::{
use crate::distribution::metrics::ImportConfig;
#[derive(Traversable)]
pub struct RealizedAdjusted<M: StorageMode = Rw> {
pub struct AdjustedSopr<M: StorageMode = Rw> {
pub value_created: ComputedPerBlock<Cents, M>,
pub value_destroyed: ComputedPerBlock<Cents, M>,
pub value_created_sum: RollingWindows<Cents, M>,
pub value_destroyed_sum: RollingWindows<Cents, M>,
pub sopr: RollingWindows<StoredF64, M>,
pub ratio: RollingWindows<StoredF64, M>,
}
impl RealizedAdjusted {
impl AdjustedSopr {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
Ok(RealizedAdjusted {
Ok(Self {
value_created: cfg.import("adjusted_value_created", Version::ZERO)?,
value_destroyed: cfg.import("adjusted_value_destroyed", Version::ZERO)?,
value_created_sum: cfg.import("adjusted_value_created", Version::ONE)?,
value_destroyed_sum: cfg.import("adjusted_value_destroyed", Version::ONE)?,
sopr: cfg.import("adjusted_sopr", Version::ONE)?,
ratio: cfg.import("adjusted_sopr", Version::ONE)?,
})
}
@@ -72,7 +72,7 @@ impl RealizedAdjusted {
// SOPR ratios from rolling sums
for ((sopr, vc), vd) in self
.sopr
.ratio
.as_mut_array()
.into_iter()
.zip(self.value_created_sum.as_array())

View File

@@ -1,106 +0,0 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Height, Indexes, Sats, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{AnyStoredVec, AnyVec, Exit, Rw, StorageMode, WritableVec};
use crate::{
blocks,
distribution::state::RealizedOps,
internal::{ComputedPerBlock, RollingWindow24h},
};
use crate::distribution::metrics::ImportConfig;
use super::RealizedCore;
#[derive(Deref, DerefMut, Traversable)]
pub struct RealizedBase<M: StorageMode = Rw> {
#[deref]
#[deref_mut]
#[traversable(flatten)]
pub core: RealizedCore<M>,
pub sent_in_profit: ComputedPerBlock<Sats, M>,
pub sent_in_loss: ComputedPerBlock<Sats, M>,
pub sent_in_profit_sum: RollingWindow24h<Sats, M>,
pub sent_in_loss_sum: RollingWindow24h<Sats, M>,
}
impl RealizedBase {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
let v1 = Version::ONE;
Ok(Self {
core: RealizedCore::forced_import(cfg)?,
sent_in_profit: cfg.import("sent_in_profit", v1)?,
sent_in_loss: cfg.import("sent_in_loss", v1)?,
sent_in_profit_sum: cfg.import("sent_in_profit", v1)?,
sent_in_loss_sum: cfg.import("sent_in_loss", v1)?,
})
}
pub(crate) fn min_stateful_height_len(&self) -> usize {
self.core
.min_stateful_height_len()
.min(self.sent_in_profit.height.len())
.min(self.sent_in_loss.height.len())
}
pub(crate) fn truncate_push(&mut self, height: Height, state: &impl RealizedOps) -> Result<()> {
self.core.truncate_push(height, state)?;
self.sent_in_profit
.height
.truncate_push(height, state.sent_in_profit())?;
self.sent_in_loss
.height
.truncate_push(height, state.sent_in_loss())?;
Ok(())
}
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
let mut vecs = self.core.collect_vecs_mut();
vecs.push(&mut self.sent_in_profit.height as &mut dyn AnyStoredVec);
vecs.push(&mut self.sent_in_loss.height);
vecs
}
pub(crate) fn compute_from_stateful(
&mut self,
starting_indexes: &Indexes,
others: &[&Self],
exit: &Exit,
) -> Result<()> {
let core_refs: Vec<&RealizedCore> = others.iter().map(|o| &o.core).collect();
self.core
.compute_from_stateful(starting_indexes, &core_refs, exit)?;
sum_others!(self, starting_indexes, others, exit; sent_in_profit.height);
sum_others!(self, starting_indexes, others, exit; sent_in_loss.height);
Ok(())
}
pub(crate) fn compute_rest_part1(
&mut self,
blocks: &blocks::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
self.core.compute_rest_part1(blocks, starting_indexes, exit)?;
self.sent_in_profit_sum.compute_rolling_sum(
starting_indexes.height,
&blocks.lookback.height_24h_ago,
&self.sent_in_profit.height,
exit,
)?;
self.sent_in_loss_sum.compute_rolling_sum(
starting_indexes.height,
&blocks.lookback.height_24h_ago,
&self.sent_in_loss.height,
exit,
)?;
Ok(())
}
}

View File

@@ -8,10 +8,11 @@ use vecdb::{
use crate::{
blocks,
distribution::state::RealizedOps,
distribution::state::{CohortState, RealizedOps},
internal::{
ComputedPerBlock, FiatRollingDelta1m, LazyPerBlock, NegCentsUnsignedToDollars,
RatioCents64, RollingWindow24h,
AmountPerBlockWithSum24h, ComputedPerBlock, FiatRollingDelta1m, LazyPerBlock,
NegCentsUnsignedToDollars, PerBlockWithSum24h, RatioCents64,
RollingWindow24hPerBlock,
},
prices,
};
@@ -20,6 +21,17 @@ use crate::distribution::metrics::ImportConfig;
use super::RealizedMinimal;
#[derive(Traversable)]
pub struct RealizedSoprCore<M: StorageMode = Rw> {
pub ratio: RollingWindow24hPerBlock<StoredF64, M>,
}
#[derive(Traversable)]
pub struct RealizedSentCore<M: StorageMode = Rw> {
pub in_profit: AmountPerBlockWithSum24h<M>,
pub in_loss: AmountPerBlockWithSum24h<M>,
}
#[derive(Deref, DerefMut, Traversable)]
pub struct RealizedCore<M: StorageMode = Rw> {
#[deref]
@@ -27,17 +39,19 @@ pub struct RealizedCore<M: StorageMode = Rw> {
#[traversable(flatten)]
pub minimal: RealizedMinimal<M>,
#[traversable(wrap = "profit", rename = "cumulative")]
pub profit_cumulative: ComputedPerBlock<Cents, M>,
#[traversable(wrap = "loss", rename = "cumulative")]
pub loss_cumulative: ComputedPerBlock<Cents, M>,
#[traversable(wrap = "cap", rename = "delta")]
pub cap_delta: FiatRollingDelta1m<Cents, CentsSigned, M>,
#[traversable(wrap = "loss", rename = "neg")]
pub neg_loss: LazyPerBlock<Dollars, Cents>,
pub net_pnl: ComputedPerBlock<CentsSigned, M>,
pub net_pnl_sum: RollingWindow24h<CentsSigned, M>,
pub value_created: ComputedPerBlock<Cents, M>,
pub value_destroyed: ComputedPerBlock<Cents, M>,
pub value_created_sum: RollingWindow24h<Cents, M>,
pub value_destroyed_sum: RollingWindow24h<Cents, M>,
pub sopr: RollingWindow24h<StoredF64, M>,
pub net_pnl: PerBlockWithSum24h<CentsSigned, M>,
pub sopr: RealizedSoprCore<M>,
pub sent: RealizedSentCore<M>,
}
impl RealizedCore {
@@ -50,55 +64,57 @@ impl RealizedCore {
let neg_realized_loss = LazyPerBlock::from_height_source::<NegCentsUnsignedToDollars>(
&cfg.name("neg_realized_loss"),
cfg.version + Version::ONE,
minimal.loss.height.read_only_boxed_clone(),
minimal.loss.raw.cents.height.read_only_boxed_clone(),
cfg.indexes,
);
let net_realized_pnl = cfg.import("net_realized_pnl", v1)?;
let net_realized_pnl_sum = cfg.import("net_realized_pnl", v1)?;
let value_created = cfg.import("value_created", v0)?;
let value_destroyed = cfg.import("value_destroyed", v0)?;
let value_created_sum = cfg.import("value_created", v1)?;
let value_destroyed_sum = cfg.import("value_destroyed", v1)?;
let sopr = cfg.import("sopr", v1)?;
Ok(Self {
minimal,
profit_cumulative: cfg.import("realized_profit_cumulative", v0)?,
loss_cumulative: cfg.import("realized_loss_cumulative", v0)?,
cap_delta: cfg.import("realized_cap_delta", v1)?,
neg_loss: neg_realized_loss,
net_pnl: net_realized_pnl,
net_pnl_sum: net_realized_pnl_sum,
value_created,
value_destroyed,
value_created_sum,
value_destroyed_sum,
sopr,
net_pnl: cfg.import("net_realized_pnl", v1)?,
sopr: RealizedSoprCore {
ratio: cfg.import("sopr", v1)?,
},
sent: RealizedSentCore {
in_profit: cfg.import("sent_in_profit", v1)?,
in_loss: cfg.import("sent_in_loss", v1)?,
},
})
}
pub(crate) fn min_stateful_height_len(&self) -> usize {
self.minimal
.min_stateful_height_len()
.min(self.value_created.height.len())
.min(self.value_destroyed.height.len())
.min(self.sent.in_profit.raw.sats.height.len())
.min(self.sent.in_loss.raw.sats.height.len())
}
pub(crate) fn truncate_push(&mut self, height: Height, state: &impl RealizedOps) -> Result<()> {
pub(crate) fn truncate_push(&mut self, height: Height, state: &CohortState<impl RealizedOps>) -> Result<()> {
self.minimal.truncate_push(height, state)?;
self.value_created
self.sent
.in_profit
.raw
.sats
.height
.truncate_push(height, state.value_created())?;
self.value_destroyed
.truncate_push(height, state.realized.sent_in_profit())?;
self.sent
.in_loss
.raw
.sats
.height
.truncate_push(height, state.value_destroyed())?;
.truncate_push(height, state.realized.sent_in_loss())?;
Ok(())
}
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
let mut vecs = self.minimal.collect_vecs_mut();
vecs.push(&mut self.value_created.height as &mut dyn AnyStoredVec);
vecs.push(&mut self.value_destroyed.height);
vecs.push(&mut self.sent.in_profit.raw.sats.height as &mut dyn AnyStoredVec);
vecs.push(&mut self.sent.in_profit.raw.cents.height);
vecs.push(&mut self.sent.in_loss.raw.sats.height);
vecs.push(&mut self.sent.in_loss.raw.cents.height);
vecs
}
@@ -112,8 +128,10 @@ impl RealizedCore {
self.minimal
.compute_from_stateful(starting_indexes, &minimal_refs, exit)?;
sum_others!(self, starting_indexes, others, exit; value_created.height);
sum_others!(self, starting_indexes, others, exit; value_destroyed.height);
sum_others!(self, starting_indexes, others, exit; sent.in_profit.raw.sats.height);
sum_others!(self, starting_indexes, others, exit; sent.in_profit.raw.cents.height);
sum_others!(self, starting_indexes, others, exit; sent.in_loss.raw.sats.height);
sum_others!(self, starting_indexes, others, exit; sent.in_loss.raw.cents.height);
Ok(())
}
@@ -127,10 +145,21 @@ impl RealizedCore {
self.minimal
.compute_rest_part1(blocks, starting_indexes, exit)?;
self.net_pnl.height.compute_transform2(
self.profit_cumulative.height.compute_cumulative(
starting_indexes.height,
&self.minimal.profit.height,
&self.minimal.loss.height,
&self.minimal.profit.raw.cents.height,
exit,
)?;
self.loss_cumulative.height.compute_cumulative(
starting_indexes.height,
&self.minimal.loss.raw.cents.height,
exit,
)?;
self.net_pnl.raw.height.compute_transform2(
starting_indexes.height,
&self.minimal.profit.raw.cents.height,
&self.minimal.loss.raw.cents.height,
|(i, profit, loss, ..)| {
(
i,
@@ -157,39 +186,51 @@ impl RealizedCore {
self.cap_delta.compute(
starting_indexes.height,
&blocks.lookback.height_1m_ago,
&self.minimal.cap_cents.height,
&self.minimal.cap.cents.height,
exit,
)?;
self.net_pnl_sum.compute_rolling_sum(
self.net_pnl.sum.compute_rolling_sum(
starting_indexes.height,
&blocks.lookback.height_24h_ago,
&self.net_pnl.height,
exit,
)?;
self.value_created_sum.compute_rolling_sum(
starting_indexes.height,
&blocks.lookback.height_24h_ago,
&self.value_created.height,
exit,
)?;
self.value_destroyed_sum.compute_rolling_sum(
starting_indexes.height,
&blocks.lookback.height_24h_ago,
&self.value_destroyed.height,
&self.net_pnl.raw.height,
exit,
)?;
self.sopr
.ratio
._24h
.compute_binary::<Cents, Cents, RatioCents64>(
starting_indexes.height,
&self.value_created_sum._24h.height,
&self.value_destroyed_sum._24h.height,
&self.minimal.sopr.value_created.sum._24h.height,
&self.minimal.sopr.value_destroyed.sum._24h.height,
exit,
)?;
self.sent
.in_profit
.raw
.compute(prices, starting_indexes.height, exit)?;
self.sent
.in_loss
.raw
.compute(prices, starting_indexes.height, exit)?;
self.sent.in_profit.sum.compute_rolling_sum(
starting_indexes.height,
&blocks.lookback.height_24h_ago,
&self.sent.in_profit.raw.sats.height,
&self.sent.in_profit.raw.cents.height,
exit,
)?;
self.sent.in_loss.sum.compute_rolling_sum(
starting_indexes.height,
&blocks.lookback.height_24h_ago,
&self.sent.in_loss.raw.sats.height,
&self.sent.in_loss.raw.cents.height,
exit,
)?;
Ok(())
}
}

View File

@@ -12,7 +12,7 @@ use vecdb::{
use crate::{
blocks,
distribution::state::RealizedState,
distribution::state::{CohortState, RealizedState},
internal::{
CentsUnsignedToDollars, ComputedPerBlock, ComputedPerBlockCumulative, FiatPerBlock,
FiatRollingDelta1m, FiatRollingDeltaExcept1m, LazyPerBlock, PercentPerBlock,
@@ -25,86 +25,118 @@ use crate::{
use crate::distribution::metrics::ImportConfig;
use super::RealizedBase;
use super::RealizedCore;
#[derive(Traversable)]
pub struct RealizedProfit<M: StorageMode = Rw> {
pub rel_to_rcap: PercentPerBlock<BasisPoints32, M>,
pub value_created: ComputedPerBlock<Cents, M>,
pub value_destroyed: ComputedPerBlock<Cents, M>,
pub value_created_sum: RollingWindows<Cents, M>,
pub value_destroyed_sum: RollingWindows<Cents, M>,
pub flow: LazyPerBlock<Dollars, Cents>,
#[traversable(rename = "sum")]
pub sum_extended: RollingWindowsFrom1w<Cents, M>,
}
#[derive(Traversable)]
pub struct RealizedLoss<M: StorageMode = Rw> {
pub rel_to_rcap: PercentPerBlock<BasisPoints32, M>,
pub value_created: ComputedPerBlock<Cents, M>,
pub value_destroyed: ComputedPerBlock<Cents, M>,
pub value_created_sum: RollingWindows<Cents, M>,
pub value_destroyed_sum: RollingWindows<Cents, M>,
pub capitulation_flow: LazyPerBlock<Dollars, Cents>,
#[traversable(rename = "sum")]
pub sum_extended: RollingWindowsFrom1w<Cents, M>,
}
#[derive(Traversable)]
pub struct RealizedGrossPnl<M: StorageMode = Rw> {
#[traversable(flatten)]
pub value: FiatPerBlock<Cents, M>,
pub sum: RollingWindows<Cents, M>,
pub sell_side_risk_ratio: PercentRollingWindows<BasisPoints32, M>,
}
#[derive(Traversable)]
pub struct RealizedNetPnl<M: StorageMode = Rw> {
pub rel_to_rcap: PercentPerBlock<BasisPointsSigned32, M>,
pub cumulative: ComputedPerBlock<CentsSigned, M>,
#[traversable(rename = "sum")]
pub sum_extended: RollingWindowsFrom1w<CentsSigned, M>,
pub delta: FiatRollingDelta1m<CentsSigned, CentsSigned, M>,
#[traversable(rename = "delta")]
pub delta_extended: FiatRollingDeltaExcept1m<CentsSigned, CentsSigned, M>,
pub change_1m_rel_to_rcap: PercentPerBlock<BasisPointsSigned32, M>,
pub change_1m_rel_to_mcap: PercentPerBlock<BasisPointsSigned32, M>,
}
#[derive(Traversable)]
pub struct RealizedSopr<M: StorageMode = Rw> {
#[traversable(rename = "value_created_sum")]
pub value_created_sum_extended: RollingWindowsFrom1w<Cents, M>,
#[traversable(rename = "value_destroyed_sum")]
pub value_destroyed_sum_extended: RollingWindowsFrom1w<Cents, M>,
#[traversable(rename = "ratio")]
pub ratio_extended: RollingWindowsFrom1w<StoredF64, M>,
}
#[derive(Traversable)]
pub struct RealizedSentFull<M: StorageMode = Rw> {
#[traversable(wrap = "in_profit", rename = "sum")]
pub in_profit_sum_extended: RollingWindowsFrom1w<Sats, M>,
#[traversable(wrap = "in_loss", rename = "sum")]
pub in_loss_sum_extended: RollingWindowsFrom1w<Sats, M>,
}
#[derive(Traversable)]
pub struct RealizedPeakRegret<M: StorageMode = Rw> {
#[traversable(flatten)]
pub value: ComputedPerBlockCumulative<Cents, M>,
pub rel_to_rcap: PercentPerBlock<BasisPoints32, M>,
}
#[derive(Traversable)]
pub struct RealizedInvestor<M: StorageMode = Rw> {
pub price: Price<ComputedPerBlock<Cents, M>>,
pub price_ratio: RatioPerBlock<BasisPoints32, M>,
pub lower_price_band: Price<ComputedPerBlock<Cents, M>>,
pub upper_price_band: Price<ComputedPerBlock<Cents, M>>,
pub cap_raw: M::Stored<BytesVec<Height, CentsSquaredSats>>,
pub price_ratio_percentiles: RatioPerBlockPercentiles<M>,
}
#[derive(Deref, DerefMut, Traversable)]
pub struct RealizedFull<M: StorageMode = Rw> {
#[deref]
#[deref_mut]
#[traversable(flatten)]
pub base: RealizedBase<M>,
pub core: RealizedCore<M>,
pub gross_pnl: FiatPerBlock<Cents, M>,
pub profit: RealizedProfit<M>,
pub loss: RealizedLoss<M>,
pub gross_pnl: RealizedGrossPnl<M>,
pub net_pnl: RealizedNetPnl<M>,
pub sopr: RealizedSopr<M>,
pub sent: RealizedSentFull<M>,
pub peak_regret: RealizedPeakRegret<M>,
pub investor: RealizedInvestor<M>,
pub profit_rel_to_rcap: PercentPerBlock<BasisPoints32, M>,
pub loss_rel_to_rcap: PercentPerBlock<BasisPoints32, M>,
pub net_pnl_rel_to_rcap: PercentPerBlock<BasisPointsSigned32, M>,
pub profit_value_created: ComputedPerBlock<Cents, M>,
pub profit_value_destroyed: ComputedPerBlock<Cents, M>,
pub loss_value_created: ComputedPerBlock<Cents, M>,
pub loss_value_destroyed: ComputedPerBlock<Cents, M>,
pub profit_value_created_sum: RollingWindows<Cents, M>,
pub profit_value_destroyed_sum: RollingWindows<Cents, M>,
pub loss_value_created_sum: RollingWindows<Cents, M>,
pub loss_value_destroyed_sum: RollingWindows<Cents, M>,
pub capitulation_flow: LazyPerBlock<Dollars, Cents>,
pub profit_flow: LazyPerBlock<Dollars, Cents>,
pub gross_pnl_sum: RollingWindows<Cents, M>,
pub net_pnl_cumulative: ComputedPerBlock<CentsSigned, M>,
#[traversable(rename = "net_pnl_sum")]
pub net_pnl_sum_extended: RollingWindowsFrom1w<CentsSigned, M>,
pub net_pnl_delta: FiatRollingDelta1m<CentsSigned, CentsSigned, M>,
#[traversable(rename = "net_pnl_delta")]
pub net_pnl_delta_extended: FiatRollingDeltaExcept1m<CentsSigned, CentsSigned, M>,
pub net_pnl_change_1m_rel_to_rcap: PercentPerBlock<BasisPointsSigned32, M>,
pub net_pnl_change_1m_rel_to_mcap: PercentPerBlock<BasisPointsSigned32, M>,
#[traversable(rename = "cap_delta")]
pub cap_delta_extended: FiatRollingDeltaExcept1m<Cents, CentsSigned, M>,
pub investor_price: Price<ComputedPerBlock<Cents, M>>,
pub investor_price_ratio: RatioPerBlock<M>,
pub lower_price_band: Price<ComputedPerBlock<Cents, M>>,
pub upper_price_band: Price<ComputedPerBlock<Cents, M>>,
pub cap_raw: M::Stored<BytesVec<Height, CentsSats>>,
pub investor_cap_raw: M::Stored<BytesVec<Height, CentsSquaredSats>>,
pub sell_side_risk_ratio: PercentRollingWindows<BasisPoints32, M>,
pub peak_regret: ComputedPerBlockCumulative<Cents, M>,
pub peak_regret_rel_to_rcap: PercentPerBlock<BasisPoints32, M>,
pub cap_rel_to_own_mcap: PercentPerBlock<BasisPoints32, M>,
#[traversable(rename = "profit_sum")]
pub profit_sum_extended: RollingWindowsFrom1w<Cents, M>,
#[traversable(rename = "loss_sum")]
pub loss_sum_extended: RollingWindowsFrom1w<Cents, M>,
pub profit_to_loss_ratio: RollingWindows<StoredF64, M>,
#[traversable(rename = "value_created_sum")]
pub value_created_sum_extended: RollingWindowsFrom1w<Cents, M>,
#[traversable(rename = "value_destroyed_sum")]
pub value_destroyed_sum_extended: RollingWindowsFrom1w<Cents, M>,
#[traversable(rename = "sopr")]
pub sopr_extended: RollingWindowsFrom1w<StoredF64, M>,
#[traversable(wrap = "cap", rename = "delta")]
pub cap_delta_extended: FiatRollingDeltaExcept1m<Cents, CentsSigned, M>,
#[traversable(rename = "sent_in_profit_sum")]
pub sent_in_profit_sum_extended: RollingWindowsFrom1w<Sats, M>,
#[traversable(rename = "sent_in_loss_sum")]
pub sent_in_loss_sum_extended: RollingWindowsFrom1w<Sats, M>,
#[traversable(wrap = "cap", rename = "raw")]
pub cap_raw: M::Stored<BytesVec<Height, CentsSats>>,
#[traversable(wrap = "cap", rename = "rel_to_own_mcap")]
pub cap_rel_to_own_mcap: PercentPerBlock<BasisPoints32, M>,
#[traversable(wrap = "price_ratio", rename = "percentiles")]
pub price_ratio_percentiles: RatioPerBlockPercentiles<M>,
#[traversable(wrap = "price_ratio", rename = "std_dev")]
pub price_ratio_std_dev: RatioPerBlockStdDevBands<M>,
pub investor_price_ratio_percentiles: RatioPerBlockPercentiles<M>,
}
impl RealizedFull {
@@ -112,111 +144,120 @@ impl RealizedFull {
let v0 = Version::ZERO;
let v1 = Version::ONE;
let base = RealizedBase::forced_import(cfg)?;
let core = RealizedCore::forced_import(cfg)?;
let gross_pnl = cfg.import("realized_gross_pnl", v0)?;
let profit_value_created = cfg.import("profit_value_created", v0)?;
// Profit
let profit_value_destroyed: ComputedPerBlock<Cents> =
cfg.import("profit_value_destroyed", v0)?;
let loss_value_created = cfg.import("loss_value_created", v0)?;
let loss_value_destroyed: ComputedPerBlock<Cents> =
cfg.import("loss_value_destroyed", v0)?;
let profit_value_created_sum = cfg.import("profit_value_created", v1)?;
let profit_value_destroyed_sum = cfg.import("profit_value_destroyed", v1)?;
let loss_value_created_sum = cfg.import("loss_value_created", v1)?;
let loss_value_destroyed_sum = cfg.import("loss_value_destroyed", v1)?;
let capitulation_flow = LazyPerBlock::from_computed::<CentsUnsignedToDollars>(
&cfg.name("capitulation_flow"),
cfg.version,
loss_value_destroyed.height.read_only_boxed_clone(),
&loss_value_destroyed,
);
let profit_flow = LazyPerBlock::from_computed::<CentsUnsignedToDollars>(
&cfg.name("profit_flow"),
cfg.version,
profit_value_destroyed.height.read_only_boxed_clone(),
&profit_value_destroyed,
);
let profit = RealizedProfit {
rel_to_rcap: cfg.import("realized_profit_rel_to_realized_cap", Version::new(2))?,
value_created: cfg.import("profit_value_created", v0)?,
value_destroyed: profit_value_destroyed,
value_created_sum: cfg.import("profit_value_created", v1)?,
value_destroyed_sum: cfg.import("profit_value_destroyed", v1)?,
flow: profit_flow,
sum_extended: cfg.import("realized_profit", v1)?,
};
let gross_pnl_sum = cfg.import("gross_pnl_sum", Version::ONE)?;
// Loss
let loss_value_destroyed: ComputedPerBlock<Cents> =
cfg.import("loss_value_destroyed", v0)?;
let capitulation_flow = LazyPerBlock::from_computed::<CentsUnsignedToDollars>(
&cfg.name("capitulation_flow"),
cfg.version,
loss_value_destroyed.height.read_only_boxed_clone(),
&loss_value_destroyed,
);
let loss = RealizedLoss {
rel_to_rcap: cfg.import("realized_loss_rel_to_realized_cap", Version::new(2))?,
value_created: cfg.import("loss_value_created", v0)?,
value_destroyed: loss_value_destroyed,
value_created_sum: cfg.import("loss_value_created", v1)?,
value_destroyed_sum: cfg.import("loss_value_destroyed", v1)?,
capitulation_flow,
sum_extended: cfg.import("realized_loss", v1)?,
};
let investor_price = cfg.import("investor_price", v0)?;
let investor_price_ratio = cfg.import("investor_price", v0)?;
let lower_price_band = cfg.import("lower_price_band", v0)?;
let upper_price_band = cfg.import("upper_price_band", v0)?;
// Gross PnL
let gross_pnl = RealizedGrossPnl {
value: cfg.import("realized_gross_pnl", v0)?,
sum: cfg.import("gross_pnl_sum", v1)?,
sell_side_risk_ratio: cfg.import("sell_side_risk_ratio", Version::new(2))?,
};
let cap_raw = cfg.import("cap_raw", v0)?;
let investor_cap_raw = cfg.import("investor_cap_raw", v0)?;
// Net PnL
let net_pnl = RealizedNetPnl {
rel_to_rcap: cfg
.import("net_realized_pnl_rel_to_realized_cap", Version::new(2))?,
cumulative: cfg.import("net_realized_pnl_cumulative", v1)?,
sum_extended: cfg.import("net_realized_pnl", v1)?,
delta: cfg.import("net_pnl_delta", Version::new(5))?,
delta_extended: cfg.import("net_pnl_delta", Version::new(5))?,
change_1m_rel_to_rcap: cfg
.import("net_pnl_change_1m_rel_to_realized_cap", Version::new(4))?,
change_1m_rel_to_mcap: cfg
.import("net_pnl_change_1m_rel_to_market_cap", Version::new(4))?,
};
let sell_side_risk_ratio = cfg.import("sell_side_risk_ratio", Version::new(2))?;
// SOPR
let sopr = RealizedSopr {
value_created_sum_extended: cfg.import("value_created", v1)?,
value_destroyed_sum_extended: cfg.import("value_destroyed", v1)?,
ratio_extended: cfg.import("sopr", v1)?,
};
let peak_regret = cfg.import("realized_peak_regret", Version::new(2))?;
let peak_regret_rel_to_realized_cap =
cfg.import("realized_peak_regret_rel_to_realized_cap", Version::new(2))?;
// Sent
let sent = RealizedSentFull {
in_profit_sum_extended: cfg.import("sent_in_profit", v1)?,
in_loss_sum_extended: cfg.import("sent_in_loss", v1)?,
};
// Peak regret
let peak_regret = RealizedPeakRegret {
value: cfg.import("realized_peak_regret", Version::new(2))?,
rel_to_rcap: cfg
.import("realized_peak_regret_rel_to_realized_cap", Version::new(2))?,
};
// Investor
let investor = RealizedInvestor {
price: cfg.import("investor_price", v0)?,
price_ratio: cfg.import("investor_price", v0)?,
lower_price_band: cfg.import("lower_price_band", v0)?,
upper_price_band: cfg.import("upper_price_band", v0)?,
cap_raw: cfg.import("investor_cap_raw", v0)?,
price_ratio_percentiles: RatioPerBlockPercentiles::forced_import(
cfg.db,
&cfg.name("investor_price"),
cfg.version,
cfg.indexes,
)?,
};
// Price ratio stats
let realized_price_name = cfg.name("realized_price");
let realized_price_version = cfg.version + v1;
let investor_price_name = cfg.name("investor_price");
let investor_price_version = cfg.version;
let realized_profit_rel_to_realized_cap =
cfg.import("realized_profit_rel_to_realized_cap", Version::new(2))?;
let realized_loss_rel_to_realized_cap =
cfg.import("realized_loss_rel_to_realized_cap", Version::new(2))?;
let net_realized_pnl_rel_to_realized_cap =
cfg.import("net_realized_pnl_rel_to_realized_cap", Version::new(2))?;
let value_created_sum_extended = cfg.import("value_created", v1)?;
let value_destroyed_sum_extended = cfg.import("value_destroyed", v1)?;
let sopr_extended = cfg.import("sopr", v1)?;
Ok(Self {
base,
core,
profit,
loss,
gross_pnl,
profit_rel_to_rcap: realized_profit_rel_to_realized_cap,
loss_rel_to_rcap: realized_loss_rel_to_realized_cap,
net_pnl_rel_to_rcap: net_realized_pnl_rel_to_realized_cap,
profit_value_created,
profit_value_destroyed,
loss_value_created,
loss_value_destroyed,
profit_value_created_sum,
profit_value_destroyed_sum,
loss_value_created_sum,
loss_value_destroyed_sum,
capitulation_flow,
profit_flow,
gross_pnl_sum,
net_pnl_cumulative: cfg.import("net_realized_pnl_cumulative", Version::ONE)?,
net_pnl_sum_extended: cfg.import("net_realized_pnl", Version::ONE)?,
net_pnl_delta: cfg.import("net_pnl_delta", Version::new(5))?,
net_pnl_delta_extended: cfg.import("net_pnl_delta", Version::new(5))?,
net_pnl_change_1m_rel_to_rcap: cfg
.import("net_pnl_change_1m_rel_to_realized_cap", Version::new(4))?,
net_pnl_change_1m_rel_to_mcap: cfg
.import("net_pnl_change_1m_rel_to_market_cap", Version::new(4))?,
cap_delta_extended: cfg.import("realized_cap_delta", Version::new(5))?,
investor_price,
investor_price_ratio,
lower_price_band,
upper_price_band,
cap_raw,
investor_cap_raw,
sell_side_risk_ratio,
net_pnl,
sopr,
sent,
peak_regret,
peak_regret_rel_to_rcap: peak_regret_rel_to_realized_cap,
cap_rel_to_own_mcap: cfg.import("realized_cap_rel_to_own_market_cap", v1)?,
profit_sum_extended: cfg.import("realized_profit", v1)?,
loss_sum_extended: cfg.import("realized_loss", v1)?,
investor,
profit_to_loss_ratio: cfg.import("realized_profit_to_loss_ratio", v1)?,
value_created_sum_extended,
value_destroyed_sum_extended,
sopr_extended,
sent_in_profit_sum_extended: cfg.import("sent_in_profit", v1)?,
sent_in_loss_sum_extended: cfg.import("sent_in_loss", v1)?,
cap_delta_extended: cfg.import("realized_cap_delta", Version::new(5))?,
cap_raw: cfg.import("cap_raw", v0)?,
cap_rel_to_own_mcap: cfg.import("realized_cap_rel_to_own_market_cap", v1)?,
price_ratio_percentiles: RatioPerBlockPercentiles::forced_import(
cfg.db,
&realized_price_name,
@@ -229,76 +270,82 @@ impl RealizedFull {
realized_price_version,
cfg.indexes,
)?,
investor_price_ratio_percentiles: RatioPerBlockPercentiles::forced_import(
cfg.db,
&investor_price_name,
investor_price_version,
cfg.indexes,
)?,
})
}
pub(crate) fn min_stateful_height_len(&self) -> usize {
self.base
self.core
.min_stateful_height_len()
.min(self.profit_value_created.height.len())
.min(self.profit_value_destroyed.height.len())
.min(self.loss_value_created.height.len())
.min(self.loss_value_destroyed.height.len())
.min(self.investor_price.cents.height.len())
.min(self.profit.value_created.height.len())
.min(self.profit.value_destroyed.height.len())
.min(self.loss.value_created.height.len())
.min(self.loss.value_destroyed.height.len())
.min(self.investor.price.cents.height.len())
.min(self.cap_raw.len())
.min(self.investor_cap_raw.len())
.min(self.peak_regret.height.len())
.min(self.investor.cap_raw.len())
.min(self.peak_regret.value.height.len())
}
pub(crate) fn truncate_push(&mut self, height: Height, state: &RealizedState) -> Result<()> {
self.base.truncate_push(height, state)?;
self.profit_value_created
pub(crate) fn truncate_push(
&mut self,
height: Height,
state: &CohortState<RealizedState>,
) -> Result<()> {
self.core.truncate_push(height, state)?;
self.profit
.value_created
.height
.truncate_push(height, state.profit_value_created())?;
self.profit_value_destroyed
.truncate_push(height, state.realized.profit_value_created())?;
self.profit
.value_destroyed
.height
.truncate_push(height, state.profit_value_destroyed())?;
self.loss_value_created
.truncate_push(height, state.realized.profit_value_destroyed())?;
self.loss
.value_created
.height
.truncate_push(height, state.loss_value_created())?;
self.loss_value_destroyed
.truncate_push(height, state.realized.loss_value_created())?;
self.loss
.value_destroyed
.height
.truncate_push(height, state.loss_value_destroyed())?;
self.investor_price
.truncate_push(height, state.realized.loss_value_destroyed())?;
self.investor
.price
.cents
.height
.truncate_push(height, state.investor_price())?;
self.cap_raw.truncate_push(height, state.cap_raw())?;
self.investor_cap_raw
.truncate_push(height, state.investor_cap_raw())?;
.truncate_push(height, state.realized.investor_price())?;
self.cap_raw
.truncate_push(height, state.realized.cap_raw())?;
self.investor
.cap_raw
.truncate_push(height, state.realized.investor_cap_raw())?;
self.peak_regret
.value
.height
.truncate_push(height, state.peak_regret())?;
.truncate_push(height, state.realized.peak_regret())?;
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.profit_value_created.height as &mut dyn AnyStoredVec);
vecs.push(&mut self.profit_value_destroyed.height);
vecs.push(&mut self.loss_value_created.height);
vecs.push(&mut self.loss_value_destroyed.height);
vecs.push(&mut self.investor_price.cents.height);
let mut vecs = self.core.collect_vecs_mut();
vecs.push(&mut self.profit.value_created.height as &mut dyn AnyStoredVec);
vecs.push(&mut self.profit.value_destroyed.height);
vecs.push(&mut self.loss.value_created.height);
vecs.push(&mut self.loss.value_destroyed.height);
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.height);
vecs.push(&mut self.investor.cap_raw as &mut dyn AnyStoredVec);
vecs.push(&mut self.peak_regret.value.height);
vecs
}
pub(crate) fn compute_from_stateful(
&mut self,
starting_indexes: &Indexes,
others: &[&RealizedBase],
others: &[&RealizedCore],
exit: &Exit,
) -> Result<()> {
self.base
self.core
.compute_from_stateful(starting_indexes, others, exit)?;
Ok(())
@@ -309,20 +356,26 @@ impl RealizedFull {
accum: &RealizedFullAccum,
height: Height,
) -> Result<()> {
self.profit_value_created
self.profit
.value_created
.height
.truncate_push(height, accum.profit_value_created)?;
self.profit_value_destroyed
self.profit
.value_destroyed
.height
.truncate_push(height, accum.profit_value_destroyed)?;
self.loss_value_created
self.loss
.value_created
.height
.truncate_push(height, accum.loss_value_created)?;
self.loss_value_destroyed
self.loss
.value_destroyed
.height
.truncate_push(height, accum.loss_value_destroyed)?;
self.cap_raw.truncate_push(height, accum.cap_raw)?;
self.investor_cap_raw
self.cap_raw
.truncate_push(height, accum.cap_raw)?;
self.investor
.cap_raw
.truncate_push(height, accum.investor_cap_raw)?;
let investor_price = {
@@ -333,12 +386,14 @@ impl RealizedFull {
Cents::new((accum.investor_cap_raw / cap) as u64)
}
};
self.investor_price
self.investor
.price
.cents
.height
.truncate_push(height, investor_price)?;
self.peak_regret
.value
.height
.truncate_push(height, accum.peak_regret)?;
@@ -351,16 +406,17 @@ impl RealizedFull {
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
self.base
self.core
.compute_rest_part1(blocks, starting_indexes, exit)?;
self.net_pnl_cumulative.height.compute_cumulative(
self.net_pnl.cumulative.height.compute_cumulative(
starting_indexes.height,
&self.base.core.net_pnl.height,
&self.core.net_pnl.raw.height,
exit,
)?;
self.peak_regret
.value
.compute_rest(starting_indexes.height, exit)?;
Ok(())
}
@@ -374,7 +430,7 @@ impl RealizedFull {
height_to_market_cap: &impl ReadableVec<Height, Dollars>,
exit: &Exit,
) -> Result<()> {
self.base.core.compute_rest_part2(
self.core.compute_rest_part2(
blocks,
prices,
starting_indexes,
@@ -384,33 +440,36 @@ impl RealizedFull {
let window_starts = blocks.lookback.window_starts();
// Extended rolling sum (1w, 1m, 1y) for net_realized_pnl
self.net_pnl_sum_extended.compute_rolling_sum(
// Net PnL rolling sums (1w, 1m, 1y)
self.net_pnl.sum_extended.compute_rolling_sum(
starting_indexes.height,
&window_starts,
&self.base.core.net_pnl.height,
&self.core.net_pnl.raw.height,
exit,
)?;
// Extended rolling windows (1w, 1m, 1y) for value_created/destroyed/sopr
self.value_created_sum_extended.compute_rolling_sum(
// SOPR: value created/destroyed rolling sums and ratios
self.sopr.value_created_sum_extended.compute_rolling_sum(
starting_indexes.height,
&window_starts,
&self.base.core.value_created.height,
exit,
)?;
self.value_destroyed_sum_extended.compute_rolling_sum(
starting_indexes.height,
&window_starts,
&self.base.core.value_destroyed.height,
&self.core.minimal.sopr.value_created.raw.height,
exit,
)?;
self.sopr
.value_destroyed_sum_extended
.compute_rolling_sum(
starting_indexes.height,
&window_starts,
&self.core.minimal.sopr.value_destroyed.raw.height,
exit,
)?;
for ((sopr, vc), vd) in self
.sopr_extended
.sopr
.ratio_extended
.as_mut_array()
.into_iter()
.zip(self.value_created_sum_extended.as_array())
.zip(self.value_destroyed_sum_extended.as_array())
.zip(self.sopr.value_created_sum_extended.as_array())
.zip(self.sopr.value_destroyed_sum_extended.as_array())
{
sopr.compute_binary::<Cents, Cents, RatioCents64>(
starting_indexes.height,
@@ -420,108 +479,113 @@ impl RealizedFull {
)?;
}
// Realized P/L rel to realized cap
self.profit_rel_to_rcap
// Profit/loss/net_pnl rel to realized cap
self.profit
.rel_to_rcap
.compute_binary::<Cents, Cents, RatioCentsBp32>(
starting_indexes.height,
&self.base.core.minimal.profit.height,
&self.base.core.minimal.cap_cents.height,
&self.core.minimal.profit.raw.cents.height,
&self.core.minimal.cap.cents.height,
exit,
)?;
self.loss_rel_to_rcap
self.loss
.rel_to_rcap
.compute_binary::<Cents, Cents, RatioCentsBp32>(
starting_indexes.height,
&self.base.core.minimal.loss.height,
&self.base.core.minimal.cap_cents.height,
&self.core.minimal.loss.raw.cents.height,
&self.core.minimal.cap.cents.height,
exit,
)?;
self.net_pnl_rel_to_rcap
self.net_pnl
.rel_to_rcap
.compute_binary::<CentsSigned, Cents, RatioCentsSignedCentsBps32>(
starting_indexes.height,
&self.base.core.net_pnl.height,
&self.base.core.minimal.cap_cents.height,
&self.core.net_pnl.raw.height,
&self.core.minimal.cap.cents.height,
exit,
)?;
// Sent in profit/loss extended rolling sums (1w, 1m, 1y)
self.sent_in_profit_sum_extended.compute_rolling_sum(
// Sent rolling sums (1w, 1m, 1y)
self.sent.in_profit_sum_extended.compute_rolling_sum(
starting_indexes.height,
&window_starts,
&self.base.sent_in_profit.height,
&self.core.sent.in_profit.raw.sats.height,
exit,
)?;
self.sent_in_loss_sum_extended.compute_rolling_sum(
self.sent.in_loss_sum_extended.compute_rolling_sum(
starting_indexes.height,
&window_starts,
&self.base.sent_in_loss.height,
&self.core.sent.in_loss.raw.sats.height,
exit,
)?;
// Profit/loss value created/destroyed rolling sums
self.profit_value_created_sum.compute_rolling_sum(
self.profit.value_created_sum.compute_rolling_sum(
starting_indexes.height,
&window_starts,
&self.profit_value_created.height,
&self.profit.value_created.height,
exit,
)?;
self.profit_value_destroyed_sum.compute_rolling_sum(
self.profit.value_destroyed_sum.compute_rolling_sum(
starting_indexes.height,
&window_starts,
&self.profit_value_destroyed.height,
&self.profit.value_destroyed.height,
exit,
)?;
self.loss_value_created_sum.compute_rolling_sum(
self.loss.value_created_sum.compute_rolling_sum(
starting_indexes.height,
&window_starts,
&self.loss_value_created.height,
&self.loss.value_created.height,
exit,
)?;
self.loss_value_destroyed_sum.compute_rolling_sum(
self.loss.value_destroyed_sum.compute_rolling_sum(
starting_indexes.height,
&window_starts,
&self.loss_value_destroyed.height,
&self.loss.value_destroyed.height,
exit,
)?;
// Gross PnL
self.gross_pnl.cents.height.compute_add(
self.gross_pnl.value.cents.height.compute_add(
starting_indexes.height,
&self.base.core.minimal.profit.height,
&self.base.core.minimal.loss.height,
&self.core.minimal.profit.raw.cents.height,
&self.core.minimal.loss.raw.cents.height,
exit,
)?;
self.gross_pnl_sum.compute_rolling_sum(
self.gross_pnl.sum.compute_rolling_sum(
starting_indexes.height,
&window_starts,
&self.gross_pnl.cents.height,
&self.gross_pnl.value.cents.height,
exit,
)?;
// Net PnL delta (1m base + 24h/1w/1y extended)
self.net_pnl_delta.compute(
self.net_pnl.delta.compute(
starting_indexes.height,
&blocks.lookback.height_1m_ago,
&self.net_pnl_cumulative.height,
&self.net_pnl.cumulative.height,
exit,
)?;
self.net_pnl_delta_extended.compute(
self.net_pnl.delta_extended.compute(
starting_indexes.height,
&window_starts,
&self.net_pnl_cumulative.height,
&self.net_pnl.cumulative.height,
exit,
)?;
self.net_pnl_change_1m_rel_to_rcap
self.net_pnl
.change_1m_rel_to_rcap
.compute_binary::<CentsSigned, Cents, RatioCentsSignedCentsBps32>(
starting_indexes.height,
&self.net_pnl_delta.change_1m.cents.height,
&self.base.core.minimal.cap_cents.height,
&self.net_pnl.delta.change_1m.cents.height,
&self.core.minimal.cap.cents.height,
exit,
)?;
self.net_pnl_change_1m_rel_to_mcap
self.net_pnl
.change_1m_rel_to_mcap
.compute_binary::<CentsSigned, Dollars, RatioCentsSignedDollarsBps32>(
starting_indexes.height,
&self.net_pnl_delta.change_1m.cents.height,
&self.net_pnl.delta.change_1m.cents.height,
height_to_market_cap,
exit,
)?;
@@ -530,85 +594,95 @@ impl RealizedFull {
self.cap_delta_extended.compute(
starting_indexes.height,
&window_starts,
&self.base.core.minimal.cap_cents.height,
&self.core.minimal.cap.cents.height,
exit,
)?;
// Peak regret
self.peak_regret_rel_to_rcap
// Peak regret rel to rcap
self.peak_regret
.rel_to_rcap
.compute_binary::<Cents, Cents, RatioCentsBp32>(
starting_indexes.height,
&self.peak_regret.height,
&self.base.core.minimal.cap_cents.height,
&self.peak_regret.value.height,
&self.core.minimal.cap.cents.height,
exit,
)?;
// Investor price ratio and price bands
self.investor_price_ratio.compute_ratio(
// Investor price ratio and bands
self.investor.price_ratio.compute_ratio(
starting_indexes,
&prices.price.cents.height,
&self.investor_price.cents.height,
&self.investor.price.cents.height,
exit,
)?;
self.lower_price_band.cents.height.compute_transform2(
starting_indexes.height,
&self.base.core.minimal.price.cents.height,
&self.investor_price.cents.height,
|(i, rp, ip, ..)| {
let rp = rp.as_u128();
let ip = ip.as_u128();
if ip == 0 {
(i, Cents::ZERO)
} else {
(i, Cents::from(rp * rp / ip))
}
},
exit,
)?;
self.investor
.lower_price_band
.cents
.height
.compute_transform2(
starting_indexes.height,
&self.core.minimal.price.cents.height,
&self.investor.price.cents.height,
|(i, rp, ip, ..)| {
let rp = rp.as_u128();
let ip = ip.as_u128();
if ip == 0 {
(i, Cents::ZERO)
} else {
(i, Cents::from(rp * rp / ip))
}
},
exit,
)?;
self.upper_price_band.cents.height.compute_transform2(
starting_indexes.height,
&self.investor_price.cents.height,
&self.base.core.minimal.price.cents.height,
|(i, ip, rp, ..)| {
let ip = ip.as_u128();
let rp = rp.as_u128();
if rp == 0 {
(i, Cents::ZERO)
} else {
(i, Cents::from(ip * ip / rp))
}
},
exit,
)?;
self.investor
.upper_price_band
.cents
.height
.compute_transform2(
starting_indexes.height,
&self.investor.price.cents.height,
&self.core.minimal.price.cents.height,
|(i, ip, rp, ..)| {
let ip = ip.as_u128();
let rp = rp.as_u128();
if rp == 0 {
(i, Cents::ZERO)
} else {
(i, Cents::from(ip * ip / rp))
}
},
exit,
)?;
// Sell-side risk ratios
for (ssrr, rv) in self
.gross_pnl
.sell_side_risk_ratio
.as_mut_array()
.into_iter()
.zip(self.gross_pnl_sum.as_array())
.zip(self.gross_pnl.sum.as_array())
{
ssrr.compute_binary::<Cents, Cents, RatioCentsBp32>(
starting_indexes.height,
&rv.height,
&self.base.core.minimal.cap_cents.height,
&self.core.minimal.cap.cents.height,
exit,
)?;
}
// Extended: realized profit/loss rolling sums (1w, 1m, 1y)
self.profit_sum_extended.compute_rolling_sum(
// Profit/loss sum extended (1w, 1m, 1y)
self.profit.sum_extended.compute_rolling_sum(
starting_indexes.height,
&window_starts,
&self.base.core.minimal.profit.height,
&self.core.minimal.profit.raw.cents.height,
exit,
)?;
self.loss_sum_extended.compute_rolling_sum(
self.loss.sum_extended.compute_rolling_sum(
starting_indexes.height,
&window_starts,
&self.base.core.minimal.loss.height,
&self.core.minimal.loss.raw.cents.height,
exit,
)?;
@@ -616,7 +690,7 @@ impl RealizedFull {
self.cap_rel_to_own_mcap
.compute_binary::<Dollars, Dollars, RatioDollarsBp32>(
starting_indexes.height,
&self.base.core.minimal.cap.height,
&self.core.minimal.cap.usd.height,
height_to_market_cap,
exit,
)?;
@@ -626,16 +700,16 @@ impl RealizedFull {
._24h
.compute_binary::<Cents, Cents, RatioCents64>(
starting_indexes.height,
&self.base.core.minimal.profit_sum._24h.height,
&self.base.core.minimal.loss_sum._24h.height,
&self.core.minimal.profit.sum._24h.cents.height,
&self.core.minimal.loss.sum._24h.cents.height,
exit,
)?;
for ((ratio, profit), loss) in self
.profit_to_loss_ratio
.as_mut_array_from_1w()
.into_iter()
.zip(self.profit_sum_extended.as_array())
.zip(self.loss_sum_extended.as_array())
.zip(self.profit.sum_extended.as_array())
.zip(self.loss.sum_extended.as_array())
{
ratio.compute_binary::<Cents, Cents, RatioCents64>(
starting_indexes.height,
@@ -645,29 +719,30 @@ impl RealizedFull {
)?;
}
// Price ratio: percentiles and std dev bands
self.price_ratio_percentiles.compute(
blocks,
starting_indexes,
exit,
&self.base.core.minimal.price_ratio.ratio.height,
&self.base.core.minimal.price.cents.height,
&self.core.minimal.price_ratio.ratio.height,
&self.core.minimal.price.cents.height,
)?;
self.price_ratio_std_dev.compute(
blocks,
starting_indexes,
exit,
&self.base.core.minimal.price_ratio.ratio.height,
&self.base.core.minimal.price.cents.height,
&self.core.minimal.price_ratio.ratio.height,
&self.core.minimal.price.cents.height,
)?;
// Investor price: percentiles
let investor_price = &self.investor_price.cents.height;
self.investor_price_ratio_percentiles.compute(
// Investor price ratio: percentiles
let investor_price = &self.investor.price.cents.height;
self.investor.price_ratio_percentiles.compute(
blocks,
starting_indexes,
exit,
&self.investor_price_ratio.ratio.height,
&self.investor.price_ratio.ratio.height,
investor_price,
)?;

View File

@@ -1,18 +1,19 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{
BasisPoints32, Bitcoin, Cents, Dollars, Height, Indexes, Sats, StoredF32, Version,
BasisPoints32, BasisPointsSigned32, Bitcoin, Cents, Height, Indexes, Sats, StoredF32,
Version,
};
use vecdb::{
AnyStoredVec, AnyVec, Exit, ReadableCloneableVec, ReadableVec, Rw, StorageMode, WritableVec,
AnyStoredVec, AnyVec, Exit, ReadableVec, Rw, StorageMode, WritableVec,
};
use crate::{
blocks,
distribution::state::RealizedOps,
distribution::state::{CohortState, RealizedOps},
internal::{
CentsUnsignedToDollars, ComputedPerBlock, ComputedPerBlockCumulative, Identity,
LazyPerBlock, Price, RatioPerBlock, RollingWindow24h,
ComputedPerBlock, FiatPerBlock, FiatPerBlockWithSum24h, Identity, LazyPerBlock,
PerBlockWithSum24h, Price, RatioPerBlock,
},
prices,
};
@@ -20,77 +21,92 @@ use crate::{
use crate::distribution::metrics::ImportConfig;
#[derive(Traversable)]
pub struct RealizedMinimal<M: StorageMode = Rw> {
pub cap_cents: ComputedPerBlock<Cents, M>,
pub profit: ComputedPerBlockCumulative<Cents, M>,
pub loss: ComputedPerBlockCumulative<Cents, M>,
pub cap: LazyPerBlock<Dollars, Cents>,
pub price: Price<ComputedPerBlock<Cents, M>>,
pub price_ratio: RatioPerBlock<M>,
pub mvrv: LazyPerBlock<StoredF32>,
pub struct RealizedSoprMinimal<M: StorageMode = Rw> {
pub value_created: PerBlockWithSum24h<Cents, M>,
pub value_destroyed: PerBlockWithSum24h<Cents, M>,
}
pub profit_sum: RollingWindow24h<Cents, M>,
pub loss_sum: RollingWindow24h<Cents, M>,
/// Minimal realized metrics: cap (fiat), profit/loss (fiat + 24h sum),
/// price, mvrv, nupl, sopr (value_created/destroyed with 24h sums).
#[derive(Traversable)]
pub struct RealizedMinimal<M: StorageMode = Rw> {
pub cap: FiatPerBlock<Cents, M>,
pub profit: FiatPerBlockWithSum24h<Cents, M>,
pub loss: FiatPerBlockWithSum24h<Cents, M>,
pub price: Price<ComputedPerBlock<Cents, M>>,
pub price_ratio: RatioPerBlock<BasisPoints32, M>,
pub mvrv: LazyPerBlock<StoredF32>,
pub nupl: RatioPerBlock<BasisPointsSigned32, M>,
pub sopr: RealizedSoprMinimal<M>,
}
impl RealizedMinimal {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
let realized_cap_cents: ComputedPerBlock<Cents> =
cfg.import("realized_cap_cents", Version::ZERO)?;
let realized_cap = LazyPerBlock::from_computed::<CentsUnsignedToDollars>(
&cfg.name("realized_cap"),
cfg.version,
realized_cap_cents.height.read_only_boxed_clone(),
&realized_cap_cents,
);
let v1 = Version::ONE;
let realized_profit = cfg.import("realized_profit", Version::ZERO)?;
let realized_loss = cfg.import("realized_loss", Version::ZERO)?;
let cap: FiatPerBlock<Cents> = cfg.import("realized_cap", Version::ZERO)?;
let realized_price = cfg.import("realized_price", Version::ONE)?;
let realized_price_ratio: RatioPerBlock = cfg.import("realized_price", Version::ONE)?;
let realized_price = cfg.import("realized_price", v1)?;
let realized_price_ratio: RatioPerBlock = cfg.import("realized_price", v1)?;
let mvrv = LazyPerBlock::from_lazy::<Identity<StoredF32>, BasisPoints32>(
&cfg.name("mvrv"),
cfg.version,
&realized_price_ratio.ratio,
);
let realized_profit_sum = cfg.import("realized_profit", Version::ONE)?;
let realized_loss_sum = cfg.import("realized_loss", Version::ONE)?;
let nupl = cfg.import("nupl", v1)?;
Ok(Self {
cap_cents: realized_cap_cents,
profit: realized_profit,
loss: realized_loss,
cap: realized_cap,
cap,
profit: cfg.import("realized_profit", v1)?,
loss: cfg.import("realized_loss", v1)?,
price: realized_price,
price_ratio: realized_price_ratio,
mvrv,
profit_sum: realized_profit_sum,
loss_sum: realized_loss_sum,
nupl,
sopr: RealizedSoprMinimal {
value_created: cfg.import("value_created", v1)?,
value_destroyed: cfg.import("value_destroyed", v1)?,
},
})
}
pub(crate) fn min_stateful_height_len(&self) -> usize {
self.cap_cents
self.cap
.cents
.height
.len()
.min(self.profit.height.len())
.min(self.loss.height.len())
.min(self.profit.raw.cents.height.len())
.min(self.loss.raw.cents.height.len())
.min(self.sopr.value_created.raw.height.len())
.min(self.sopr.value_destroyed.raw.height.len())
}
pub(crate) fn truncate_push(&mut self, height: Height, state: &impl RealizedOps) -> Result<()> {
self.cap_cents.height.truncate_push(height, state.cap())?;
self.profit.height.truncate_push(height, state.profit())?;
self.loss.height.truncate_push(height, state.loss())?;
pub(crate) fn truncate_push(&mut self, height: Height, state: &CohortState<impl RealizedOps>) -> Result<()> {
self.cap.cents.height.truncate_push(height, state.realized.cap())?;
self.profit.raw.cents.height.truncate_push(height, state.realized.profit())?;
self.loss.raw.cents.height.truncate_push(height, state.realized.loss())?;
self.sopr
.value_created
.raw
.height
.truncate_push(height, state.realized.value_created())?;
self.sopr
.value_destroyed
.raw
.height
.truncate_push(height, state.realized.value_destroyed())?;
Ok(())
}
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
vec![
&mut self.cap_cents.height as &mut dyn AnyStoredVec,
&mut self.profit.height,
&mut self.loss.height,
&mut self.cap.cents.height as &mut dyn AnyStoredVec,
&mut self.profit.raw.cents.height,
&mut self.loss.raw.cents.height,
&mut self.sopr.value_created.raw.height,
&mut self.sopr.value_destroyed.raw.height,
]
}
@@ -100,9 +116,11 @@ impl RealizedMinimal {
others: &[&Self],
exit: &Exit,
) -> Result<()> {
sum_others!(self, starting_indexes, others, exit; cap_cents.height);
sum_others!(self, starting_indexes, others, exit; profit.height);
sum_others!(self, starting_indexes, others, exit; loss.height);
sum_others!(self, starting_indexes, others, exit; cap.cents.height);
sum_others!(self, starting_indexes, others, exit; profit.raw.cents.height);
sum_others!(self, starting_indexes, others, exit; loss.raw.cents.height);
sum_others!(self, starting_indexes, others, exit; sopr.value_created.raw.height);
sum_others!(self, starting_indexes, others, exit; sopr.value_destroyed.raw.height);
Ok(())
}
@@ -112,18 +130,28 @@ impl RealizedMinimal {
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
self.profit.compute_rest(starting_indexes.height, exit)?;
self.loss.compute_rest(starting_indexes.height, exit)?;
self.profit_sum.compute_rolling_sum(
self.profit.sum.compute_rolling_sum(
starting_indexes.height,
&blocks.lookback.height_24h_ago,
&self.profit.height,
&self.profit.raw.cents.height,
exit,
)?;
self.loss_sum.compute_rolling_sum(
self.loss.sum.compute_rolling_sum(
starting_indexes.height,
&blocks.lookback.height_24h_ago,
&self.loss.height,
&self.loss.raw.cents.height,
exit,
)?;
self.sopr.value_created.sum.compute_rolling_sum(
starting_indexes.height,
&blocks.lookback.height_24h_ago,
&self.sopr.value_created.raw.height,
exit,
)?;
self.sopr.value_destroyed.sum.compute_rolling_sum(
starting_indexes.height,
&blocks.lookback.height_24h_ago,
&self.sopr.value_destroyed.raw.height,
exit,
)?;
Ok(())
@@ -138,7 +166,7 @@ impl RealizedMinimal {
) -> Result<()> {
self.price.cents.height.compute_transform2(
starting_indexes.height,
&self.cap_cents.height,
&self.cap.cents.height,
height_to_supply,
|(i, cap_cents, supply, ..)| {
let cap = cap_cents.as_u128();
@@ -159,6 +187,23 @@ impl RealizedMinimal {
exit,
)?;
self.nupl.bps.height.compute_transform2(
starting_indexes.height,
&prices.price.cents.height,
&self.price.cents.height,
|(i, price, realized_price, ..)| {
let p = price.as_u128();
if p == 0 {
(i, BasisPointsSigned32::ZERO)
} else {
let rp = realized_price.as_u128();
let nupl_bps = ((p as i128 - rp as i128) * 10000) / p as i128;
(i, BasisPointsSigned32::from(nupl_bps as i32))
}
},
exit,
)?;
Ok(())
}
}

View File

@@ -1,11 +1,9 @@
mod adjusted;
mod base;
mod core;
mod full;
mod minimal;
pub use adjusted::RealizedAdjusted;
pub use base::RealizedBase;
pub use adjusted::AdjustedSopr;
pub use self::core::RealizedCore;
pub use full::{RealizedFull, RealizedFullAccum};
pub use minimal::RealizedMinimal;
@@ -14,53 +12,53 @@ use brk_error::Result;
use brk_types::{Height, Indexes};
use vecdb::Exit;
use crate::{blocks, distribution::state::RealizedState};
use crate::{blocks, distribution::state::{CohortState, RealizedState}};
/// Polymorphic dispatch for realized metric types.
///
/// Both `RealizedBase` and `RealizedFull` have the same inherent methods
/// Both `RealizedCore` and `RealizedFull` have the same inherent methods
/// but with different behavior (Full checks/pushes more fields).
/// This trait enables `CohortMetricsBase` to dispatch correctly via associated type.
pub trait RealizedLike: Send + Sync {
fn as_base(&self) -> &RealizedBase;
fn as_base_mut(&mut self) -> &mut RealizedBase;
fn as_core(&self) -> &RealizedCore;
fn as_core_mut(&mut self) -> &mut RealizedCore;
fn min_stateful_height_len(&self) -> usize;
fn truncate_push(&mut self, height: Height, state: &RealizedState) -> Result<()>;
fn truncate_push(&mut self, height: Height, state: &CohortState<RealizedState>) -> Result<()>;
fn compute_rest_part1(&mut self, blocks: &blocks::Vecs, starting_indexes: &Indexes, exit: &Exit) -> Result<()>;
fn compute_from_stateful(
&mut self,
starting_indexes: &Indexes,
others: &[&RealizedBase],
others: &[&RealizedCore],
exit: &Exit,
) -> Result<()>;
}
impl RealizedLike for RealizedBase {
fn as_base(&self) -> &RealizedBase { self }
fn as_base_mut(&mut self) -> &mut RealizedBase { self }
impl RealizedLike for RealizedCore {
fn as_core(&self) -> &RealizedCore { self }
fn as_core_mut(&mut self) -> &mut RealizedCore { self }
fn min_stateful_height_len(&self) -> usize { self.min_stateful_height_len() }
fn truncate_push(&mut self, height: Height, state: &RealizedState) -> Result<()> {
fn truncate_push(&mut self, height: Height, state: &CohortState<RealizedState>) -> Result<()> {
self.truncate_push(height, state)
}
fn compute_rest_part1(&mut self, blocks: &blocks::Vecs, starting_indexes: &Indexes, exit: &Exit) -> Result<()> {
self.compute_rest_part1(blocks, starting_indexes, exit)
}
fn compute_from_stateful(&mut self, starting_indexes: &Indexes, others: &[&RealizedBase], exit: &Exit) -> Result<()> {
fn compute_from_stateful(&mut self, starting_indexes: &Indexes, others: &[&RealizedCore], exit: &Exit) -> Result<()> {
self.compute_from_stateful(starting_indexes, others, exit)
}
}
impl RealizedLike for RealizedFull {
fn as_base(&self) -> &RealizedBase { &self.base }
fn as_base_mut(&mut self) -> &mut RealizedBase { &mut self.base }
fn as_core(&self) -> &RealizedCore { &self.core }
fn as_core_mut(&mut self) -> &mut RealizedCore { &mut self.core }
fn min_stateful_height_len(&self) -> usize { self.min_stateful_height_len() }
fn truncate_push(&mut self, height: Height, state: &RealizedState) -> Result<()> {
fn truncate_push(&mut self, height: Height, state: &CohortState<RealizedState>) -> Result<()> {
self.truncate_push(height, state)
}
fn compute_rest_part1(&mut self, blocks: &blocks::Vecs, starting_indexes: &Indexes, exit: &Exit) -> Result<()> {
self.compute_rest_part1(blocks, starting_indexes, exit)
}
fn compute_from_stateful(&mut self, starting_indexes: &Indexes, others: &[&RealizedBase], exit: &Exit) -> Result<()> {
fn compute_from_stateful(&mut self, starting_indexes: &Indexes, others: &[&RealizedCore], exit: &Exit) -> Result<()> {
self.compute_from_stateful(starting_indexes, others, exit)
}
}

View File

@@ -10,8 +10,11 @@ use crate::distribution::metrics::{ImportConfig, UnrealizedCore};
/// Extended relative metrics for own market cap (extended && rel_to_all).
#[derive(Traversable)]
pub struct RelativeExtendedOwnMarketCap<M: StorageMode = Rw> {
#[traversable(wrap = "unrealized/profit", rename = "rel_to_own_market_cap")]
pub unrealized_profit_rel_to_own_market_cap: PercentPerBlock<BasisPoints16, M>,
#[traversable(wrap = "unrealized/loss", rename = "rel_to_own_market_cap")]
pub unrealized_loss_rel_to_own_market_cap: PercentPerBlock<BasisPoints32, M>,
#[traversable(wrap = "unrealized/net_pnl", rename = "rel_to_own_market_cap")]
pub net_unrealized_pnl_rel_to_own_market_cap: PercentPerBlock<BasisPointsSigned32, M>,
}
@@ -39,14 +42,14 @@ impl RelativeExtendedOwnMarketCap {
self.unrealized_profit_rel_to_own_market_cap
.compute_binary::<Dollars, Dollars, RatioDollarsBp16>(
max_from,
&unrealized.profit.usd.height,
&unrealized.profit.raw.usd.height,
own_market_cap,
exit,
)?;
self.unrealized_loss_rel_to_own_market_cap
.compute_binary::<Dollars, Dollars, RatioDollarsBp32>(
max_from,
&unrealized.loss.usd.height,
&unrealized.loss.raw.usd.height,
own_market_cap,
exit,
)?;

View File

@@ -10,8 +10,11 @@ use crate::distribution::metrics::{ImportConfig, UnrealizedCore};
/// Extended relative metrics for own total unrealized PnL (extended only).
#[derive(Traversable)]
pub struct RelativeExtendedOwnPnl<M: StorageMode = Rw> {
#[traversable(wrap = "unrealized/profit", rename = "rel_to_own_gross_pnl")]
pub unrealized_profit_rel_to_own_gross_pnl: PercentPerBlock<BasisPoints16, M>,
#[traversable(wrap = "unrealized/loss", rename = "rel_to_own_gross_pnl")]
pub unrealized_loss_rel_to_own_gross_pnl: PercentPerBlock<BasisPoints16, M>,
#[traversable(wrap = "unrealized/net_pnl", rename = "rel_to_own_gross_pnl")]
pub net_unrealized_pnl_rel_to_own_gross_pnl: PercentPerBlock<BasisPointsSigned32, M>,
}
@@ -39,14 +42,14 @@ impl RelativeExtendedOwnPnl {
self.unrealized_profit_rel_to_own_gross_pnl
.compute_binary::<Dollars, Dollars, RatioDollarsBp16>(
max_from,
&unrealized.profit.usd.height,
&unrealized.profit.raw.usd.height,
gross_pnl_usd,
exit,
)?;
self.unrealized_loss_rel_to_own_gross_pnl
.compute_binary::<Dollars, Dollars, RatioDollarsBp16>(
max_from,
&unrealized.loss.usd.height,
&unrealized.loss.raw.usd.height,
gross_pnl_usd,
exit,
)?;

View File

@@ -1,44 +1,31 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{BasisPoints16, BasisPointsSigned32, Dollars, Height, Sats, StoredF32, Version};
use vecdb::{Exit, ReadableCloneableVec, ReadableVec, Rw, StorageMode};
use brk_types::{BasisPoints16, Dollars, Height, Sats, Version};
use vecdb::{Exit, ReadableVec, Rw, StorageMode};
use crate::internal::{
Bps32ToFloat, LazyPerBlock, PercentPerBlock, RatioDollarsBp16, RatioDollarsBps32, RatioSatsBp16,
use crate::{
distribution::metrics::{ImportConfig, UnrealizedCore},
internal::{PercentPerBlock, RatioDollarsBp16, RatioSatsBp16},
};
use crate::distribution::metrics::{ImportConfig, UnrealizedCore};
/// Full relative metrics (sth/lth/all tier).
#[derive(Traversable)]
pub struct RelativeFull<M: StorageMode = Rw> {
#[traversable(wrap = "supply/in_profit", rename = "rel_to_own_supply")]
pub supply_in_profit_rel_to_own_supply: PercentPerBlock<BasisPoints16, M>,
#[traversable(wrap = "supply/in_loss", rename = "rel_to_own_supply")]
pub supply_in_loss_rel_to_own_supply: PercentPerBlock<BasisPoints16, M>,
#[traversable(wrap = "unrealized/profit", rename = "rel_to_market_cap")]
pub unrealized_profit_rel_to_market_cap: PercentPerBlock<BasisPoints16, M>,
#[traversable(wrap = "unrealized/loss", rename = "rel_to_market_cap")]
pub unrealized_loss_rel_to_market_cap: PercentPerBlock<BasisPoints16, M>,
pub net_unrealized_pnl_rel_to_market_cap: PercentPerBlock<BasisPointsSigned32, M>,
pub nupl: LazyPerBlock<StoredF32, BasisPointsSigned32>,
}
impl RelativeFull {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
let v1 = Version::ONE;
let v2 = Version::new(2);
let v3 = Version::new(3);
let net_unrealized_pnl_rel_to_market_cap: PercentPerBlock<BasisPointsSigned32> =
cfg.import("net_unrealized_pnl_rel_to_market_cap", v3)?;
let nupl = LazyPerBlock::from_computed::<Bps32ToFloat>(
&cfg.name("nupl"),
cfg.version + v3,
net_unrealized_pnl_rel_to_market_cap
.bps
.height
.read_only_boxed_clone(),
&net_unrealized_pnl_rel_to_market_cap.bps,
);
Ok(Self {
supply_in_profit_rel_to_own_supply: cfg
@@ -48,8 +35,6 @@ impl RelativeFull {
.import("unrealized_profit_rel_to_market_cap", v2)?,
unrealized_loss_rel_to_market_cap: cfg
.import("unrealized_loss_rel_to_market_cap", v2)?,
net_unrealized_pnl_rel_to_market_cap,
nupl,
})
}
@@ -79,21 +64,14 @@ impl RelativeFull {
self.unrealized_profit_rel_to_market_cap
.compute_binary::<Dollars, Dollars, RatioDollarsBp16>(
max_from,
&unrealized.profit.usd.height,
&unrealized.profit.raw.usd.height,
market_cap,
exit,
)?;
self.unrealized_loss_rel_to_market_cap
.compute_binary::<Dollars, Dollars, RatioDollarsBp16>(
max_from,
&unrealized.loss.usd.height,
market_cap,
exit,
)?;
self.net_unrealized_pnl_rel_to_market_cap
.compute_binary::<Dollars, Dollars, RatioDollarsBps32>(
max_from,
&unrealized.net_pnl.usd.height,
&unrealized.loss.raw.usd.height,
market_cap,
exit,
)?;

View File

@@ -10,8 +10,11 @@ use crate::distribution::metrics::ImportConfig;
/// Relative-to-all metrics (not present for the "all" cohort itself).
#[derive(Traversable)]
pub struct RelativeToAll<M: StorageMode = Rw> {
#[traversable(wrap = "supply", rename = "rel_to_circulating_supply")]
pub supply_rel_to_circulating_supply: PercentPerBlock<BasisPoints16, M>,
#[traversable(wrap = "supply/in_profit", rename = "rel_to_circulating_supply")]
pub supply_in_profit_rel_to_circulating_supply: PercentPerBlock<BasisPoints16, M>,
#[traversable(wrap = "supply/in_loss", rename = "rel_to_circulating_supply")]
pub supply_in_loss_rel_to_circulating_supply: PercentPerBlock<BasisPoints16, M>,
}

View File

@@ -1,27 +1,25 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Height, Indexes, Sats, SatsSigned, Version};
use crate::{blocks, prices};
use brk_types::{Height, Indexes, Version};
use vecdb::{AnyStoredVec, AnyVec, Exit, Rw, StorageMode, WritableVec};
use crate::{distribution::state::{CohortState, RealizedOps}, prices};
use crate::internal::{
AmountPerBlock, HalveCents, HalveDollars, HalveSats, HalveSatsToBitcoin,
LazyAmountPerBlock, RollingDelta1m,
LazyAmountPerBlock,
};
use super::ImportConfig;
use crate::distribution::metrics::ImportConfig;
/// Supply metrics for a cohort.
/// Base supply metrics: total supply only (2 stored vecs).
#[derive(Traversable)]
pub struct SupplyMetrics<M: StorageMode = Rw> {
pub struct SupplyBase<M: StorageMode = Rw> {
pub total: AmountPerBlock<M>,
pub halved: LazyAmountPerBlock,
pub delta: RollingDelta1m<Sats, SatsSigned, M>,
}
impl SupplyMetrics {
/// Import supply metrics from database.
impl SupplyBase {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
let supply = cfg.import("supply", Version::ZERO)?;
@@ -32,23 +30,18 @@ impl SupplyMetrics {
HalveDollars,
>(&cfg.name("supply_halved"), &supply, cfg.version);
let delta = cfg.import("supply_delta", Version::ONE)?;
Ok(Self {
total: supply,
halved: supply_halved,
delta,
})
}
/// Get minimum length across height-indexed vectors.
pub(crate) fn min_len(&self) -> usize {
self.total.sats.height.len()
}
/// Push supply state values to height-indexed vectors.
pub(crate) fn truncate_push(&mut self, height: Height, supply: Sats) -> Result<()> {
self.total.sats.height.truncate_push(height, supply)?;
pub(crate) fn truncate_push(&mut self, height: Height, state: &CohortState<impl RealizedOps>) -> Result<()> {
self.total.sats.height.truncate_push(height, state.supply.value)?;
Ok(())
}
@@ -59,7 +52,6 @@ impl SupplyMetrics {
]
}
/// Eagerly compute USD height values from sats × price.
pub(crate) fn compute(
&mut self,
prices: &prices::Vecs,
@@ -69,13 +61,6 @@ impl SupplyMetrics {
self.total.compute(prices, max_from, exit)
}
/// Validate computed versions against base version.
pub(crate) fn validate_computed_versions(&mut self, _base_version: Version) -> Result<()> {
// Validation logic for computed vecs
Ok(())
}
/// Compute aggregate values from separate cohorts.
pub(crate) fn compute_from_stateful(
&mut self,
starting_indexes: &Indexes,
@@ -92,19 +77,4 @@ impl SupplyMetrics {
)?;
Ok(())
}
/// Compute derived vecs from existing height data.
pub(crate) fn compute_rest_part1(
&mut self,
blocks: &blocks::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
self.delta.compute(
starting_indexes.height,
&blocks.lookback.height_1m_ago,
&self.total.sats.height,
exit,
)
}
}

View File

@@ -0,0 +1,63 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Indexes, Sats, SatsSigned, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{AnyStoredVec, Exit, Rw, StorageMode};
use crate::{blocks, internal::RollingDelta1m};
use crate::distribution::metrics::ImportConfig;
use super::SupplyBase;
/// Full supply metrics: total + delta (4 stored vecs).
#[derive(Deref, DerefMut, Traversable)]
pub struct SupplyFull<M: StorageMode = Rw> {
#[deref]
#[deref_mut]
#[traversable(flatten)]
pub base: SupplyBase<M>,
pub delta: RollingDelta1m<Sats, SatsSigned, M>,
}
impl SupplyFull {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
let base = SupplyBase::forced_import(cfg)?;
let delta = cfg.import("supply_delta", Version::ONE)?;
Ok(Self { base, delta })
}
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
self.base.collect_vecs_mut()
}
pub(crate) fn validate_computed_versions(&mut self, _base_version: Version) -> Result<()> {
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)
}
pub(crate) fn compute_rest_part1(
&mut self,
blocks: &blocks::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
self.delta.compute(
starting_indexes.height,
&blocks.lookback.height_1m_ago,
&self.base.total.sats.height,
exit,
)
}
}

View File

@@ -0,0 +1,5 @@
mod base;
mod full;
pub use base::SupplyBase;
pub use full::SupplyFull;

View File

@@ -4,7 +4,7 @@ use brk_types::{CentsSats, CentsSquaredSats, Height, Indexes, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{AnyStoredVec, AnyVec, BytesVec, Exit, ReadableVec, Rw, StorageMode, WritableVec};
use crate::{distribution::{metrics::ImportConfig, state::UnrealizedState}, prices};
use crate::{blocks, distribution::{metrics::ImportConfig, state::UnrealizedState}, prices};
use super::UnrealizedCore;
@@ -15,9 +15,13 @@ pub struct UnrealizedBase<M: StorageMode = Rw> {
#[traversable(flatten)]
pub core: UnrealizedCore<M>,
#[traversable(wrap = "invested_capital/in_profit", rename = "raw")]
pub invested_capital_in_profit_raw: M::Stored<BytesVec<Height, CentsSats>>,
#[traversable(wrap = "invested_capital/in_loss", rename = "raw")]
pub invested_capital_in_loss_raw: M::Stored<BytesVec<Height, CentsSats>>,
#[traversable(wrap = "investor_cap/in_profit", rename = "raw")]
pub investor_cap_in_profit_raw: M::Stored<BytesVec<Height, CentsSquaredSats>>,
#[traversable(wrap = "investor_cap/in_loss", rename = "raw")]
pub investor_cap_in_loss_raw: M::Stored<BytesVec<Height, CentsSquaredSats>>,
}
@@ -161,11 +165,12 @@ impl UnrealizedBase {
pub(crate) fn compute_rest(
&mut self,
blocks: &blocks::Vecs,
prices: &prices::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
self.core.compute_rest(prices, starting_indexes, exit)?;
self.core.compute_rest(blocks, prices, starting_indexes, exit)?;
Ok(())
}
}

View File

@@ -0,0 +1,107 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Cents, Height, Indexes, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{AnyStoredVec, AnyVec, Exit, Rw, StorageMode, WritableVec};
use crate::{
blocks,
distribution::{metrics::ImportConfig, state::UnrealizedState},
internal::FiatPerBlockWithSum24h,
prices,
};
use super::UnrealizedMinimal;
/// Basic unrealized metrics: supply in profit/loss + unrealized profit/loss (fiat + 24h sums).
#[derive(Deref, DerefMut, Traversable)]
pub struct UnrealizedBasic<M: StorageMode = Rw> {
#[deref]
#[deref_mut]
#[traversable(flatten)]
pub minimal: UnrealizedMinimal<M>,
pub profit: FiatPerBlockWithSum24h<Cents, M>,
pub loss: FiatPerBlockWithSum24h<Cents, M>,
}
impl UnrealizedBasic {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
let v1 = Version::ONE;
let minimal = UnrealizedMinimal::forced_import(cfg)?;
Ok(Self {
minimal,
profit: cfg.import("unrealized_profit", v1)?,
loss: cfg.import("unrealized_loss", v1)?,
})
}
pub(crate) fn min_stateful_height_len(&self) -> usize {
self.minimal
.min_stateful_height_len()
.min(self.profit.raw.cents.height.len())
.min(self.loss.raw.cents.height.len())
}
pub(crate) fn truncate_push(&mut self, height: Height, state: &UnrealizedState) -> Result<()> {
self.minimal.truncate_push(height, state)?;
self.profit
.raw
.cents
.height
.truncate_push(height, state.unrealized_profit)?;
self.loss
.raw
.cents
.height
.truncate_push(height, state.unrealized_loss)?;
Ok(())
}
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
let mut vecs = self.minimal.collect_vecs_mut();
vecs.push(&mut self.profit.raw.cents.height as &mut dyn AnyStoredVec);
vecs.push(&mut self.loss.raw.cents.height);
vecs
}
pub(crate) fn compute_from_sources(
&mut self,
starting_indexes: &Indexes,
others: &[&Self],
exit: &Exit,
) -> Result<()> {
let minimal_refs: Vec<&UnrealizedMinimal> = others.iter().map(|o| &o.minimal).collect();
self.minimal
.compute_from_sources(starting_indexes, &minimal_refs, exit)?;
sum_others!(self, starting_indexes, others, exit; profit.raw.cents.height);
sum_others!(self, starting_indexes, others, exit; loss.raw.cents.height);
Ok(())
}
pub(crate) fn compute_rest(
&mut self,
blocks: &blocks::Vecs,
prices: &prices::Vecs,
max_from: Height,
exit: &Exit,
) -> Result<()> {
self.minimal.compute_rest(prices, max_from, exit)?;
self.profit.sum.compute_rolling_sum(
max_from,
&blocks.lookback.height_24h_ago,
&self.profit.raw.cents.height,
exit,
)?;
self.loss.sum.compute_rolling_sum(
max_from,
&blocks.lookback.height_24h_ago,
&self.loss.raw.cents.height,
exit,
)?;
Ok(())
}
}

View File

@@ -2,11 +2,12 @@ use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Cents, CentsSigned, Height, Indexes, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{AnyStoredVec, AnyVec, Exit, ReadableCloneableVec, Rw, StorageMode, WritableVec};
use vecdb::{AnyStoredVec, Exit, ReadableCloneableVec, Rw, StorageMode};
use crate::{
blocks,
distribution::{
metrics::{ImportConfig, unrealized::UnrealizedMinimal},
metrics::ImportConfig,
state::UnrealizedState,
},
internal::{CentsSubtractToCentsSigned, FiatPerBlock, LazyPerBlock, NegCentsUnsignedToDollars},
@@ -15,51 +16,42 @@ use crate::{
use brk_types::Dollars;
use super::UnrealizedBasic;
#[derive(Deref, DerefMut, Traversable)]
pub struct UnrealizedCore<M: StorageMode = Rw> {
#[deref]
#[deref_mut]
#[traversable(flatten)]
pub minimal: UnrealizedMinimal<M>,
pub basic: UnrealizedBasic<M>,
pub profit: FiatPerBlock<Cents, M>,
pub loss: FiatPerBlock<Cents, M>,
#[traversable(wrap = "loss", rename = "neg")]
pub neg_loss: LazyPerBlock<Dollars, Cents>,
pub net_pnl: FiatPerBlock<CentsSigned, M>,
}
impl UnrealizedCore {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
let v0 = Version::ZERO;
let minimal = UnrealizedMinimal::forced_import(cfg)?;
let unrealized_profit = cfg.import("unrealized_profit", v0)?;
let unrealized_loss: FiatPerBlock<Cents> = cfg.import("unrealized_loss", v0)?;
let basic = UnrealizedBasic::forced_import(cfg)?;
let neg_unrealized_loss = LazyPerBlock::from_computed::<NegCentsUnsignedToDollars>(
&cfg.name("neg_unrealized_loss"),
cfg.version,
unrealized_loss.cents.height.read_only_boxed_clone(),
&unrealized_loss.cents,
basic.loss.raw.cents.height.read_only_boxed_clone(),
&basic.loss.raw.cents,
);
let net_unrealized_pnl = cfg.import("net_unrealized_pnl", v0)?;
let net_unrealized_pnl = cfg.import("net_unrealized_pnl", Version::ZERO)?;
Ok(Self {
minimal,
profit: unrealized_profit,
loss: unrealized_loss,
basic,
neg_loss: neg_unrealized_loss,
net_pnl: net_unrealized_pnl,
})
}
pub(crate) fn min_stateful_height_len(&self) -> usize {
self.minimal
.min_stateful_height_len()
.min(self.profit.cents.height.len())
.min(self.loss.cents.height.len())
self.basic.min_stateful_height_len()
}
pub(crate) fn truncate_push(
@@ -67,24 +59,12 @@ impl UnrealizedCore {
height: Height,
height_state: &UnrealizedState,
) -> Result<()> {
self.minimal.truncate_push(height, height_state)?;
self.profit
.cents
.height
.truncate_push(height, height_state.unrealized_profit)?;
self.loss
.cents
.height
.truncate_push(height, height_state.unrealized_loss)?;
self.basic.truncate_push(height, height_state)?;
Ok(())
}
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
let mut vecs = self.minimal.collect_vecs_mut();
vecs.push(&mut self.profit.cents.height);
vecs.push(&mut self.loss.cents.height);
vecs
self.basic.collect_vecs_mut()
}
pub(crate) fn compute_from_stateful(
@@ -93,33 +73,30 @@ impl UnrealizedCore {
others: &[&Self],
exit: &Exit,
) -> Result<()> {
let minimal_refs: Vec<&UnrealizedMinimal> = others.iter().map(|o| &o.minimal).collect();
self.minimal
.compute_from_sources(starting_indexes, &minimal_refs, exit)?;
sum_others!(self, starting_indexes, others, exit; profit.cents.height);
sum_others!(self, starting_indexes, others, exit; loss.cents.height);
let basic_refs: Vec<&UnrealizedBasic> = others.iter().map(|o| &o.basic).collect();
self.basic
.compute_from_sources(starting_indexes, &basic_refs, exit)?;
Ok(())
}
/// Compute derived metrics from stored values.
pub(crate) fn compute_rest(
&mut self,
blocks: &blocks::Vecs,
prices: &prices::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
self.minimal
.compute_rest(prices, starting_indexes.height, exit)?;
self.basic
.compute_rest(blocks, prices, starting_indexes.height, exit)?;
self.net_pnl
.cents
.height
.compute_binary::<Cents, Cents, CentsSubtractToCentsSigned>(
starting_indexes.height,
&self.profit.cents.height,
&self.loss.cents.height,
&self.basic.profit.raw.cents.height,
&self.basic.loss.raw.cents.height,
exit,
)?;

View File

@@ -6,10 +6,17 @@ use vecdb::{AnyStoredVec, Exit, Rw, StorageMode, WritableVec};
use crate::distribution::state::UnrealizedState;
use crate::internal::{CentsSubtractToCentsSigned, FiatPerBlock};
use crate::{distribution::metrics::ImportConfig, prices};
use crate::{blocks, distribution::metrics::ImportConfig, prices};
use super::UnrealizedBase;
#[derive(Traversable)]
pub struct UnrealizedSentiment<M: StorageMode = Rw> {
pub pain_index: FiatPerBlock<Cents, M>,
pub greed_index: FiatPerBlock<Cents, M>,
pub net: FiatPerBlock<CentsSigned, M>,
}
#[derive(Deref, DerefMut, Traversable)]
pub struct UnrealizedFull<M: StorageMode = Rw> {
#[deref]
@@ -18,12 +25,13 @@ pub struct UnrealizedFull<M: StorageMode = Rw> {
pub inner: UnrealizedBase<M>,
pub gross_pnl: FiatPerBlock<Cents, M>,
#[traversable(wrap = "invested_capital", rename = "in_profit")]
pub invested_capital_in_profit: FiatPerBlock<Cents, M>,
#[traversable(wrap = "invested_capital", rename = "in_loss")]
pub invested_capital_in_loss: FiatPerBlock<Cents, M>,
pub pain_index: FiatPerBlock<Cents, M>,
pub greed_index: FiatPerBlock<Cents, M>,
pub net_sentiment: FiatPerBlock<CentsSigned, M>,
pub sentiment: UnrealizedSentiment<M>,
}
impl UnrealizedFull {
@@ -35,18 +43,18 @@ impl UnrealizedFull {
let invested_capital_in_profit = cfg.import("invested_capital_in_profit", v0)?;
let invested_capital_in_loss = cfg.import("invested_capital_in_loss", v0)?;
let pain_index = cfg.import("pain_index", v0)?;
let greed_index = cfg.import("greed_index", v0)?;
let net_sentiment = cfg.import("net_sentiment", Version::ONE)?;
let sentiment = UnrealizedSentiment {
pain_index: cfg.import("pain_index", v0)?,
greed_index: cfg.import("greed_index", v0)?,
net: cfg.import("net_sentiment", Version::ONE)?,
};
Ok(Self {
inner,
gross_pnl,
invested_capital_in_profit,
invested_capital_in_loss,
pain_index,
greed_index,
net_sentiment,
sentiment,
})
}
@@ -72,24 +80,25 @@ impl UnrealizedFull {
vecs.push(&mut self.gross_pnl.cents.height as &mut dyn AnyStoredVec);
vecs.push(&mut self.invested_capital_in_profit.cents.height as &mut dyn AnyStoredVec);
vecs.push(&mut self.invested_capital_in_loss.cents.height as &mut dyn AnyStoredVec);
vecs.push(&mut self.pain_index.cents.height as &mut dyn AnyStoredVec);
vecs.push(&mut self.greed_index.cents.height as &mut dyn AnyStoredVec);
vecs.push(&mut self.net_sentiment.cents.height as &mut dyn AnyStoredVec);
vecs.push(&mut self.sentiment.pain_index.cents.height as &mut dyn AnyStoredVec);
vecs.push(&mut self.sentiment.greed_index.cents.height as &mut dyn AnyStoredVec);
vecs.push(&mut self.sentiment.net.cents.height as &mut dyn AnyStoredVec);
vecs
}
pub(crate) fn compute_rest_all(
&mut self,
blocks: &blocks::Vecs,
prices: &prices::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
self.inner.compute_rest(prices, starting_indexes, exit)?;
self.inner.compute_rest(blocks, prices, starting_indexes, exit)?;
self.gross_pnl.cents.height.compute_add(
starting_indexes.height,
&self.inner.core.profit.cents.height,
&self.inner.core.loss.cents.height,
&self.inner.core.basic.profit.raw.cents.height,
&self.inner.core.basic.loss.raw.cents.height,
exit,
)?;
@@ -123,7 +132,7 @@ impl UnrealizedFull {
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
self.pain_index.cents.height.compute_transform3(
self.sentiment.pain_index.cents.height.compute_transform3(
starting_indexes.height,
&self.inner.investor_cap_in_loss_raw,
&self.inner.invested_capital_in_loss_raw,
@@ -139,7 +148,7 @@ impl UnrealizedFull {
exit,
)?;
self.greed_index.cents.height.compute_transform3(
self.sentiment.greed_index.cents.height.compute_transform3(
starting_indexes.height,
&self.inner.investor_cap_in_profit_raw,
&self.inner.invested_capital_in_profit_raw,
@@ -163,13 +172,14 @@ impl UnrealizedFull {
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
self.net_sentiment
self.sentiment
.net
.cents
.height
.compute_binary::<Cents, Cents, CentsSubtractToCentsSigned>(
starting_indexes.height,
&self.greed_index.cents.height,
&self.pain_index.cents.height,
&self.sentiment.greed_index.cents.height,
&self.sentiment.pain_index.cents.height,
exit,
)?;
Ok(())

View File

@@ -12,15 +12,18 @@ use crate::distribution::{metrics::ImportConfig, state::UnrealizedState};
/// Minimal unrealized metrics: supply in profit/loss only.
#[derive(Traversable)]
pub struct UnrealizedMinimal<M: StorageMode = Rw> {
#[traversable(wrap = "profit", rename = "supply")]
pub supply_in_profit: AmountPerBlock<M>,
#[traversable(wrap = "loss", rename = "supply")]
pub supply_in_loss: AmountPerBlock<M>,
}
impl UnrealizedMinimal {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
let v0 = Version::ZERO;
Ok(Self {
supply_in_profit: cfg.import("supply_in_profit", Version::ZERO)?,
supply_in_loss: cfg.import("supply_in_loss", Version::ZERO)?,
supply_in_profit: cfg.import("supply_in_profit", v0)?,
supply_in_loss: cfg.import("supply_in_loss", v0)?,
})
}
@@ -47,9 +50,9 @@ impl UnrealizedMinimal {
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
vec![
&mut self.supply_in_profit.sats.height as &mut dyn AnyStoredVec,
&mut self.supply_in_profit.cents.height as &mut dyn AnyStoredVec,
&mut self.supply_in_loss.sats.height as &mut dyn AnyStoredVec,
&mut self.supply_in_loss.cents.height as &mut dyn AnyStoredVec,
&mut self.supply_in_profit.cents.height,
&mut self.supply_in_loss.sats.height,
&mut self.supply_in_loss.cents.height,
]
}

View File

@@ -1,10 +1,12 @@
mod base;
mod basic;
mod core;
mod full;
mod minimal;
pub use self::core::UnrealizedCore;
pub use base::UnrealizedBase;
pub use basic::UnrealizedBasic;
pub use full::UnrealizedFull;
pub use minimal::UnrealizedMinimal;
@@ -12,7 +14,7 @@ use brk_error::Result;
use brk_types::{Height, Indexes};
use vecdb::Exit;
use crate::{distribution::state::UnrealizedState, prices};
use crate::{blocks, distribution::state::UnrealizedState, prices};
pub trait UnrealizedLike: Send + Sync {
fn as_base(&self) -> &UnrealizedBase;
@@ -21,6 +23,7 @@ pub trait UnrealizedLike: Send + Sync {
fn truncate_push(&mut self, height: Height, state: &UnrealizedState) -> Result<()>;
fn compute_rest(
&mut self,
blocks: &blocks::Vecs,
prices: &prices::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
@@ -47,11 +50,12 @@ impl UnrealizedLike for UnrealizedBase {
}
fn compute_rest(
&mut self,
blocks: &blocks::Vecs,
prices: &prices::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
self.compute_rest(prices, starting_indexes, exit)
self.compute_rest(blocks, prices, starting_indexes, exit)
}
fn compute_net_sentiment_height(
&mut self,
@@ -77,11 +81,12 @@ impl UnrealizedLike for UnrealizedFull {
}
fn compute_rest(
&mut self,
blocks: &blocks::Vecs,
prices: &prices::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
self.compute_rest_all(prices, starting_indexes, exit)
self.compute_rest_all(blocks, prices, starting_indexes, exit)
}
fn compute_net_sentiment_height(
&mut self,

View File

@@ -38,13 +38,15 @@ pub trait RealizedOps: Default + Clone + Send + Sync + 'static {
);
}
/// Minimal realized state: only cap, profit, loss.
/// Minimal realized state: cap, profit, loss, value_created/destroyed.
/// Used by MinimalCohortMetrics cohorts (amount_range, type_, address — ~135 separate cohorts).
#[derive(Debug, Default, Clone)]
pub struct MinimalRealizedState {
cap_raw: u128,
profit_raw: u128,
loss_raw: u128,
value_created_raw: u128,
value_destroyed_raw: u128,
}
impl RealizedOps for MinimalRealizedState {
@@ -72,6 +74,22 @@ impl RealizedOps for MinimalRealizedState {
Cents::new((self.loss_raw / Sats::ONE_BTC_U128) as u64)
}
#[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.cap_raw = cap_raw.inner();
@@ -84,6 +102,8 @@ impl RealizedOps for MinimalRealizedState {
fn reset_single_iteration_values(&mut self) {
self.profit_raw = 0;
self.loss_raw = 0;
self.value_created_raw = 0;
self.value_destroyed_raw = 0;
}
#[inline]
@@ -124,16 +144,16 @@ impl RealizedOps for MinimalRealizedState {
Ordering::Equal => {}
}
self.cap_raw -= prev_ps.as_u128();
self.value_created_raw += current_ps.as_u128();
self.value_destroyed_raw += prev_ps.as_u128();
}
}
/// Core realized state: cap, profit, loss + value_created/destroyed for SOPR + sent tracking.
/// Core realized state: extends Minimal with sent_in_profit/loss tracking.
/// 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,
sent_in_profit: Sats,
sent_in_loss: Sats,
}
@@ -156,18 +176,12 @@ impl RealizedOps for CoreRealizedState {
#[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)
self.minimal.value_created()
}
#[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)
self.minimal.value_destroyed()
}
#[inline]
@@ -191,8 +205,6 @@ impl RealizedOps for CoreRealizedState {
#[inline]
fn reset_single_iteration_values(&mut self) {
self.minimal.reset_single_iteration_values();
self.value_created_raw = 0;
self.value_destroyed_raw = 0;
self.sent_in_profit = Sats::ZERO;
self.sent_in_loss = Sats::ZERO;
}
@@ -223,8 +235,6 @@ impl RealizedOps for CoreRealizedState {
) {
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();
match current_ps.cmp(&prev_ps) {
Ordering::Greater | Ordering::Equal => {
self.sent_in_profit += sats;

View File

@@ -60,9 +60,9 @@ pub struct Vecs<M: StorageMode = Rw> {
/// Windowed change + growth rate for addr_count, global + per-type
pub delta: DeltaVecs<M>,
pub fundedaddressindex:
pub funded_address_index:
LazyVecFrom1<FundedAddressIndex, FundedAddressIndex, FundedAddressIndex, FundedAddressData>,
pub emptyaddressindex:
pub empty_address_index:
LazyVecFrom1<EmptyAddressIndex, EmptyAddressIndex, EmptyAddressIndex, EmptyAddressData>,
/// In-memory block state for UTXO processing. Persisted via supply_state.
@@ -115,14 +115,14 @@ impl Vecs {
)?;
// Identity mappings for traversable
let fundedaddressindex = LazyVecFrom1::init(
"fundedaddressindex",
let funded_address_index = LazyVecFrom1::init(
"funded_address_index",
version,
fundedaddressindex_to_fundedaddressdata.read_only_boxed_clone(),
|index, _| index,
);
let emptyaddressindex = LazyVecFrom1::init(
"emptyaddressindex",
let empty_address_index = LazyVecFrom1::init(
"empty_address_index",
version,
emptyaddressindex_to_emptyaddressdata.read_only_boxed_clone(),
|index, _| index,
@@ -164,8 +164,8 @@ impl Vecs {
funded: fundedaddressindex_to_fundedaddressdata,
empty: emptyaddressindex_to_emptyaddressdata,
},
fundedaddressindex,
emptyaddressindex,
funded_address_index,
empty_address_index,
chain_state: Vec::new(),
txindex_to_height: RangeMap::default(),