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

File diff suppressed because it is too large Load Diff

View File

@@ -34,7 +34,7 @@ impl Vecs {
vec.compute_subtract( vec.compute_subtract(
starting_indexes.height, starting_indexes.height,
&self.coinblocks_created.height, &self.coinblocks_created.height,
&all_metrics.activity.coinblocks_destroyed.height, &all_metrics.activity.coinblocks_destroyed.raw.height,
exit, exit,
)?; )?;
Ok(()) Ok(())
@@ -42,7 +42,7 @@ impl Vecs {
self.liveliness.height.compute_divide( self.liveliness.height.compute_divide(
starting_indexes.height, starting_indexes.height,
&all_metrics.activity.coinblocks_destroyed_cumulative.height, &all_metrics.activity.coinblocks_destroyed.cumulative.height,
&self.coinblocks_created.cumulative.height, &self.coinblocks_created.cumulative.height,
exit, exit,
)?; )?;

View File

@@ -18,7 +18,7 @@ impl Vecs {
exit: &Exit, exit: &Exit,
) -> Result<()> { ) -> Result<()> {
let all_metrics = &distribution.utxo_cohorts.all.metrics; let all_metrics = &distribution.utxo_cohorts.all.metrics;
let realized_cap_cents = &all_metrics.realized.cap_cents.height; let realized_cap_cents = &all_metrics.realized.cap.cents.height;
let circulating_supply = &all_metrics.supply.total.btc.height; let circulating_supply = &all_metrics.supply.total.btc.height;
self.thermo_cap.cents.height.compute_transform( self.thermo_cap.cents.height.compute_transform(

View File

@@ -28,7 +28,7 @@ impl Vecs {
vec.compute_multiply( vec.compute_multiply(
starting_indexes.height, starting_indexes.height,
&prices.price.usd.height, &prices.price.usd.height,
&coinblocks_destroyed.height, &coinblocks_destroyed.raw.height,
exit, exit,
)?; )?;
Ok(()) Ok(())
@@ -64,7 +64,7 @@ impl Vecs {
vec.compute_transform3( vec.compute_transform3(
starting_indexes.height, starting_indexes.height,
&prices.price.usd.height, &prices.price.usd.height,
&coindays_destroyed.height, &coindays_destroyed.raw.height,
circulating_supply, circulating_supply,
|(i, price, cdd, supply, _): (_, Dollars, StoredF64, Bitcoin, _)| { |(i, price, cdd, supply, _): (_, Dollars, StoredF64, Bitcoin, _)| {
let supply_f64 = f64::from(supply); let supply_f64 = f64::from(supply);

View File

@@ -157,7 +157,6 @@ impl DynCohortVecs for AddressCohortVecs {
self.addr_count self.addr_count
.height .height
.validate_computed_version_or_reset(base_version)?; .validate_computed_version_or_reset(base_version)?;
self.metrics.validate_computed_versions(base_version)?;
Ok(()) Ok(())
} }
@@ -170,18 +169,9 @@ impl DynCohortVecs for AddressCohortVecs {
self.addr_count self.addr_count
.height .height
.truncate_push(height, state.addr_count.into())?; .truncate_push(height, state.addr_count.into())?;
self.metrics self.metrics.supply.truncate_push(height, &state.inner)?;
.supply self.metrics.outputs.truncate_push(height, &state.inner)?;
.truncate_push(height, state.inner.supply.value)?; self.metrics.realized.truncate_push(height, &state.inner)?;
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)?;
} }
Ok(()) Ok(())
@@ -189,17 +179,10 @@ impl DynCohortVecs for AddressCohortVecs {
fn compute_then_truncate_push_unrealized_states( fn compute_then_truncate_push_unrealized_states(
&mut self, &mut self,
height: Height, _height: Height,
height_price: Cents, _height_price: Cents,
_is_day_boundary: bool, _is_day_boundary: bool,
) -> Result<()> { ) -> 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(()) Ok(())
} }

View File

@@ -17,9 +17,10 @@ use crate::{
distribution::{ distribution::{
DynCohortVecs, DynCohortVecs,
metrics::{ metrics::{
AllCohortMetrics, BasicCohortMetrics, CohortMetricsBase, CoreCohortMetrics, AllCohortMetrics, BasicCohortMetrics, CohortMetricsBase,
ExtendedAdjustedCohortMetrics, ExtendedCohortMetrics, ImportConfig, CoreCohortMetrics, ExtendedAdjustedCohortMetrics, ExtendedCohortMetrics, ImportConfig,
MinimalCohortMetrics, ProfitabilityMetrics, RealizedFullAccum, SupplyMetrics, MinimalCohortMetrics, ProfitabilityMetrics, RealizedFullAccum, SupplyFull,
TypeCohortMetrics,
}, },
state::UTXOCohortState, state::UTXOCohortState,
}, },
@@ -47,7 +48,7 @@ pub struct UTXOCohorts<M: StorageMode = Rw> {
pub amount_range: ByAmountRange<UTXOCohortVecs<MinimalCohortMetrics<M>>>, pub amount_range: ByAmountRange<UTXOCohortVecs<MinimalCohortMetrics<M>>>,
pub lt_amount: ByLowerThanAmount<UTXOCohortVecs<MinimalCohortMetrics<M>>>, pub lt_amount: ByLowerThanAmount<UTXOCohortVecs<MinimalCohortMetrics<M>>>,
#[traversable(rename = "type")] #[traversable(rename = "type")]
pub type_: BySpendableType<UTXOCohortVecs<MinimalCohortMetrics<M>>>, pub type_: BySpendableType<UTXOCohortVecs<TypeCohortMetrics<M>>>,
pub profitability: ProfitabilityMetrics<M>, pub profitability: ProfitabilityMetrics<M>,
pub matured: ByAgeRange<AmountPerBlock<M>>, pub matured: ByAgeRange<AmountPerBlock<M>>,
#[traversable(skip)] #[traversable(skip)]
@@ -81,7 +82,7 @@ impl UTXOCohorts<Rw> {
version: v + Version::ONE, version: v + Version::ONE,
indexes, indexes,
}; };
let all_supply = SupplyMetrics::forced_import(&all_cfg)?; let all_supply = SupplyFull::forced_import(&all_cfg)?;
// Phase 2: Import separate (stateful) cohorts. // Phase 2: Import separate (stateful) cohorts.
@@ -144,7 +145,25 @@ impl UTXOCohorts<Rw> {
}; };
let amount_range = ByAmountRange::try_new(&minimal_separate)?; 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. // Phase 3: Import "all" cohort with pre-imported supply.
let all = UTXOCohortVecs::new( let all = UTXOCohortVecs::new(
@@ -208,7 +227,6 @@ impl UTXOCohorts<Rw> {
// min_age: CoreCohortMetrics (no state, aggregates from age_range) // min_age: CoreCohortMetrics (no state, aggregates from age_range)
let min_age = ByMinAge::try_new(&core_no_state)?; let min_age = ByMinAge::try_new(&core_no_state)?;
// MinimalCohortMetrics without state (for aggregate amount cohorts)
let minimal_no_state = let minimal_no_state =
|f: Filter, name: &'static str| -> Result<UTXOCohortVecs<MinimalCohortMetrics>> { |f: Filter, name: &'static str| -> Result<UTXOCohortVecs<MinimalCohortMetrics>> {
let full_name = CohortContext::Utxo.full_name(&f, name); let full_name = CohortContext::Utxo.full_name(&f, name);
@@ -424,7 +442,8 @@ impl UTXOCohorts<Rw> {
.try_for_each(|vecs| { .try_for_each(|vecs| {
let sources = let sources =
filter_minimal_sources_from(amr.iter(), Some(&vecs.metrics.filter)); 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 .up_to_1h
.metrics .metrics
.realized .realized
.minimal
.sopr
.value_created .value_created
.raw
.height .height
.read_only_clone(); .read_only_clone();
let up_to_1h_value_destroyed = self let up_to_1h_value_destroyed = self
@@ -515,7 +537,10 @@ impl UTXOCohorts<Rw> {
.up_to_1h .up_to_1h
.metrics .metrics
.realized .realized
.minimal
.sopr
.value_destroyed .value_destroyed
.raw
.height .height
.read_only_clone(); .read_only_clone();
@@ -746,12 +771,6 @@ impl UTXOCohorts<Rw> {
for v in self.max_age.iter_mut() { for v in self.max_age.iter_mut() {
v.metrics.validate_computed_versions(base_version)?; 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(()) 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_cohort::{Filter, Filtered};
use brk_error::Result; use brk_error::Result;
use brk_traversable::Traversable; use brk_traversable::Traversable;
use brk_types::{Cents, Height, Indexes, Version}; use brk_types::{Cents, Height, Indexes, Version};
use vecdb::{Exit, ReadableVec}; use vecdb::{Exit, ReadableVec};
use crate::{blocks, distribution::state::UTXOCohortState, prices}; use crate::{
blocks,
use crate::distribution::metrics::{ distribution::{
CohortMetricsBase, CohortMetricsState, CoreCohortMetrics, MinimalCohortMetrics, cohorts::traits::DynCohortVecs,
metrics::{CohortMetricsBase, CohortMetricsState},
state::UTXOCohortState,
},
prices,
}; };
use super::super::traits::DynCohortVecs;
#[derive(Traversable)] #[derive(Traversable)]
pub struct UTXOCohortVecs<M: CohortMetricsState> { pub struct UTXOCohortVecs<M: CohortMetricsState> {
#[traversable(skip)] #[traversable(skip)]
@@ -24,8 +70,6 @@ pub struct UTXOCohortVecs<M: CohortMetricsState> {
pub metrics: M, pub metrics: M,
} }
// --- Shared state helpers (identical across all DynCohortVecs impls) ---
impl<M: CohortMetricsState> UTXOCohortVecs<M> { impl<M: CohortMetricsState> UTXOCohortVecs<M> {
pub(crate) fn new(state: Option<Box<UTXOCohortState<M::Realized>>>, metrics: M) -> Self { pub(crate) fn new(state: Option<Box<UTXOCohortState<M::Realized>>>, metrics: M) -> Self {
Self { Self {
@@ -172,213 +216,3 @@ impl<M: CohortMetricsBase + Traversable> DynCohortVecs for UTXOCohortVecs<M> {
self.reset_iteration_impl(); 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_error::Result;
use brk_traversable::Traversable; 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 vecdb::{AnyStoredVec, AnyVec, Exit, Rw, StorageMode, WritableVec};
use crate::internal::{ComputedPerBlock, RollingWindow24h}; use crate::{
blocks,
use crate::{blocks, distribution::metrics::ImportConfig}; distribution::{metrics::ImportConfig, state::{CohortState, RealizedOps}},
internal::PerBlockWithSum24h,
};
#[derive(Traversable)] #[derive(Traversable)]
pub struct ActivityCore<M: StorageMode = Rw> { pub struct ActivityCore<M: StorageMode = Rw> {
pub sent: ComputedPerBlock<Sats, M>, pub sent: PerBlockWithSum24h<Sats, M>,
pub sent_sum: RollingWindow24h<Sats, M>, pub coindays_destroyed: PerBlockWithSum24h<StoredF64, M>,
} }
impl ActivityCore { impl ActivityCore {
@@ -18,21 +20,36 @@ impl ActivityCore {
let v1 = Version::ONE; let v1 = Version::ONE;
Ok(Self { Ok(Self {
sent: cfg.import("sent", v1)?, 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 { 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<()> { pub(crate) fn truncate_push(
self.sent.height.truncate_push(height, sent)?; &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(()) Ok(())
} }
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> { 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<()> { pub(crate) fn validate_computed_versions(&mut self, _base_version: Version) -> Result<()> {
@@ -45,14 +62,17 @@ impl ActivityCore {
others: &[&Self], others: &[&Self],
exit: &Exit, exit: &Exit,
) -> Result<()> { ) -> Result<()> {
self.sent.height.compute_sum_of_others( self.sent.raw.height.compute_sum_of_others(
starting_indexes.height, starting_indexes.height,
&others &others
.iter() .iter()
.map(|v| &v.sent.height) .map(|v| &v.sent.raw.height)
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
exit, exit,
)?; )?;
sum_others!(self, starting_indexes, others, exit; coindays_destroyed.raw.height);
Ok(()) Ok(())
} }
@@ -62,10 +82,16 @@ impl ActivityCore {
starting_indexes: &Indexes, starting_indexes: &Indexes,
exit: &Exit, exit: &Exit,
) -> Result<()> { ) -> Result<()> {
self.sent_sum.compute_rolling_sum( self.sent.sum.compute_rolling_sum(
starting_indexes.height, starting_indexes.height,
&blocks.lookback.height_24h_ago, &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, exit,
)?; )?;
Ok(()) Ok(())

View File

@@ -1,47 +1,90 @@
use brk_error::Result; use brk_error::Result;
use brk_traversable::Traversable; 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 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)] #[derive(Deref, DerefMut, Traversable)]
pub struct ActivityFull<M: StorageMode = Rw> { pub struct ActivityFull<M: StorageMode = Rw> {
#[deref] #[deref]
#[deref_mut] #[deref_mut]
#[traversable(flatten)] #[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_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 sent_sum_extended: RollingWindowsFrom1w<Sats, M>,
pub dormancy: ComputedPerBlock<StoredF32, M>,
pub velocity: ComputedPerBlock<StoredF32, M>,
} }
impl ActivityFull { impl ActivityFull {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> { pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
let v1 = Version::ONE; let v1 = Version::ONE;
Ok(Self { Ok(Self {
inner: ActivityBase::forced_import(cfg)?, inner: ActivityCore::forced_import(cfg)?,
coinblocks_destroyed_cumulative: cfg coinblocks_destroyed: ActivityCoinblocks {
.import("coinblocks_destroyed_cumulative", v1)?, raw: cfg.import("coinblocks_destroyed", v1)?,
cumulative: cfg.import("coinblocks_destroyed_cumulative", v1)?,
},
coindays_destroyed_cumulative: cfg.import("coindays_destroyed_cumulative", v1)?, coindays_destroyed_cumulative: cfg.import("coindays_destroyed_cumulative", v1)?,
coindays_destroyed_sum: cfg.import("coindays_destroyed", v1)?, coindays_destroyed_sum: cfg.import("coindays_destroyed", v1)?,
sent_sum_extended: cfg.import("sent", 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( pub(crate) fn compute_from_stateful(
&mut self, &mut self,
starting_indexes: &Indexes, starting_indexes: &Indexes,
others: &[&ActivityBase], others: &[&ActivityCore],
exit: &Exit, exit: &Exit,
) -> Result<()> { ) -> Result<()> {
self.inner self.inner
@@ -57,11 +100,12 @@ impl ActivityFull {
self.inner self.inner
.compute_rest_part1(blocks, starting_indexes, exit)?; .compute_rest_part1(blocks, starting_indexes, exit)?;
self.coinblocks_destroyed_cumulative self.coinblocks_destroyed
.cumulative
.height .height
.compute_cumulative( .compute_cumulative(
starting_indexes.height, starting_indexes.height,
&self.inner.coinblocks_destroyed.height, &self.coinblocks_destroyed.raw.height,
exit, exit,
)?; )?;
@@ -69,7 +113,7 @@ impl ActivityFull {
.height .height
.compute_cumulative( .compute_cumulative(
starting_indexes.height, starting_indexes.height,
&self.inner.coindays_destroyed.height, &self.inner.coindays_destroyed.raw.height,
exit, exit,
)?; )?;
@@ -77,14 +121,53 @@ impl ActivityFull {
self.coindays_destroyed_sum.compute_rolling_sum( self.coindays_destroyed_sum.compute_rolling_sum(
starting_indexes.height, starting_indexes.height,
&window_starts, &window_starts,
&self.inner.coindays_destroyed.height, &self.inner.coindays_destroyed.raw.height,
exit, exit,
)?; )?;
self.sent_sum_extended.compute_rolling_sum( self.sent_sum_extended.compute_rolling_sum(
starting_indexes.height, starting_indexes.height,
&window_starts, &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, exit,
)?; )?;

View File

@@ -1,33 +1,29 @@
mod base;
mod core; mod core;
mod full; mod full;
pub use base::ActivityBase;
pub use self::core::ActivityCore; pub use self::core::ActivityCore;
pub use full::ActivityFull; pub use full::ActivityFull;
use brk_error::Result; use brk_error::Result;
use brk_types::{Height, Indexes, Sats, Version}; use brk_types::{Height, Indexes, Version};
use vecdb::Exit; use vecdb::Exit;
use crate::blocks; use crate::{blocks, distribution::state::{CohortState, RealizedOps}};
pub trait ActivityLike: Send + Sync { pub trait ActivityLike: Send + Sync {
fn as_base(&self) -> &ActivityBase; fn as_core(&self) -> &ActivityCore;
fn as_base_mut(&mut self) -> &mut ActivityBase; fn as_core_mut(&mut self) -> &mut ActivityCore;
fn min_len(&self) -> usize; fn min_len(&self) -> usize;
fn truncate_push( fn truncate_push<R: RealizedOps>(
&mut self, &mut self,
height: Height, height: Height,
sent: Sats, state: &CohortState<R>,
satblocks_destroyed: Sats,
satdays_destroyed: Sats,
) -> Result<()>; ) -> Result<()>;
fn validate_computed_versions(&mut self, base_version: Version) -> Result<()>; fn validate_computed_versions(&mut self, base_version: Version) -> Result<()>;
fn compute_from_stateful( fn compute_from_stateful(
&mut self, &mut self,
starting_indexes: &Indexes, starting_indexes: &Indexes,
others: &[&ActivityBase], others: &[&ActivityCore],
exit: &Exit, exit: &Exit,
) -> Result<()>; ) -> Result<()>;
fn compute_rest_part1( fn compute_rest_part1(
@@ -38,17 +34,17 @@ pub trait ActivityLike: Send + Sync {
) -> Result<()>; ) -> Result<()>;
} }
impl ActivityLike for ActivityBase { impl ActivityLike for ActivityCore {
fn as_base(&self) -> &ActivityBase { self } fn as_core(&self) -> &ActivityCore { self }
fn as_base_mut(&mut self) -> &mut ActivityBase { self } fn as_core_mut(&mut self) -> &mut ActivityCore { self }
fn min_len(&self) -> usize { self.min_len() } fn min_len(&self) -> usize { self.min_len() }
fn truncate_push(&mut self, height: Height, sent: Sats, satblocks_destroyed: Sats, satdays_destroyed: Sats) -> Result<()> { fn truncate_push<R: RealizedOps>(&mut self, height: Height, state: &CohortState<R>) -> Result<()> {
self.truncate_push(height, sent, satblocks_destroyed, satdays_destroyed) self.truncate_push(height, state)
} }
fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> { fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> {
self.validate_computed_versions(base_version) 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) self.compute_from_stateful(starting_indexes, others, exit)
} }
fn compute_rest_part1(&mut self, blocks: &blocks::Vecs, starting_indexes: &Indexes, exit: &Exit) -> Result<()> { 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 { impl ActivityLike for ActivityFull {
fn as_base(&self) -> &ActivityBase { &self.inner } fn as_core(&self) -> &ActivityCore { &self.inner }
fn as_base_mut(&mut self) -> &mut ActivityBase { &mut self.inner } fn as_core_mut(&mut self) -> &mut ActivityCore { &mut self.inner }
fn min_len(&self) -> usize { self.inner.min_len() } fn min_len(&self) -> usize { self.full_min_len() }
fn truncate_push(&mut self, height: Height, sent: Sats, satblocks_destroyed: Sats, satdays_destroyed: Sats) -> Result<()> { fn truncate_push<R: RealizedOps>(&mut self, height: Height, state: &CohortState<R>) -> Result<()> {
self.inner.truncate_push(height, sent, satblocks_destroyed, satdays_destroyed) self.full_truncate_push(height, state)
} }
fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> { fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> {
self.inner.validate_computed_versions(base_version) 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) self.compute_from_stateful(starting_indexes, others, exit)
} }
fn compute_rest_part1(&mut self, blocks: &blocks::Vecs, starting_indexes: &Indexes, exit: &Exit) -> Result<()> { 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_error::Result;
use brk_traversable::Traversable; use brk_traversable::Traversable;
use brk_types::{ use brk_types::{
Bitcoin, Cents, Dollars, Height, Indexes, Sats, SatsSigned, StoredF32, StoredI64, StoredU64, Cents, Dollars, Height, Indexes, Sats, SatsSigned, StoredI64, StoredU64,
Version, Version,
}; };
use vecdb::AnyStoredVec; use vecdb::AnyStoredVec;
@@ -10,11 +10,11 @@ use vecdb::{Exit, ReadableVec, Rw, StorageMode};
use crate::{blocks, prices}; use crate::{blocks, prices};
use crate::internal::{ComputedPerBlock, RollingDeltaExcept1m}; use crate::internal::RollingDeltaExcept1m;
use crate::distribution::metrics::{ use crate::distribution::metrics::{
ActivityFull, CohortMetricsBase, CostBasis, ImportConfig, OutputsMetrics, ActivityFull, CohortMetricsBase, CostBasis, ImportConfig, OutputsFull,
RealizedAdjusted, RealizedFull, RelativeForAll, SupplyMetrics, UnrealizedFull, AdjustedSopr, RealizedFull, RelativeForAll, SupplyFull, UnrealizedFull,
}; };
/// All-cohort metrics: extended realized + adjusted (as composable add-on), /// All-cohort metrics: extended realized + adjusted (as composable add-on),
@@ -24,16 +24,15 @@ use crate::distribution::metrics::{
pub struct AllCohortMetrics<M: StorageMode = Rw> { pub struct AllCohortMetrics<M: StorageMode = Rw> {
#[traversable(skip)] #[traversable(skip)]
pub filter: Filter, pub filter: Filter,
pub supply: Box<SupplyMetrics<M>>, pub supply: Box<SupplyFull<M>>,
pub outputs: Box<OutputsMetrics<M>>, pub outputs: Box<OutputsFull<M>>,
pub activity: Box<ActivityFull<M>>, pub activity: Box<ActivityFull<M>>,
pub realized: Box<RealizedFull<M>>, pub realized: Box<RealizedFull<M>>,
pub cost_basis: Box<CostBasis<M>>, pub cost_basis: Box<CostBasis<M>>,
pub unrealized: Box<UnrealizedFull<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 relative: Box<RelativeForAll<M>>,
pub dormancy: ComputedPerBlock<StoredF32, M>,
pub velocity: ComputedPerBlock<StoredF32, M>,
#[traversable(wrap = "supply", rename = "delta")] #[traversable(wrap = "supply", rename = "delta")]
pub supply_delta_extended: RollingDeltaExcept1m<Sats, SatsSigned, M>, 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.realized.collect_vecs_mut());
vecs.extend(self.cost_basis.collect_vecs_mut()); vecs.extend(self.cost_basis.collect_vecs_mut());
vecs.extend(self.unrealized.collect_vecs_mut()); vecs.extend(self.unrealized.collect_vecs_mut());
vecs.push(&mut self.dormancy.height);
vecs.push(&mut self.velocity.height);
vecs vecs
} }
} }
@@ -86,26 +83,24 @@ impl AllCohortMetrics {
/// reference for relative metric lazy vecs in other cohorts. /// reference for relative metric lazy vecs in other cohorts.
pub(crate) fn forced_import_with_supply( pub(crate) fn forced_import_with_supply(
cfg: &ImportConfig, cfg: &ImportConfig,
supply: SupplyMetrics, supply: SupplyFull,
) -> Result<Self> { ) -> Result<Self> {
let unrealized = UnrealizedFull::forced_import(cfg)?; let unrealized = UnrealizedFull::forced_import(cfg)?;
let realized = RealizedFull::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)?; let relative = RelativeForAll::forced_import(cfg)?;
Ok(Self { Ok(Self {
filter: cfg.filter.clone(), filter: cfg.filter.clone(),
supply: Box::new(supply), 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)?), activity: Box::new(ActivityFull::forced_import(cfg)?),
realized: Box::new(realized), realized: Box::new(realized),
cost_basis: Box::new(CostBasis::forced_import(cfg)?), cost_basis: Box::new(CostBasis::forced_import(cfg)?),
unrealized: Box::new(unrealized), unrealized: Box::new(unrealized),
adjusted: Box::new(adjusted), asopr: Box::new(asopr),
relative: Box::new(relative), 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)?, supply_delta_extended: cfg.import("supply_delta", Version::ONE)?,
utxo_count_delta_extended: cfg.import("utxo_count_delta", Version::ONE)?, utxo_count_delta_extended: cfg.import("utxo_count_delta", Version::ONE)?,
}) })
@@ -131,11 +126,11 @@ impl AllCohortMetrics {
exit, exit,
)?; )?;
self.adjusted.compute_rest_part2( self.asopr.compute_rest_part2(
blocks, blocks,
starting_indexes, starting_indexes,
&self.realized.value_created.height, &self.realized.minimal.sopr.value_created.raw.height,
&self.realized.value_destroyed.height, &self.realized.minimal.sopr.value_destroyed.raw.height,
up_to_1h_value_created, up_to_1h_value_created,
up_to_1h_value_destroyed, up_to_1h_value_destroyed,
exit, exit,
@@ -163,33 +158,9 @@ impl AllCohortMetrics {
exit, exit,
)?; )?;
self.dormancy.height.compute_transform2( self.activity.compute_rest_part2(
starting_indexes.height, starting_indexes,
&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.supply.total.sats.height, &self.supply.total.sats.height,
|(i, sent_sats, supply_sats, ..)| {
let supply = supply_sats.as_u128() as f64;
if supply == 0.0 {
(i, StoredF32::from(0.0f32))
} else {
(i, StoredF32::from((sent_sats.as_u128() as f64 / supply) as f32))
}
},
exit, exit,
)?; )?;

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,41 +1,35 @@
use brk_cohort::Filter; use brk_cohort::Filter;
use brk_error::Result; use brk_error::Result;
use brk_traversable::Traversable; use brk_traversable::Traversable;
use brk_types::{Indexes, Version}; use brk_types::Indexes;
use vecdb::{AnyStoredVec, Exit, Rw, StorageMode}; use vecdb::{AnyStoredVec, Exit, Rw, StorageMode};
use crate::distribution::metrics::unrealized::UnrealizedMinimal;
use crate::{blocks, prices}; use crate::{blocks, prices};
use crate::distribution::metrics::{ use crate::distribution::metrics::{
ActivityCore, ImportConfig, OutputsMetrics, RealizedMinimal, SupplyMetrics, ImportConfig, OutputsBase, RealizedMinimal, SupplyBase,
}; };
/// MinimalCohortMetrics: supply, outputs, sent+ema, realized cap/price/mvrv/profit/loss, /// MinimalCohortMetrics: supply, outputs, realized cap/price/mvrv/profit/loss + value_created/destroyed.
/// supply in profit/loss.
/// ///
/// Used for type_, amount, and address cohorts. /// Used for amount_range cohorts.
/// Does NOT implement CohortMetricsBase — standalone, not aggregatable via trait. /// Does NOT implement CohortMetricsBase — standalone, not aggregatable via trait.
#[derive(Traversable)] #[derive(Traversable)]
pub struct MinimalCohortMetrics<M: StorageMode = Rw> { pub struct MinimalCohortMetrics<M: StorageMode = Rw> {
#[traversable(skip)] #[traversable(skip)]
pub filter: Filter, pub filter: Filter,
pub supply: Box<SupplyMetrics<M>>, pub supply: Box<SupplyBase<M>>,
pub outputs: Box<OutputsMetrics<M>>, pub outputs: Box<OutputsBase<M>>,
pub activity: Box<ActivityCore<M>>,
pub realized: Box<RealizedMinimal<M>>, pub realized: Box<RealizedMinimal<M>>,
pub unrealized: Box<UnrealizedMinimal<M>>,
} }
impl MinimalCohortMetrics { impl MinimalCohortMetrics {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> { pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
Ok(Self { Ok(Self {
filter: cfg.filter.clone(), filter: cfg.filter.clone(),
supply: Box::new(SupplyMetrics::forced_import(cfg)?), supply: Box::new(SupplyBase::forced_import(cfg)?),
outputs: Box::new(OutputsMetrics::forced_import(cfg)?), outputs: Box::new(OutputsBase::forced_import(cfg)?),
activity: Box::new(ActivityCore::forced_import(cfg)?),
realized: Box::new(RealizedMinimal::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 self.supply
.min_len() .min_len()
.min(self.outputs.min_len()) .min(self.outputs.min_len())
.min(self.activity.min_len())
.min(self.realized.min_stateful_height_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> { pub(crate) fn collect_all_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
let mut vecs: Vec<&mut dyn AnyStoredVec> = Vec::new(); let mut vecs: Vec<&mut dyn AnyStoredVec> = Vec::new();
vecs.extend(self.supply.collect_vecs_mut()); vecs.extend(self.supply.collect_vecs_mut());
vecs.extend(self.outputs.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.realized.collect_vecs_mut());
vecs.extend(self.unrealized.collect_vecs_mut());
vecs vecs
} }
@@ -83,14 +68,6 @@ impl MinimalCohortMetrics {
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
exit, exit,
)?; )?;
self.activity.compute_from_stateful(
starting_indexes,
&others
.iter()
.map(|v| v.activity.as_ref())
.collect::<Vec<_>>(),
exit,
)?;
self.realized.compute_from_stateful( self.realized.compute_from_stateful(
starting_indexes, starting_indexes,
&others &others
@@ -99,14 +76,6 @@ impl MinimalCohortMetrics {
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
exit, exit,
)?; )?;
self.unrealized.compute_from_sources(
starting_indexes,
&others
.iter()
.map(|v| v.unrealized.as_ref())
.collect::<Vec<_>>(),
exit,
)?;
Ok(()) Ok(())
} }
@@ -118,15 +87,8 @@ impl MinimalCohortMetrics {
exit: &Exit, exit: &Exit,
) -> Result<()> { ) -> Result<()> {
self.supply.compute(prices, starting_indexes.height, exit)?; 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 self.realized
.compute_rest_part1(blocks, starting_indexes, exit)?; .compute_rest_part1(blocks, starting_indexes, exit)?;
self.unrealized
.compute_rest(prices, starting_indexes.height, exit)?;
Ok(()) Ok(())
} }

View File

@@ -4,6 +4,7 @@ mod core;
mod extended; mod extended;
mod extended_adjusted; mod extended_adjusted;
mod minimal; mod minimal;
mod r#type;
pub use all::AllCohortMetrics; pub use all::AllCohortMetrics;
pub use basic::BasicCohortMetrics; pub use basic::BasicCohortMetrics;
@@ -11,3 +12,4 @@ pub use core::CoreCohortMetrics;
pub use extended::ExtendedCohortMetrics; pub use extended::ExtendedCohortMetrics;
pub use extended_adjusted::ExtendedAdjustedCohortMetrics; pub use extended_adjusted::ExtendedAdjustedCohortMetrics;
pub use minimal::MinimalCohortMetrics; 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::{ use crate::{
indexes, indexes,
internal::{ internal::{
AmountPerBlock, AmountPerBlockCumulative, CentsType, ComputedPerBlock, AmountPerBlock, AmountPerBlockCumulative, AmountPerBlockWithSum24h, CentsType, ComputedPerBlock,
ComputedPerBlockCumulative, ComputedPerBlockCumulativeSum, RatioPerBlock, ComputedPerBlockCumulative, ComputedPerBlockCumulativeSum, FiatPerBlockWithSum24h,
PerBlockWithSum24h, RatioPerBlock, RollingWindow24hAmountPerBlock,
RollingWindow24hFiatPerBlock, RollingWindow24hPerBlock,
FiatPerBlock, FiatRollingDelta1m, FiatRollingDeltaExcept1m, NumericValue, FiatPerBlock, FiatRollingDelta1m, FiatRollingDeltaExcept1m, NumericValue,
PercentPerBlock, PercentRollingWindows, Price, RollingDelta1m, RollingDeltaExcept1m, PercentPerBlock, PercentRollingWindows, Price, RollingDelta1m, RollingDeltaExcept1m,
RollingWindow24h, RollingWindows, RollingWindowsFrom1w, RollingWindows, RollingWindowsFrom1w,
}, },
}; };
@@ -37,7 +39,9 @@ macro_rules! impl_config_import {
impl_config_import!( impl_config_import!(
AmountPerBlock, AmountPerBlock,
AmountPerBlockCumulative, AmountPerBlockCumulative,
RatioPerBlock, RollingWindow24hAmountPerBlock,
RatioPerBlock<BasisPoints32>,
RatioPerBlock<BasisPointsSigned32>,
PercentPerBlock<BasisPoints16>, PercentPerBlock<BasisPoints16>,
PercentPerBlock<BasisPoints32>, PercentPerBlock<BasisPoints32>,
PercentPerBlock<BasisPointsSigned32>, 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) 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> { fn config_import(cfg: &ImportConfig, suffix: &str, offset: Version) -> Result<Self> {
Self::forced_import(cfg.db, &cfg.name(suffix), cfg.version + offset, cfg.indexes) 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> { impl<T: NumericValue + JsonSchema> ConfigImport for RollingWindowsFrom1w<T> {
fn config_import(cfg: &ImportConfig, suffix: &str, offset: Version) -> Result<Self> { fn config_import(cfg: &ImportConfig, suffix: &str, offset: Version) -> Result<Self> {
Self::forced_import(cfg.db, &cfg.name(suffix), cfg.version + offset, cfg.indexes) 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 { macro_rules! impl_cohort_accessors {
() => { () => {
fn filter(&self) -> &brk_cohort::Filter { &self.filter } fn filter(&self) -> &brk_cohort::Filter { &self.filter }
fn supply(&self) -> &$crate::distribution::metrics::SupplyMetrics { &self.supply } fn supply(&self) -> &$crate::distribution::metrics::SupplyFull { &self.supply }
fn supply_mut(&mut self) -> &mut $crate::distribution::metrics::SupplyMetrics { &mut self.supply } fn supply_mut(&mut self) -> &mut $crate::distribution::metrics::SupplyFull { &mut self.supply }
fn outputs(&self) -> &$crate::distribution::metrics::OutputsMetrics { &self.outputs } fn outputs(&self) -> &$crate::distribution::metrics::OutputsFull { &self.outputs }
fn outputs_mut(&mut self) -> &mut $crate::distribution::metrics::OutputsMetrics { &mut self.outputs } fn outputs_mut(&mut self) -> &mut $crate::distribution::metrics::OutputsFull { &mut self.outputs }
fn activity(&self) -> &Self::ActivityVecs { &self.activity } fn activity(&self) -> &Self::ActivityVecs { &self.activity }
fn activity_mut(&mut self) -> &mut Self::ActivityVecs { &mut self.activity } fn activity_mut(&mut self) -> &mut Self::ActivityVecs { &mut self.activity }
fn realized(&self) -> &Self::RealizedVecs { &self.realized } fn realized(&self) -> &Self::RealizedVecs { &self.realized }
@@ -42,24 +42,24 @@ mod relative;
mod supply; mod supply;
mod unrealized; mod unrealized;
pub use activity::{ActivityBase, ActivityCore, ActivityFull, ActivityLike}; pub use activity::{ActivityCore, ActivityFull, ActivityLike};
pub use cohort::{ pub use cohort::{
AllCohortMetrics, BasicCohortMetrics, CoreCohortMetrics, ExtendedAdjustedCohortMetrics, AllCohortMetrics, BasicCohortMetrics, CoreCohortMetrics,
ExtendedCohortMetrics, MinimalCohortMetrics, ExtendedAdjustedCohortMetrics, ExtendedCohortMetrics, MinimalCohortMetrics, TypeCohortMetrics,
}; };
pub use config::ImportConfig; pub use config::ImportConfig;
pub use cost_basis::CostBasis; pub use cost_basis::CostBasis;
pub use profitability::ProfitabilityMetrics; pub use profitability::ProfitabilityMetrics;
pub use outputs::OutputsMetrics; pub use outputs::{OutputsBase, OutputsFull};
pub use realized::{ pub use realized::{
RealizedAdjusted, RealizedBase, RealizedCore, RealizedFull, RealizedFullAccum, RealizedLike, AdjustedSopr, RealizedCore, RealizedFull, RealizedFullAccum, RealizedLike,
RealizedMinimal, RealizedMinimal,
}; };
pub use relative::{ pub use relative::{
RelativeForAll, RelativeToAll, RelativeWithExtended, RelativeForAll, RelativeToAll, RelativeWithExtended,
}; };
pub use supply::SupplyMetrics; pub use supply::{SupplyBase, SupplyFull};
pub use unrealized::{UnrealizedBase, UnrealizedCore, UnrealizedFull, UnrealizedLike}; pub use unrealized::{UnrealizedBase, UnrealizedBasic, UnrealizedCore, UnrealizedFull, UnrealizedLike};
use brk_cohort::Filter; use brk_cohort::Filter;
use brk_error::Result; use brk_error::Result;
@@ -72,6 +72,9 @@ pub trait CohortMetricsState {
type Realized: RealizedOps; type Realized: RealizedOps;
} }
impl<M: StorageMode> CohortMetricsState for TypeCohortMetrics<M> {
type Realized = MinimalRealizedState;
}
impl<M: StorageMode> CohortMetricsState for MinimalCohortMetrics<M> { impl<M: StorageMode> CohortMetricsState for MinimalCohortMetrics<M> {
type Realized = MinimalRealizedState; type Realized = MinimalRealizedState;
} }
@@ -97,10 +100,10 @@ pub trait CohortMetricsBase: CohortMetricsState<Realized = RealizedState> + Send
type UnrealizedVecs: UnrealizedLike; type UnrealizedVecs: UnrealizedLike;
fn filter(&self) -> &Filter; fn filter(&self) -> &Filter;
fn supply(&self) -> &SupplyMetrics; fn supply(&self) -> &SupplyFull;
fn supply_mut(&mut self) -> &mut SupplyMetrics; fn supply_mut(&mut self) -> &mut SupplyFull;
fn outputs(&self) -> &OutputsMetrics; fn outputs(&self) -> &OutputsFull;
fn outputs_mut(&mut self) -> &mut OutputsMetrics; fn outputs_mut(&mut self) -> &mut OutputsFull;
fn activity(&self) -> &Self::ActivityVecs; fn activity(&self) -> &Self::ActivityVecs;
fn activity_mut(&mut self) -> &mut Self::ActivityVecs; fn activity_mut(&mut self) -> &mut Self::ActivityVecs;
fn realized(&self) -> &Self::RealizedVecs; fn realized(&self) -> &Self::RealizedVecs;
@@ -108,13 +111,13 @@ pub trait CohortMetricsBase: CohortMetricsState<Realized = RealizedState> + Send
fn unrealized(&self) -> &Self::UnrealizedVecs; fn unrealized(&self) -> &Self::UnrealizedVecs;
fn unrealized_mut(&mut self) -> &mut Self::UnrealizedVecs; fn unrealized_mut(&mut self) -> &mut Self::UnrealizedVecs;
/// Convenience: access activity as `&ActivityBase` (via `ActivityLike::as_base`). /// Convenience: access activity as `&ActivityCore` (via `ActivityLike::as_core`).
fn activity_base(&self) -> &ActivityBase { self.activity().as_base() } fn activity_core(&self) -> &ActivityCore { self.activity().as_core() }
fn activity_base_mut(&mut self) -> &mut ActivityBase { self.activity_mut().as_base_mut() } fn activity_core_mut(&mut self) -> &mut ActivityCore { self.activity_mut().as_core_mut() }
/// Convenience: access realized as `&RealizedBase` (via `RealizedLike::as_base`). /// Convenience: access realized as `&RealizedCore` (via `RealizedLike::as_core`).
fn realized_base(&self) -> &RealizedBase { self.realized().as_base() } fn realized_core(&self) -> &RealizedCore { self.realized().as_core() }
fn realized_base_mut(&mut self) -> &mut RealizedBase { self.realized_mut().as_base_mut() } fn realized_core_mut(&mut self) -> &mut RealizedCore { self.realized_mut().as_core_mut() }
/// Convenience: access unrealized as `&UnrealizedBase` (via `UnrealizedLike::as_base`). /// Convenience: access unrealized as `&UnrealizedBase` (via `UnrealizedLike::as_base`).
fn unrealized_base(&self) -> &UnrealizedBase { self.unrealized().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<()> { fn truncate_push(&mut self, height: Height, state: &CohortState<RealizedState>) -> Result<()> {
self.supply_mut() self.supply_mut().truncate_push(height, state)?;
.truncate_push(height, state.supply.value)?; self.outputs_mut().truncate_push(height, state)?;
self.outputs_mut() self.activity_mut().truncate_push(height, state)?;
.truncate_push(height, state.supply.utxo_count)?; self.realized_mut().truncate_push(height, state)?;
self.activity_mut().truncate_push(
height,
state.sent,
state.satblocks_destroyed,
state.satdays_destroyed,
)?;
self.realized_mut()
.truncate_push(height, &state.realized)?;
Ok(()) Ok(())
} }
@@ -188,7 +183,7 @@ pub trait CohortMetricsBase: CohortMetricsState<Realized = RealizedState> + Send
.compute_rest_part1(blocks, starting_indexes, exit)?; .compute_rest_part1(blocks, starting_indexes, exit)?;
self.unrealized_mut() self.unrealized_mut()
.compute_rest(prices, starting_indexes, exit)?; .compute_rest(blocks, prices, starting_indexes, exit)?;
self.unrealized_mut() self.unrealized_mut()
.compute_net_sentiment_height(starting_indexes, exit)?; .compute_net_sentiment_height(starting_indexes, exit)?;
@@ -215,12 +210,12 @@ pub trait CohortMetricsBase: CohortMetricsState<Realized = RealizedState> + Send
)?; )?;
self.activity_mut().compute_from_stateful( self.activity_mut().compute_from_stateful(
starting_indexes, starting_indexes,
&others.iter().map(|v| v.activity_base()).collect::<Vec<_>>(), &others.iter().map(|v| v.activity_core()).collect::<Vec<_>>(),
exit, exit,
)?; )?;
self.realized_mut().compute_from_stateful( self.realized_mut().compute_from_stateful(
starting_indexes, starting_indexes,
&others.iter().map(|v| v.realized_base()).collect::<Vec<_>>(), &others.iter().map(|v| v.realized_core()).collect::<Vec<_>>(),
exit, exit,
)?; )?;
self.unrealized_base_mut().compute_from_stateful( 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; use crate::distribution::metrics::ImportConfig;
#[derive(Traversable)] #[derive(Traversable)]
pub struct RealizedAdjusted<M: StorageMode = Rw> { pub struct AdjustedSopr<M: StorageMode = Rw> {
pub value_created: ComputedPerBlock<Cents, M>, pub value_created: ComputedPerBlock<Cents, M>,
pub value_destroyed: ComputedPerBlock<Cents, M>, pub value_destroyed: ComputedPerBlock<Cents, M>,
pub value_created_sum: RollingWindows<Cents, M>, pub value_created_sum: RollingWindows<Cents, M>,
pub value_destroyed_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> { pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
Ok(RealizedAdjusted { Ok(Self {
value_created: cfg.import("adjusted_value_created", Version::ZERO)?, value_created: cfg.import("adjusted_value_created", Version::ZERO)?,
value_destroyed: cfg.import("adjusted_value_destroyed", Version::ZERO)?, value_destroyed: cfg.import("adjusted_value_destroyed", Version::ZERO)?,
value_created_sum: cfg.import("adjusted_value_created", Version::ONE)?, value_created_sum: cfg.import("adjusted_value_created", Version::ONE)?,
value_destroyed_sum: cfg.import("adjusted_value_destroyed", 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 // SOPR ratios from rolling sums
for ((sopr, vc), vd) in self for ((sopr, vc), vd) in self
.sopr .ratio
.as_mut_array() .as_mut_array()
.into_iter() .into_iter()
.zip(self.value_created_sum.as_array()) .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::{ use crate::{
blocks, blocks,
distribution::state::RealizedOps, distribution::state::{CohortState, RealizedOps},
internal::{ internal::{
ComputedPerBlock, FiatRollingDelta1m, LazyPerBlock, NegCentsUnsignedToDollars, AmountPerBlockWithSum24h, ComputedPerBlock, FiatRollingDelta1m, LazyPerBlock,
RatioCents64, RollingWindow24h, NegCentsUnsignedToDollars, PerBlockWithSum24h, RatioCents64,
RollingWindow24hPerBlock,
}, },
prices, prices,
}; };
@@ -20,6 +21,17 @@ use crate::distribution::metrics::ImportConfig;
use super::RealizedMinimal; 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)] #[derive(Deref, DerefMut, Traversable)]
pub struct RealizedCore<M: StorageMode = Rw> { pub struct RealizedCore<M: StorageMode = Rw> {
#[deref] #[deref]
@@ -27,17 +39,19 @@ pub struct RealizedCore<M: StorageMode = Rw> {
#[traversable(flatten)] #[traversable(flatten)]
pub minimal: RealizedMinimal<M>, 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>, pub cap_delta: FiatRollingDelta1m<Cents, CentsSigned, M>,
#[traversable(wrap = "loss", rename = "neg")]
pub neg_loss: LazyPerBlock<Dollars, Cents>, pub neg_loss: LazyPerBlock<Dollars, Cents>,
pub net_pnl: ComputedPerBlock<CentsSigned, M>, pub net_pnl: PerBlockWithSum24h<CentsSigned, M>,
pub net_pnl_sum: RollingWindow24h<CentsSigned, M>, pub sopr: RealizedSoprCore<M>,
pub sent: RealizedSentCore<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>,
} }
impl RealizedCore { impl RealizedCore {
@@ -50,55 +64,57 @@ impl RealizedCore {
let neg_realized_loss = LazyPerBlock::from_height_source::<NegCentsUnsignedToDollars>( let neg_realized_loss = LazyPerBlock::from_height_source::<NegCentsUnsignedToDollars>(
&cfg.name("neg_realized_loss"), &cfg.name("neg_realized_loss"),
cfg.version + Version::ONE, cfg.version + Version::ONE,
minimal.loss.height.read_only_boxed_clone(), minimal.loss.raw.cents.height.read_only_boxed_clone(),
cfg.indexes, 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 { Ok(Self {
minimal, 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)?, cap_delta: cfg.import("realized_cap_delta", v1)?,
neg_loss: neg_realized_loss, neg_loss: neg_realized_loss,
net_pnl: net_realized_pnl, net_pnl: cfg.import("net_realized_pnl", v1)?,
net_pnl_sum: net_realized_pnl_sum, sopr: RealizedSoprCore {
value_created, ratio: cfg.import("sopr", v1)?,
value_destroyed, },
value_created_sum, sent: RealizedSentCore {
value_destroyed_sum, in_profit: cfg.import("sent_in_profit", v1)?,
sopr, in_loss: cfg.import("sent_in_loss", v1)?,
},
}) })
} }
pub(crate) fn min_stateful_height_len(&self) -> usize { pub(crate) fn min_stateful_height_len(&self) -> usize {
self.minimal self.minimal
.min_stateful_height_len() .min_stateful_height_len()
.min(self.value_created.height.len()) .min(self.sent.in_profit.raw.sats.height.len())
.min(self.value_destroyed.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.minimal.truncate_push(height, state)?;
self.value_created self.sent
.in_profit
.raw
.sats
.height .height
.truncate_push(height, state.value_created())?; .truncate_push(height, state.realized.sent_in_profit())?;
self.value_destroyed self.sent
.in_loss
.raw
.sats
.height .height
.truncate_push(height, state.value_destroyed())?; .truncate_push(height, state.realized.sent_in_loss())?;
Ok(()) Ok(())
} }
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> { pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
let mut vecs = self.minimal.collect_vecs_mut(); let mut vecs = self.minimal.collect_vecs_mut();
vecs.push(&mut self.value_created.height as &mut dyn AnyStoredVec); vecs.push(&mut self.sent.in_profit.raw.sats.height as &mut dyn AnyStoredVec);
vecs.push(&mut self.value_destroyed.height); 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 vecs
} }
@@ -112,8 +128,10 @@ impl RealizedCore {
self.minimal self.minimal
.compute_from_stateful(starting_indexes, &minimal_refs, exit)?; .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; sent.in_profit.raw.sats.height);
sum_others!(self, starting_indexes, others, exit; value_destroyed.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(()) Ok(())
} }
@@ -127,10 +145,21 @@ impl RealizedCore {
self.minimal self.minimal
.compute_rest_part1(blocks, starting_indexes, exit)?; .compute_rest_part1(blocks, starting_indexes, exit)?;
self.net_pnl.height.compute_transform2( self.profit_cumulative.height.compute_cumulative(
starting_indexes.height, starting_indexes.height,
&self.minimal.profit.height, &self.minimal.profit.raw.cents.height,
&self.minimal.loss.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, profit, loss, ..)| {
( (
i, i,
@@ -157,36 +186,48 @@ impl RealizedCore {
self.cap_delta.compute( self.cap_delta.compute(
starting_indexes.height, starting_indexes.height,
&blocks.lookback.height_1m_ago, &blocks.lookback.height_1m_ago,
&self.minimal.cap_cents.height, &self.minimal.cap.cents.height,
exit, exit,
)?; )?;
self.net_pnl_sum.compute_rolling_sum( self.net_pnl.sum.compute_rolling_sum(
starting_indexes.height, starting_indexes.height,
&blocks.lookback.height_24h_ago, &blocks.lookback.height_24h_ago,
&self.net_pnl.height, &self.net_pnl.raw.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,
exit, exit,
)?; )?;
self.sopr self.sopr
.ratio
._24h ._24h
.compute_binary::<Cents, Cents, RatioCents64>( .compute_binary::<Cents, Cents, RatioCents64>(
starting_indexes.height, starting_indexes.height,
&self.value_created_sum._24h.height, &self.minimal.sopr.value_created.sum._24h.height,
&self.value_destroyed_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, exit,
)?; )?;

View File

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

View File

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

View File

@@ -1,11 +1,9 @@
mod adjusted; mod adjusted;
mod base;
mod core; mod core;
mod full; mod full;
mod minimal; mod minimal;
pub use adjusted::RealizedAdjusted; pub use adjusted::AdjustedSopr;
pub use base::RealizedBase;
pub use self::core::RealizedCore; pub use self::core::RealizedCore;
pub use full::{RealizedFull, RealizedFullAccum}; pub use full::{RealizedFull, RealizedFullAccum};
pub use minimal::RealizedMinimal; pub use minimal::RealizedMinimal;
@@ -14,53 +12,53 @@ use brk_error::Result;
use brk_types::{Height, Indexes}; use brk_types::{Height, Indexes};
use vecdb::Exit; use vecdb::Exit;
use crate::{blocks, distribution::state::RealizedState}; use crate::{blocks, distribution::state::{CohortState, RealizedState}};
/// Polymorphic dispatch for realized metric types. /// 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). /// but with different behavior (Full checks/pushes more fields).
/// This trait enables `CohortMetricsBase` to dispatch correctly via associated type. /// This trait enables `CohortMetricsBase` to dispatch correctly via associated type.
pub trait RealizedLike: Send + Sync { pub trait RealizedLike: Send + Sync {
fn as_base(&self) -> &RealizedBase; fn as_core(&self) -> &RealizedCore;
fn as_base_mut(&mut self) -> &mut RealizedBase; fn as_core_mut(&mut self) -> &mut RealizedCore;
fn min_stateful_height_len(&self) -> usize; 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_rest_part1(&mut self, blocks: &blocks::Vecs, starting_indexes: &Indexes, exit: &Exit) -> Result<()>;
fn compute_from_stateful( fn compute_from_stateful(
&mut self, &mut self,
starting_indexes: &Indexes, starting_indexes: &Indexes,
others: &[&RealizedBase], others: &[&RealizedCore],
exit: &Exit, exit: &Exit,
) -> Result<()>; ) -> Result<()>;
} }
impl RealizedLike for RealizedBase { impl RealizedLike for RealizedCore {
fn as_base(&self) -> &RealizedBase { self } fn as_core(&self) -> &RealizedCore { self }
fn as_base_mut(&mut self) -> &mut RealizedBase { self } fn as_core_mut(&mut self) -> &mut RealizedCore { self }
fn min_stateful_height_len(&self) -> usize { self.min_stateful_height_len() } 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) self.truncate_push(height, state)
} }
fn compute_rest_part1(&mut self, blocks: &blocks::Vecs, starting_indexes: &Indexes, exit: &Exit) -> Result<()> { fn compute_rest_part1(&mut self, blocks: &blocks::Vecs, starting_indexes: &Indexes, exit: &Exit) -> Result<()> {
self.compute_rest_part1(blocks, starting_indexes, exit) 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) self.compute_from_stateful(starting_indexes, others, exit)
} }
} }
impl RealizedLike for RealizedFull { impl RealizedLike for RealizedFull {
fn as_base(&self) -> &RealizedBase { &self.base } fn as_core(&self) -> &RealizedCore { &self.core }
fn as_base_mut(&mut self) -> &mut RealizedBase { &mut self.base } fn as_core_mut(&mut self) -> &mut RealizedCore { &mut self.core }
fn min_stateful_height_len(&self) -> usize { self.min_stateful_height_len() } 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) self.truncate_push(height, state)
} }
fn compute_rest_part1(&mut self, blocks: &blocks::Vecs, starting_indexes: &Indexes, exit: &Exit) -> Result<()> { fn compute_rest_part1(&mut self, blocks: &blocks::Vecs, starting_indexes: &Indexes, exit: &Exit) -> Result<()> {
self.compute_rest_part1(blocks, starting_indexes, exit) 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) 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). /// Extended relative metrics for own market cap (extended && rel_to_all).
#[derive(Traversable)] #[derive(Traversable)]
pub struct RelativeExtendedOwnMarketCap<M: StorageMode = Rw> { 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>, 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>, 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>, 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 self.unrealized_profit_rel_to_own_market_cap
.compute_binary::<Dollars, Dollars, RatioDollarsBp16>( .compute_binary::<Dollars, Dollars, RatioDollarsBp16>(
max_from, max_from,
&unrealized.profit.usd.height, &unrealized.profit.raw.usd.height,
own_market_cap, own_market_cap,
exit, exit,
)?; )?;
self.unrealized_loss_rel_to_own_market_cap self.unrealized_loss_rel_to_own_market_cap
.compute_binary::<Dollars, Dollars, RatioDollarsBp32>( .compute_binary::<Dollars, Dollars, RatioDollarsBp32>(
max_from, max_from,
&unrealized.loss.usd.height, &unrealized.loss.raw.usd.height,
own_market_cap, own_market_cap,
exit, exit,
)?; )?;

View File

@@ -10,8 +10,11 @@ use crate::distribution::metrics::{ImportConfig, UnrealizedCore};
/// Extended relative metrics for own total unrealized PnL (extended only). /// Extended relative metrics for own total unrealized PnL (extended only).
#[derive(Traversable)] #[derive(Traversable)]
pub struct RelativeExtendedOwnPnl<M: StorageMode = Rw> { 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>, 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>, 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>, 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 self.unrealized_profit_rel_to_own_gross_pnl
.compute_binary::<Dollars, Dollars, RatioDollarsBp16>( .compute_binary::<Dollars, Dollars, RatioDollarsBp16>(
max_from, max_from,
&unrealized.profit.usd.height, &unrealized.profit.raw.usd.height,
gross_pnl_usd, gross_pnl_usd,
exit, exit,
)?; )?;
self.unrealized_loss_rel_to_own_gross_pnl self.unrealized_loss_rel_to_own_gross_pnl
.compute_binary::<Dollars, Dollars, RatioDollarsBp16>( .compute_binary::<Dollars, Dollars, RatioDollarsBp16>(
max_from, max_from,
&unrealized.loss.usd.height, &unrealized.loss.raw.usd.height,
gross_pnl_usd, gross_pnl_usd,
exit, exit,
)?; )?;

View File

@@ -1,44 +1,31 @@
use brk_error::Result; use brk_error::Result;
use brk_traversable::Traversable; use brk_traversable::Traversable;
use brk_types::{BasisPoints16, BasisPointsSigned32, Dollars, Height, Sats, StoredF32, Version}; use brk_types::{BasisPoints16, Dollars, Height, Sats, Version};
use vecdb::{Exit, ReadableCloneableVec, ReadableVec, Rw, StorageMode}; use vecdb::{Exit, ReadableVec, Rw, StorageMode};
use crate::internal::{ use crate::{
Bps32ToFloat, LazyPerBlock, PercentPerBlock, RatioDollarsBp16, RatioDollarsBps32, RatioSatsBp16, distribution::metrics::{ImportConfig, UnrealizedCore},
internal::{PercentPerBlock, RatioDollarsBp16, RatioSatsBp16},
}; };
use crate::distribution::metrics::{ImportConfig, UnrealizedCore};
/// Full relative metrics (sth/lth/all tier). /// Full relative metrics (sth/lth/all tier).
#[derive(Traversable)] #[derive(Traversable)]
pub struct RelativeFull<M: StorageMode = Rw> { 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>, 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>, 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>, 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 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 { impl RelativeFull {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> { pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
let v1 = Version::ONE; let v1 = Version::ONE;
let v2 = Version::new(2); 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 { Ok(Self {
supply_in_profit_rel_to_own_supply: cfg supply_in_profit_rel_to_own_supply: cfg
@@ -48,8 +35,6 @@ impl RelativeFull {
.import("unrealized_profit_rel_to_market_cap", v2)?, .import("unrealized_profit_rel_to_market_cap", v2)?,
unrealized_loss_rel_to_market_cap: cfg unrealized_loss_rel_to_market_cap: cfg
.import("unrealized_loss_rel_to_market_cap", v2)?, .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 self.unrealized_profit_rel_to_market_cap
.compute_binary::<Dollars, Dollars, RatioDollarsBp16>( .compute_binary::<Dollars, Dollars, RatioDollarsBp16>(
max_from, max_from,
&unrealized.profit.usd.height, &unrealized.profit.raw.usd.height,
market_cap, market_cap,
exit, exit,
)?; )?;
self.unrealized_loss_rel_to_market_cap self.unrealized_loss_rel_to_market_cap
.compute_binary::<Dollars, Dollars, RatioDollarsBp16>( .compute_binary::<Dollars, Dollars, RatioDollarsBp16>(
max_from, max_from,
&unrealized.loss.usd.height, &unrealized.loss.raw.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,
market_cap, market_cap,
exit, exit,
)?; )?;

View File

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

View File

@@ -1,27 +1,25 @@
use brk_error::Result; use brk_error::Result;
use brk_traversable::Traversable; use brk_traversable::Traversable;
use brk_types::{Height, Indexes, Sats, SatsSigned, Version}; use brk_types::{Height, Indexes, Version};
use crate::{blocks, prices};
use vecdb::{AnyStoredVec, AnyVec, Exit, Rw, StorageMode, WritableVec}; use vecdb::{AnyStoredVec, AnyVec, Exit, Rw, StorageMode, WritableVec};
use crate::{distribution::state::{CohortState, RealizedOps}, prices};
use crate::internal::{ use crate::internal::{
AmountPerBlock, HalveCents, HalveDollars, HalveSats, HalveSatsToBitcoin, 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)] #[derive(Traversable)]
pub struct SupplyMetrics<M: StorageMode = Rw> { pub struct SupplyBase<M: StorageMode = Rw> {
pub total: AmountPerBlock<M>, pub total: AmountPerBlock<M>,
pub halved: LazyAmountPerBlock, pub halved: LazyAmountPerBlock,
pub delta: RollingDelta1m<Sats, SatsSigned, M>,
} }
impl SupplyMetrics { impl SupplyBase {
/// Import supply metrics from database.
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> { pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
let supply = cfg.import("supply", Version::ZERO)?; let supply = cfg.import("supply", Version::ZERO)?;
@@ -32,23 +30,18 @@ impl SupplyMetrics {
HalveDollars, HalveDollars,
>(&cfg.name("supply_halved"), &supply, cfg.version); >(&cfg.name("supply_halved"), &supply, cfg.version);
let delta = cfg.import("supply_delta", Version::ONE)?;
Ok(Self { Ok(Self {
total: supply, total: supply,
halved: supply_halved, halved: supply_halved,
delta,
}) })
} }
/// Get minimum length across height-indexed vectors.
pub(crate) fn min_len(&self) -> usize { pub(crate) fn min_len(&self) -> usize {
self.total.sats.height.len() self.total.sats.height.len()
} }
/// Push supply state values to height-indexed vectors. pub(crate) fn truncate_push(&mut self, height: Height, state: &CohortState<impl RealizedOps>) -> Result<()> {
pub(crate) fn truncate_push(&mut self, height: Height, supply: Sats) -> Result<()> { self.total.sats.height.truncate_push(height, state.supply.value)?;
self.total.sats.height.truncate_push(height, supply)?;
Ok(()) Ok(())
} }
@@ -59,7 +52,6 @@ impl SupplyMetrics {
] ]
} }
/// Eagerly compute USD height values from sats × price.
pub(crate) fn compute( pub(crate) fn compute(
&mut self, &mut self,
prices: &prices::Vecs, prices: &prices::Vecs,
@@ -69,13 +61,6 @@ impl SupplyMetrics {
self.total.compute(prices, max_from, exit) 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( pub(crate) fn compute_from_stateful(
&mut self, &mut self,
starting_indexes: &Indexes, starting_indexes: &Indexes,
@@ -92,19 +77,4 @@ impl SupplyMetrics {
)?; )?;
Ok(()) 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 derive_more::{Deref, DerefMut};
use vecdb::{AnyStoredVec, AnyVec, BytesVec, Exit, ReadableVec, Rw, StorageMode, WritableVec}; 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; use super::UnrealizedCore;
@@ -15,9 +15,13 @@ pub struct UnrealizedBase<M: StorageMode = Rw> {
#[traversable(flatten)] #[traversable(flatten)]
pub core: UnrealizedCore<M>, pub core: UnrealizedCore<M>,
#[traversable(wrap = "invested_capital/in_profit", rename = "raw")]
pub invested_capital_in_profit_raw: M::Stored<BytesVec<Height, CentsSats>>, 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>>, 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>>, 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>>, pub investor_cap_in_loss_raw: M::Stored<BytesVec<Height, CentsSquaredSats>>,
} }
@@ -161,11 +165,12 @@ impl UnrealizedBase {
pub(crate) fn compute_rest( pub(crate) fn compute_rest(
&mut self, &mut self,
blocks: &blocks::Vecs,
prices: &prices::Vecs, prices: &prices::Vecs,
starting_indexes: &Indexes, starting_indexes: &Indexes,
exit: &Exit, exit: &Exit,
) -> Result<()> { ) -> Result<()> {
self.core.compute_rest(prices, starting_indexes, exit)?; self.core.compute_rest(blocks, prices, starting_indexes, exit)?;
Ok(()) 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_traversable::Traversable;
use brk_types::{Cents, CentsSigned, Height, Indexes, Version}; use brk_types::{Cents, CentsSigned, Height, Indexes, Version};
use derive_more::{Deref, DerefMut}; use derive_more::{Deref, DerefMut};
use vecdb::{AnyStoredVec, AnyVec, Exit, ReadableCloneableVec, Rw, StorageMode, WritableVec}; use vecdb::{AnyStoredVec, Exit, ReadableCloneableVec, Rw, StorageMode};
use crate::{ use crate::{
blocks,
distribution::{ distribution::{
metrics::{ImportConfig, unrealized::UnrealizedMinimal}, metrics::ImportConfig,
state::UnrealizedState, state::UnrealizedState,
}, },
internal::{CentsSubtractToCentsSigned, FiatPerBlock, LazyPerBlock, NegCentsUnsignedToDollars}, internal::{CentsSubtractToCentsSigned, FiatPerBlock, LazyPerBlock, NegCentsUnsignedToDollars},
@@ -15,51 +16,42 @@ use crate::{
use brk_types::Dollars; use brk_types::Dollars;
use super::UnrealizedBasic;
#[derive(Deref, DerefMut, Traversable)] #[derive(Deref, DerefMut, Traversable)]
pub struct UnrealizedCore<M: StorageMode = Rw> { pub struct UnrealizedCore<M: StorageMode = Rw> {
#[deref] #[deref]
#[deref_mut] #[deref_mut]
#[traversable(flatten)] #[traversable(flatten)]
pub minimal: UnrealizedMinimal<M>, pub basic: UnrealizedBasic<M>,
pub profit: FiatPerBlock<Cents, M>, #[traversable(wrap = "loss", rename = "neg")]
pub loss: FiatPerBlock<Cents, M>,
pub neg_loss: LazyPerBlock<Dollars, Cents>, pub neg_loss: LazyPerBlock<Dollars, Cents>,
pub net_pnl: FiatPerBlock<CentsSigned, M>, pub net_pnl: FiatPerBlock<CentsSigned, M>,
} }
impl UnrealizedCore { impl UnrealizedCore {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> { pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
let v0 = Version::ZERO; let basic = UnrealizedBasic::forced_import(cfg)?;
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 neg_unrealized_loss = LazyPerBlock::from_computed::<NegCentsUnsignedToDollars>( let neg_unrealized_loss = LazyPerBlock::from_computed::<NegCentsUnsignedToDollars>(
&cfg.name("neg_unrealized_loss"), &cfg.name("neg_unrealized_loss"),
cfg.version, cfg.version,
unrealized_loss.cents.height.read_only_boxed_clone(), basic.loss.raw.cents.height.read_only_boxed_clone(),
&unrealized_loss.cents, &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 { Ok(Self {
minimal, basic,
profit: unrealized_profit,
loss: unrealized_loss,
neg_loss: neg_unrealized_loss, neg_loss: neg_unrealized_loss,
net_pnl: net_unrealized_pnl, net_pnl: net_unrealized_pnl,
}) })
} }
pub(crate) fn min_stateful_height_len(&self) -> usize { pub(crate) fn min_stateful_height_len(&self) -> usize {
self.minimal self.basic.min_stateful_height_len()
.min_stateful_height_len()
.min(self.profit.cents.height.len())
.min(self.loss.cents.height.len())
} }
pub(crate) fn truncate_push( pub(crate) fn truncate_push(
@@ -67,24 +59,12 @@ impl UnrealizedCore {
height: Height, height: Height,
height_state: &UnrealizedState, height_state: &UnrealizedState,
) -> Result<()> { ) -> Result<()> {
self.minimal.truncate_push(height, height_state)?; self.basic.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)?;
Ok(()) Ok(())
} }
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> { pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
let mut vecs = self.minimal.collect_vecs_mut(); self.basic.collect_vecs_mut()
vecs.push(&mut self.profit.cents.height);
vecs.push(&mut self.loss.cents.height);
vecs
} }
pub(crate) fn compute_from_stateful( pub(crate) fn compute_from_stateful(
@@ -93,33 +73,30 @@ impl UnrealizedCore {
others: &[&Self], others: &[&Self],
exit: &Exit, exit: &Exit,
) -> Result<()> { ) -> Result<()> {
let minimal_refs: Vec<&UnrealizedMinimal> = others.iter().map(|o| &o.minimal).collect(); let basic_refs: Vec<&UnrealizedBasic> = others.iter().map(|o| &o.basic).collect();
self.minimal self.basic
.compute_from_sources(starting_indexes, &minimal_refs, exit)?; .compute_from_sources(starting_indexes, &basic_refs, exit)?;
sum_others!(self, starting_indexes, others, exit; profit.cents.height);
sum_others!(self, starting_indexes, others, exit; loss.cents.height);
Ok(()) Ok(())
} }
/// Compute derived metrics from stored values. /// Compute derived metrics from stored values.
pub(crate) fn compute_rest( pub(crate) fn compute_rest(
&mut self, &mut self,
blocks: &blocks::Vecs,
prices: &prices::Vecs, prices: &prices::Vecs,
starting_indexes: &Indexes, starting_indexes: &Indexes,
exit: &Exit, exit: &Exit,
) -> Result<()> { ) -> Result<()> {
self.minimal self.basic
.compute_rest(prices, starting_indexes.height, exit)?; .compute_rest(blocks, prices, starting_indexes.height, exit)?;
self.net_pnl self.net_pnl
.cents .cents
.height .height
.compute_binary::<Cents, Cents, CentsSubtractToCentsSigned>( .compute_binary::<Cents, Cents, CentsSubtractToCentsSigned>(
starting_indexes.height, starting_indexes.height,
&self.profit.cents.height, &self.basic.profit.raw.cents.height,
&self.loss.cents.height, &self.basic.loss.raw.cents.height,
exit, exit,
)?; )?;

View File

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

View File

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

View File

@@ -1,10 +1,12 @@
mod base; mod base;
mod basic;
mod core; mod core;
mod full; mod full;
mod minimal; mod minimal;
pub use self::core::UnrealizedCore; pub use self::core::UnrealizedCore;
pub use base::UnrealizedBase; pub use base::UnrealizedBase;
pub use basic::UnrealizedBasic;
pub use full::UnrealizedFull; pub use full::UnrealizedFull;
pub use minimal::UnrealizedMinimal; pub use minimal::UnrealizedMinimal;
@@ -12,7 +14,7 @@ use brk_error::Result;
use brk_types::{Height, Indexes}; use brk_types::{Height, Indexes};
use vecdb::Exit; use vecdb::Exit;
use crate::{distribution::state::UnrealizedState, prices}; use crate::{blocks, distribution::state::UnrealizedState, prices};
pub trait UnrealizedLike: Send + Sync { pub trait UnrealizedLike: Send + Sync {
fn as_base(&self) -> &UnrealizedBase; 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 truncate_push(&mut self, height: Height, state: &UnrealizedState) -> Result<()>;
fn compute_rest( fn compute_rest(
&mut self, &mut self,
blocks: &blocks::Vecs,
prices: &prices::Vecs, prices: &prices::Vecs,
starting_indexes: &Indexes, starting_indexes: &Indexes,
exit: &Exit, exit: &Exit,
@@ -47,11 +50,12 @@ impl UnrealizedLike for UnrealizedBase {
} }
fn compute_rest( fn compute_rest(
&mut self, &mut self,
blocks: &blocks::Vecs,
prices: &prices::Vecs, prices: &prices::Vecs,
starting_indexes: &Indexes, starting_indexes: &Indexes,
exit: &Exit, exit: &Exit,
) -> Result<()> { ) -> Result<()> {
self.compute_rest(prices, starting_indexes, exit) self.compute_rest(blocks, prices, starting_indexes, exit)
} }
fn compute_net_sentiment_height( fn compute_net_sentiment_height(
&mut self, &mut self,
@@ -77,11 +81,12 @@ impl UnrealizedLike for UnrealizedFull {
} }
fn compute_rest( fn compute_rest(
&mut self, &mut self,
blocks: &blocks::Vecs,
prices: &prices::Vecs, prices: &prices::Vecs,
starting_indexes: &Indexes, starting_indexes: &Indexes,
exit: &Exit, exit: &Exit,
) -> Result<()> { ) -> Result<()> {
self.compute_rest_all(prices, starting_indexes, exit) self.compute_rest_all(blocks, prices, starting_indexes, exit)
} }
fn compute_net_sentiment_height( fn compute_net_sentiment_height(
&mut self, &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). /// Used by MinimalCohortMetrics cohorts (amount_range, type_, address — ~135 separate cohorts).
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct MinimalRealizedState { pub struct MinimalRealizedState {
cap_raw: u128, cap_raw: u128,
profit_raw: u128, profit_raw: u128,
loss_raw: u128, loss_raw: u128,
value_created_raw: u128,
value_destroyed_raw: u128,
} }
impl RealizedOps for MinimalRealizedState { impl RealizedOps for MinimalRealizedState {
@@ -72,6 +74,22 @@ impl RealizedOps for MinimalRealizedState {
Cents::new((self.loss_raw / Sats::ONE_BTC_U128) as u64) 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] #[inline]
fn set_cap_raw(&mut self, cap_raw: CentsSats) { fn set_cap_raw(&mut self, cap_raw: CentsSats) {
self.cap_raw = cap_raw.inner(); self.cap_raw = cap_raw.inner();
@@ -84,6 +102,8 @@ impl RealizedOps for MinimalRealizedState {
fn reset_single_iteration_values(&mut self) { fn reset_single_iteration_values(&mut self) {
self.profit_raw = 0; self.profit_raw = 0;
self.loss_raw = 0; self.loss_raw = 0;
self.value_created_raw = 0;
self.value_destroyed_raw = 0;
} }
#[inline] #[inline]
@@ -124,16 +144,16 @@ impl RealizedOps for MinimalRealizedState {
Ordering::Equal => {} Ordering::Equal => {}
} }
self.cap_raw -= prev_ps.as_u128(); 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). /// Used by CoreCohortMetrics cohorts (epoch, class, max_age, min_age — ~59 separate cohorts).
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct CoreRealizedState { pub struct CoreRealizedState {
minimal: MinimalRealizedState, minimal: MinimalRealizedState,
value_created_raw: u128,
value_destroyed_raw: u128,
sent_in_profit: Sats, sent_in_profit: Sats,
sent_in_loss: Sats, sent_in_loss: Sats,
} }
@@ -156,18 +176,12 @@ impl RealizedOps for CoreRealizedState {
#[inline] #[inline]
fn value_created(&self) -> Cents { fn value_created(&self) -> Cents {
if self.value_created_raw == 0 { self.minimal.value_created()
return Cents::ZERO;
}
Cents::new((self.value_created_raw / Sats::ONE_BTC_U128) as u64)
} }
#[inline] #[inline]
fn value_destroyed(&self) -> Cents { fn value_destroyed(&self) -> Cents {
if self.value_destroyed_raw == 0 { self.minimal.value_destroyed()
return Cents::ZERO;
}
Cents::new((self.value_destroyed_raw / Sats::ONE_BTC_U128) as u64)
} }
#[inline] #[inline]
@@ -191,8 +205,6 @@ impl RealizedOps for CoreRealizedState {
#[inline] #[inline]
fn reset_single_iteration_values(&mut self) { fn reset_single_iteration_values(&mut self) {
self.minimal.reset_single_iteration_values(); 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_profit = Sats::ZERO;
self.sent_in_loss = Sats::ZERO; self.sent_in_loss = Sats::ZERO;
} }
@@ -223,8 +235,6 @@ impl RealizedOps for CoreRealizedState {
) { ) {
self.minimal self.minimal
.send(sats, current_ps, prev_ps, ath_ps, prev_investor_cap); .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) { match current_ps.cmp(&prev_ps) {
Ordering::Greater | Ordering::Equal => { Ordering::Greater | Ordering::Equal => {
self.sent_in_profit += sats; 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 /// Windowed change + growth rate for addr_count, global + per-type
pub delta: DeltaVecs<M>, pub delta: DeltaVecs<M>,
pub fundedaddressindex: pub funded_address_index:
LazyVecFrom1<FundedAddressIndex, FundedAddressIndex, FundedAddressIndex, FundedAddressData>, LazyVecFrom1<FundedAddressIndex, FundedAddressIndex, FundedAddressIndex, FundedAddressData>,
pub emptyaddressindex: pub empty_address_index:
LazyVecFrom1<EmptyAddressIndex, EmptyAddressIndex, EmptyAddressIndex, EmptyAddressData>, LazyVecFrom1<EmptyAddressIndex, EmptyAddressIndex, EmptyAddressIndex, EmptyAddressData>,
/// In-memory block state for UTXO processing. Persisted via supply_state. /// In-memory block state for UTXO processing. Persisted via supply_state.
@@ -115,14 +115,14 @@ impl Vecs {
)?; )?;
// Identity mappings for traversable // Identity mappings for traversable
let fundedaddressindex = LazyVecFrom1::init( let funded_address_index = LazyVecFrom1::init(
"fundedaddressindex", "funded_address_index",
version, version,
fundedaddressindex_to_fundedaddressdata.read_only_boxed_clone(), fundedaddressindex_to_fundedaddressdata.read_only_boxed_clone(),
|index, _| index, |index, _| index,
); );
let emptyaddressindex = LazyVecFrom1::init( let empty_address_index = LazyVecFrom1::init(
"emptyaddressindex", "empty_address_index",
version, version,
emptyaddressindex_to_emptyaddressdata.read_only_boxed_clone(), emptyaddressindex_to_emptyaddressdata.read_only_boxed_clone(),
|index, _| index, |index, _| index,
@@ -164,8 +164,8 @@ impl Vecs {
funded: fundedaddressindex_to_fundedaddressdata, funded: fundedaddressindex_to_fundedaddressdata,
empty: emptyaddressindex_to_emptyaddressdata, empty: emptyaddressindex_to_emptyaddressdata,
}, },
fundedaddressindex, funded_address_index,
emptyaddressindex, empty_address_index,
chain_state: Vec::new(), chain_state: Vec::new(),
txindex_to_height: RangeMap::default(), txindex_to_height: RangeMap::default(),

View File

@@ -14,7 +14,7 @@ pub struct Vecs<M: StorageMode = Rw> {
impl Vecs { impl Vecs {
pub(crate) fn forced_import(db: &Database, version: Version) -> Result<Self> { pub(crate) fn forced_import(db: &Database, version: Version) -> Result<Self> {
Ok(Self { Ok(Self {
identity: EagerVec::forced_import(db, "difficultyepoch", version)?, identity: EagerVec::forced_import(db, "epoch", version)?,
first_height: EagerVec::forced_import(db, "first_height", version)?, first_height: EagerVec::forced_import(db, "first_height", version)?,
height_count: EagerVec::forced_import(db, "height_count", version)?, height_count: EagerVec::forced_import(db, "height_count", version)?,
}) })

View File

@@ -13,7 +13,7 @@ pub struct Vecs<M: StorageMode = Rw> {
impl Vecs { impl Vecs {
pub(crate) fn forced_import(db: &Database, version: Version) -> Result<Self> { pub(crate) fn forced_import(db: &Database, version: Version) -> Result<Self> {
Ok(Self { Ok(Self {
identity: EagerVec::forced_import(db, "halvingepoch", version)?, identity: EagerVec::forced_import(db, "halving", version)?,
first_height: EagerVec::forced_import(db, "first_height", version)?, first_height: EagerVec::forced_import(db, "first_height", version)?,
}) })
} }

View File

@@ -39,7 +39,7 @@ impl Vecs {
hour12: EagerVec::forced_import(db, "hour12", version)?, hour12: EagerVec::forced_import(db, "hour12", version)?,
day1: EagerVec::forced_import(db, "day1", version)?, day1: EagerVec::forced_import(db, "day1", version)?,
day3: EagerVec::forced_import(db, "day3", version)?, day3: EagerVec::forced_import(db, "day3", version)?,
epoch: EagerVec::forced_import(db, "difficulty", version)?, epoch: EagerVec::forced_import(db, "epoch", version)?,
halving: EagerVec::forced_import(db, "halving", version)?, halving: EagerVec::forced_import(db, "halving", version)?,
week1: EagerVec::forced_import(db, "week1", version)?, week1: EagerVec::forced_import(db, "week1", version)?,
month1: EagerVec::forced_import(db, "month1", version)?, month1: EagerVec::forced_import(db, "month1", version)?,

View File

@@ -106,19 +106,19 @@ where
&mut *p75_out, &mut *p75_out,
&mut *p90_out, &mut *p90_out,
] { ] {
v.checked_push_at(i, zero)?; v.truncate_push_at(i, zero)?;
} }
} else { } else {
average_out.checked_push_at(i, T::from(window.average()))?; average_out.truncate_push_at(i, T::from(window.average()))?;
min_out.checked_push_at(i, T::from(window.min()))?; min_out.truncate_push_at(i, T::from(window.min()))?;
max_out.checked_push_at(i, T::from(window.max()))?; max_out.truncate_push_at(i, T::from(window.max()))?;
let [p10, p25, p50, p75, p90] = let [p10, p25, p50, p75, p90] =
window.percentiles(&[0.10, 0.25, 0.50, 0.75, 0.90]); window.percentiles(&[0.10, 0.25, 0.50, 0.75, 0.90]);
p10_out.checked_push_at(i, T::from(p10))?; p10_out.truncate_push_at(i, T::from(p10))?;
p25_out.checked_push_at(i, T::from(p25))?; p25_out.truncate_push_at(i, T::from(p25))?;
median_out.checked_push_at(i, T::from(p50))?; median_out.truncate_push_at(i, T::from(p50))?;
p75_out.checked_push_at(i, T::from(p75))?; p75_out.truncate_push_at(i, T::from(p75))?;
p90_out.checked_push_at(i, T::from(p90))?; p90_out.truncate_push_at(i, T::from(p90))?;
} }
if average_out.batch_limit_reached() { if average_out.batch_limit_reached() {

View File

@@ -1,7 +1,13 @@
mod distribution_stats; mod distribution_stats;
mod per_resolution; mod per_resolution;
mod window_24h;
mod windows; mod windows;
mod windows_except_1m;
mod windows_from_1w;
pub use distribution_stats::*; pub use distribution_stats::*;
pub use per_resolution::*; pub use per_resolution::*;
pub use window_24h::*;
pub use windows::*; pub use windows::*;
pub use windows_except_1m::*;
pub use windows_from_1w::*;

View File

@@ -0,0 +1,7 @@
use brk_traversable::Traversable;
/// Generic single-24h-window container.
#[derive(Traversable)]
pub struct RollingWindow24h<Inner> {
pub _24h: Inner,
}

View File

@@ -0,0 +1,30 @@
use brk_traversable::Traversable;
#[derive(Clone, Traversable)]
pub struct WindowsExcept1m<A> {
pub _24h: A,
pub _1w: A,
pub _1y: A,
}
impl<A> WindowsExcept1m<A> {
pub const SUFFIXES: [&'static str; 3] = ["24h", "1w", "1y"];
pub fn try_from_fn<E>(
mut f: impl FnMut(&str) -> std::result::Result<A, E>,
) -> std::result::Result<Self, E> {
Ok(Self {
_24h: f(Self::SUFFIXES[0])?,
_1w: f(Self::SUFFIXES[1])?,
_1y: f(Self::SUFFIXES[2])?,
})
}
pub fn as_array(&self) -> [&A; 3] {
[&self._24h, &self._1w, &self._1y]
}
pub fn as_mut_array(&mut self) -> [&mut A; 3] {
[&mut self._24h, &mut self._1w, &mut self._1y]
}
}

View File

@@ -0,0 +1,30 @@
use brk_traversable::Traversable;
#[derive(Clone, Traversable)]
pub struct WindowsFrom1w<A> {
pub _1w: A,
pub _1m: A,
pub _1y: A,
}
impl<A> WindowsFrom1w<A> {
pub const SUFFIXES: [&'static str; 3] = ["1w", "1m", "1y"];
pub fn try_from_fn<E>(
mut f: impl FnMut(&str) -> std::result::Result<A, E>,
) -> std::result::Result<Self, E> {
Ok(Self {
_1w: f(Self::SUFFIXES[0])?,
_1m: f(Self::SUFFIXES[1])?,
_1y: f(Self::SUFFIXES[2])?,
})
}
pub fn as_array(&self) -> [&A; 3] {
[&self._1w, &self._1m, &self._1y]
}
pub fn as_mut_array(&mut self) -> [&mut A; 3] {
[&mut self._1w, &mut self._1m, &mut self._1y]
}
}

View File

@@ -8,6 +8,7 @@ mod rolling;
mod rolling_full; mod rolling_full;
mod rolling_sum; mod rolling_sum;
mod windows; mod windows;
mod with_sum_24h;
pub use base::*; pub use base::*;
pub use cumulative::*; pub use cumulative::*;
@@ -19,3 +20,4 @@ pub use rolling::*;
pub use rolling_full::*; pub use rolling_full::*;
pub use rolling_sum::*; pub use rolling_sum::*;
pub use windows::*; pub use windows::*;
pub use with_sum_24h::*;

View File

@@ -6,9 +6,43 @@ use vecdb::{Database, Exit, ReadableVec, Rw, StorageMode};
use crate::{ use crate::{
indexes, indexes,
internal::{AmountPerBlock, WindowStarts, Windows}, internal::{AmountPerBlock, RollingWindow24h, WindowStarts, Windows},
}; };
/// Single 24h rolling sum as amount (sats + btc + cents + usd).
///
/// Tree: `_24h.sats.height`, `_24h.btc.height`, etc.
#[derive(Deref, DerefMut, Traversable)]
#[traversable(transparent)]
pub struct RollingWindow24hAmountPerBlock<M: StorageMode = Rw>(
pub RollingWindow24h<AmountPerBlock<M>>,
);
impl RollingWindow24hAmountPerBlock {
pub(crate) fn forced_import(
db: &Database,
name: &str,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
Ok(Self(RollingWindow24h {
_24h: AmountPerBlock::forced_import(db, &format!("{name}_24h"), version, indexes)?,
}))
}
pub(crate) fn compute_rolling_sum(
&mut self,
max_from: Height,
height_24h_ago: &impl ReadableVec<Height, Height>,
sats_source: &impl ReadableVec<Height, Sats>,
cents_source: &impl ReadableVec<Height, Cents>,
exit: &Exit,
) -> Result<()> {
self._24h
.compute_rolling_sum(max_from, height_24h_ago, sats_source, cents_source, exit)
}
}
/// Rolling sum only, window-first then unit. /// Rolling sum only, window-first then unit.
/// ///
/// Tree: `_24h.sats.height`, `_24h.btc.height`, etc. /// Tree: `_24h.sats.height`, `_24h.btc.height`, etc.

View File

@@ -0,0 +1,14 @@
//! AmountPerBlockWithSum24h - AmountPerBlock raw + RollingWindow24hAmountPerBlock sum.
use brk_traversable::Traversable;
use vecdb::{Rw, StorageMode};
use crate::internal::{AmountPerBlock, RollingWindow24hAmountPerBlock};
/// Amount per-block value (sats + cents) with 24h rolling sum (also amount).
#[derive(Traversable)]
pub struct AmountPerBlockWithSum24h<M: StorageMode = Rw> {
#[traversable(flatten)]
pub raw: AmountPerBlock<M>,
pub sum: RollingWindow24hAmountPerBlock<M>,
}

View File

@@ -19,7 +19,7 @@ use crate::{
indexes, indexes,
internal::{ internal::{
ComputedPerBlock, NumericValue, PercentPerBlock, PercentRollingWindows, ComputedPerBlock, NumericValue, PercentPerBlock, PercentRollingWindows,
RollingWindows, WindowStarts, RollingWindows, WindowStarts, WindowsExcept1m,
}, },
}; };
@@ -168,7 +168,9 @@ where
S: NumericValue + JsonSchema, S: NumericValue + JsonSchema,
C: NumericValue + JsonSchema, C: NumericValue + JsonSchema,
{ {
#[traversable(wrap = "change", rename = "1m")]
pub change_1m: ComputedPerBlock<C, M>, pub change_1m: ComputedPerBlock<C, M>,
#[traversable(wrap = "rate", rename = "1m")]
pub rate_1m: PercentPerBlock<BasisPointsSigned32, M>, pub rate_1m: PercentPerBlock<BasisPointsSigned32, M>,
_phantom: std::marker::PhantomData<S>, _phantom: std::marker::PhantomData<S>,
} }
@@ -227,14 +229,8 @@ where
S: NumericValue + JsonSchema, S: NumericValue + JsonSchema,
C: NumericValue + JsonSchema, C: NumericValue + JsonSchema,
{ {
#[traversable(rename = "24h")] pub change: WindowsExcept1m<ComputedPerBlock<C, M>>,
pub change_24h: ComputedPerBlock<C, M>, pub rate: WindowsExcept1m<PercentPerBlock<BasisPointsSigned32, M>>,
pub change_1w: ComputedPerBlock<C, M>,
pub change_1y: ComputedPerBlock<C, M>,
#[traversable(rename = "24h")]
pub rate_24h: PercentPerBlock<BasisPointsSigned32, M>,
pub rate_1w: PercentPerBlock<BasisPointsSigned32, M>,
pub rate_1y: PercentPerBlock<BasisPointsSigned32, M>,
_phantom: std::marker::PhantomData<S>, _phantom: std::marker::PhantomData<S>,
} }
@@ -250,42 +246,22 @@ where
indexes: &indexes::Vecs, indexes: &indexes::Vecs,
) -> Result<Self> { ) -> Result<Self> {
Ok(Self { Ok(Self {
change_24h: ComputedPerBlock::forced_import( change: WindowsExcept1m::try_from_fn(|suffix| {
ComputedPerBlock::forced_import(
db, db,
&format!("{name}_change_24h"), &format!("{name}_change_{suffix}"),
version, version,
indexes, indexes,
)?, )
change_1w: ComputedPerBlock::forced_import( })?,
rate: WindowsExcept1m::try_from_fn(|suffix| {
PercentPerBlock::forced_import(
db, db,
&format!("{name}_change_1w"), &format!("{name}_rate_{suffix}"),
version, version,
indexes, indexes,
)?, )
change_1y: ComputedPerBlock::forced_import( })?,
db,
&format!("{name}_change_1y"),
version,
indexes,
)?,
rate_24h: PercentPerBlock::forced_import(
db,
&format!("{name}_rate_24h"),
version,
indexes,
)?,
rate_1w: PercentPerBlock::forced_import(
db,
&format!("{name}_rate_1w"),
version,
indexes,
)?,
rate_1y: PercentPerBlock::forced_import(
db,
&format!("{name}_rate_1y"),
version,
indexes,
)?,
_phantom: std::marker::PhantomData, _phantom: std::marker::PhantomData,
}) })
} }
@@ -297,8 +273,8 @@ where
source: &impl ReadableVec<Height, S>, source: &impl ReadableVec<Height, S>,
exit: &Exit, exit: &Exit,
) -> Result<()> { ) -> Result<()> {
let changes = [&mut self.change_24h, &mut self.change_1w, &mut self.change_1y]; let changes = self.change.as_mut_array();
let rates = [&mut self.rate_24h, &mut self.rate_1w, &mut self.rate_1y]; let rates = self.rate.as_mut_array();
let starts = [windows._24h, windows._1w, windows._1y]; let starts = [windows._24h, windows._1w, windows._1y];
for ((change_w, rate_w), starts) in changes.into_iter().zip(rates).zip(starts) { for ((change_w, rate_w), starts) in changes.into_iter().zip(rates).zip(starts) {

View File

@@ -11,7 +11,7 @@ use crate::{
indexes, indexes,
internal::{ internal::{
CentsType, FiatPerBlock, NumericValue, PercentPerBlock, PercentRollingWindows, CentsType, FiatPerBlock, NumericValue, PercentPerBlock, PercentRollingWindows,
WindowStarts, Windows, WindowStarts, WindowsExcept1m,
}, },
}; };
@@ -24,7 +24,9 @@ where
S: NumericValue + JsonSchema, S: NumericValue + JsonSchema,
C: CentsType, C: CentsType,
{ {
#[traversable(wrap = "change", rename = "1m")]
pub change_1m: FiatPerBlock<C, M>, pub change_1m: FiatPerBlock<C, M>,
#[traversable(wrap = "rate", rename = "1m")]
pub rate_1m: PercentPerBlock<BasisPointsSigned32, M>, pub rate_1m: PercentPerBlock<BasisPointsSigned32, M>,
_phantom: std::marker::PhantomData<S>, _phantom: std::marker::PhantomData<S>,
} }
@@ -82,14 +84,8 @@ where
S: NumericValue + JsonSchema, S: NumericValue + JsonSchema,
C: CentsType, C: CentsType,
{ {
#[traversable(rename = "24h")] pub change: WindowsExcept1m<FiatPerBlock<C, M>>,
pub change_24h: FiatPerBlock<C, M>, pub rate: WindowsExcept1m<PercentPerBlock<BasisPointsSigned32, M>>,
pub change_1w: FiatPerBlock<C, M>,
pub change_1y: FiatPerBlock<C, M>,
#[traversable(rename = "24h")]
pub rate_24h: PercentPerBlock<BasisPointsSigned32, M>,
pub rate_1w: PercentPerBlock<BasisPointsSigned32, M>,
pub rate_1y: PercentPerBlock<BasisPointsSigned32, M>,
_phantom: std::marker::PhantomData<S>, _phantom: std::marker::PhantomData<S>,
} }
@@ -105,42 +101,22 @@ where
indexes: &indexes::Vecs, indexes: &indexes::Vecs,
) -> Result<Self> { ) -> Result<Self> {
Ok(Self { Ok(Self {
change_24h: FiatPerBlock::forced_import( change: WindowsExcept1m::try_from_fn(|suffix| {
FiatPerBlock::forced_import(
db, db,
&format!("{name}_change_24h"), &format!("{name}_change_{suffix}"),
version, version,
indexes, indexes,
)?, )
change_1w: FiatPerBlock::forced_import( })?,
rate: WindowsExcept1m::try_from_fn(|suffix| {
PercentPerBlock::forced_import(
db, db,
&format!("{name}_change_1w"), &format!("{name}_rate_{suffix}"),
version, version,
indexes, indexes,
)?, )
change_1y: FiatPerBlock::forced_import( })?,
db,
&format!("{name}_change_1y"),
version,
indexes,
)?,
rate_24h: PercentPerBlock::forced_import(
db,
&format!("{name}_rate_24h"),
version,
indexes,
)?,
rate_1w: PercentPerBlock::forced_import(
db,
&format!("{name}_rate_1w"),
version,
indexes,
)?,
rate_1y: PercentPerBlock::forced_import(
db,
&format!("{name}_rate_1y"),
version,
indexes,
)?,
_phantom: std::marker::PhantomData, _phantom: std::marker::PhantomData,
}) })
} }
@@ -152,12 +128,8 @@ where
source: &impl ReadableVec<Height, S>, source: &impl ReadableVec<Height, S>,
exit: &Exit, exit: &Exit,
) -> Result<()> { ) -> Result<()> {
let changes: [&mut FiatPerBlock<C>; 3] = [ let changes = self.change.as_mut_array();
&mut self.change_24h, let rates = self.rate.as_mut_array();
&mut self.change_1w,
&mut self.change_1y,
];
let rates = [&mut self.rate_24h, &mut self.rate_1w, &mut self.rate_1y];
let starts = [windows._24h, windows._1w, windows._1y]; let starts = [windows._24h, windows._1w, windows._1y];
for ((change_w, rate_w), starts) in changes.into_iter().zip(rates).zip(starts) { for ((change_w, rate_w), starts) in changes.into_iter().zip(rates).zip(starts) {
@@ -181,10 +153,7 @@ where
S: NumericValue + JsonSchema, S: NumericValue + JsonSchema,
C: CentsType, C: CentsType,
{ {
pub change_24h: FiatPerBlock<C, M>, pub change: Windows<FiatPerBlock<C, M>>,
pub change_1w: FiatPerBlock<C, M>,
pub change_1m: FiatPerBlock<C, M>,
pub change_1y: FiatPerBlock<C, M>,
pub rate: PercentRollingWindows<BasisPointsSigned32, M>, pub rate: PercentRollingWindows<BasisPointsSigned32, M>,
_phantom: std::marker::PhantomData<S>, _phantom: std::marker::PhantomData<S>,
} }
@@ -201,30 +170,14 @@ where
indexes: &indexes::Vecs, indexes: &indexes::Vecs,
) -> Result<Self> { ) -> Result<Self> {
Ok(Self { Ok(Self {
change_24h: FiatPerBlock::forced_import( change: Windows::try_from_fn(|suffix| {
FiatPerBlock::forced_import(
db, db,
&format!("{name}_change_24h"), &format!("{name}_change_{suffix}"),
version, version,
indexes, indexes,
)?, )
change_1w: FiatPerBlock::forced_import( })?,
db,
&format!("{name}_change_1w"),
version,
indexes,
)?,
change_1m: FiatPerBlock::forced_import(
db,
&format!("{name}_change_1m"),
version,
indexes,
)?,
change_1y: FiatPerBlock::forced_import(
db,
&format!("{name}_change_1y"),
version,
indexes,
)?,
rate: PercentRollingWindows::forced_import( rate: PercentRollingWindows::forced_import(
db, db,
&format!("{name}_rate"), &format!("{name}_rate"),
@@ -242,12 +195,7 @@ where
source: &impl ReadableVec<Height, S>, source: &impl ReadableVec<Height, S>,
exit: &Exit, exit: &Exit,
) -> Result<()> { ) -> Result<()> {
let changes: [&mut FiatPerBlock<C>; 4] = [ let changes = self.change.as_mut_array();
&mut self.change_24h,
&mut self.change_1w,
&mut self.change_1m,
&mut self.change_1y,
];
let rates = self.rate.0.as_mut_array(); let rates = self.rate.0.as_mut_array();
let starts = windows.as_array(); let starts = windows.as_array();

View File

@@ -10,6 +10,7 @@ mod fiat_delta;
mod full; mod full;
mod rolling_average; mod rolling_average;
mod sum; mod sum;
mod with_sum_24h;
pub use aggregated::*; pub use aggregated::*;
pub use base::*; pub use base::*;
@@ -23,3 +24,4 @@ pub use fiat_delta::*;
pub use full::*; pub use full::*;
pub use rolling_average::*; pub use rolling_average::*;
pub use sum::*; pub use sum::*;
pub use with_sum_24h::*;

View File

@@ -90,12 +90,7 @@ where
break; break;
} }
let target = S1I::max_from(I::from(i), source_len); let target = S1I::max_from(I::from(i), source_len);
if cursor.position() <= target { if let Some(v) = cursor.get(target) {
cursor.advance(target - cursor.position());
if let Some(v) = cursor.next() {
f(v);
}
} else if let Some(v) = source.collect_one_at(target) {
f(v); f(v);
} }
} }

View File

@@ -0,0 +1,19 @@
//! PerBlockWithSum24h - ComputedPerBlock + RollingWindow24hPerBlock rolling sum.
//!
//! Generic building block for metrics that store a per-block value
//! plus its 24h rolling sum. Used across activity and realized metrics.
use brk_traversable::Traversable;
use schemars::JsonSchema;
use vecdb::{Rw, StorageMode};
use crate::internal::{ComputedPerBlock, ComputedVecValue, RollingWindow24hPerBlock};
#[derive(Traversable)]
pub struct PerBlockWithSum24h<T, M: StorageMode = Rw>
where
T: ComputedVecValue + PartialOrd + JsonSchema,
{
pub raw: ComputedPerBlock<T, M>,
pub sum: RollingWindow24hPerBlock<T, M>,
}

View File

@@ -1,5 +1,7 @@
mod base; mod base;
mod lazy; mod lazy;
mod with_sum_24h;
pub use base::*; pub use base::*;
pub use lazy::*; pub use lazy::*;
pub use with_sum_24h::*;

View File

@@ -0,0 +1,59 @@
//! FiatPerBlockWithSum24h - FiatPerBlock raw + RollingWindow24hFiatPerBlock sum.
use std::ops::SubAssign;
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Height, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{Database, Exit, ReadableVec, Rw, StorageMode};
use crate::{
indexes,
internal::{CentsType, FiatPerBlock, RollingWindow24h},
};
/// Single 24h rolling window backed by FiatPerBlock (cents + lazy usd).
#[derive(Deref, DerefMut, Traversable)]
#[traversable(transparent)]
pub struct RollingWindow24hFiatPerBlock<C: CentsType, M: StorageMode = Rw>(
pub RollingWindow24h<FiatPerBlock<C, M>>,
);
impl<C: CentsType> RollingWindow24hFiatPerBlock<C> {
pub(crate) fn forced_import(
db: &Database,
name: &str,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
Ok(Self(RollingWindow24h {
_24h: FiatPerBlock::forced_import(db, &format!("{name}_24h"), version, indexes)?,
}))
}
pub(crate) fn compute_rolling_sum(
&mut self,
max_from: Height,
height_24h_ago: &impl ReadableVec<Height, Height>,
source: &impl ReadableVec<Height, C>,
exit: &Exit,
) -> Result<()>
where
C: Default + SubAssign,
{
self._24h
.cents
.height
.compute_rolling_sum(max_from, height_24h_ago, source, exit)?;
Ok(())
}
}
/// Fiat per-block value (cents + usd) with 24h rolling sum (also fiat).
#[derive(Traversable)]
pub struct FiatPerBlockWithSum24h<C: CentsType, M: StorageMode = Rw> {
#[traversable(flatten)]
pub raw: FiatPerBlock<C, M>,
pub sum: RollingWindow24hFiatPerBlock<C, M>,
}

View File

@@ -5,18 +5,18 @@ use vecdb::{Database, Exit, ReadableCloneableVec, ReadableVec, Rw, StorageMode};
use crate::{ use crate::{
indexes, indexes,
internal::{Bp32ToFloat, ComputedPerBlock, LazyPerBlock}, internal::{BpsType, ComputedPerBlock, LazyPerBlock},
}; };
#[derive(Traversable)] #[derive(Traversable)]
pub struct RatioPerBlock<M: StorageMode = Rw> { pub struct RatioPerBlock<B: BpsType = BasisPoints32, M: StorageMode = Rw> {
pub bps: ComputedPerBlock<BasisPoints32, M>, pub bps: ComputedPerBlock<B, M>,
pub ratio: LazyPerBlock<StoredF32, BasisPoints32>, pub ratio: LazyPerBlock<StoredF32, B>,
} }
const VERSION: Version = Version::TWO; const VERSION: Version = Version::TWO;
impl RatioPerBlock { impl<B: BpsType> RatioPerBlock<B> {
pub(crate) fn forced_import( pub(crate) fn forced_import(
db: &Database, db: &Database,
name: &str, name: &str,
@@ -36,7 +36,7 @@ impl RatioPerBlock {
let bps = ComputedPerBlock::forced_import(db, &format!("{name}_bps"), v, indexes)?; let bps = ComputedPerBlock::forced_import(db, &format!("{name}_bps"), v, indexes)?;
let ratio = LazyPerBlock::from_computed::<Bp32ToFloat>( let ratio = LazyPerBlock::from_computed::<B::ToRatio>(
name, name,
v, v,
bps.height.read_only_boxed_clone(), bps.height.read_only_boxed_clone(),
@@ -45,7 +45,9 @@ impl RatioPerBlock {
Ok(Self { bps, ratio }) Ok(Self { bps, ratio })
} }
}
impl RatioPerBlock<BasisPoints32> {
pub(crate) fn compute_ratio( pub(crate) fn compute_ratio(
&mut self, &mut self,
starting_indexes: &Indexes, starting_indexes: &Indexes,

View File

@@ -1,6 +1,6 @@
use brk_error::Result; use brk_error::Result;
use brk_traversable::Traversable; use brk_traversable::Traversable;
use brk_types::{Cents, Height, Indexes, Version}; use brk_types::{BasisPoints32, Cents, Height, Indexes, Version};
use derive_more::{Deref, DerefMut}; use derive_more::{Deref, DerefMut};
use vecdb::{Database, Exit, ReadableVec, Rw, StorageMode}; use vecdb::{Database, Exit, ReadableVec, Rw, StorageMode};
@@ -13,7 +13,7 @@ pub struct RatioPerBlockExtended<M: StorageMode = Rw> {
#[deref] #[deref]
#[deref_mut] #[deref_mut]
#[traversable(flatten)] #[traversable(flatten)]
pub base: RatioPerBlock<M>, pub base: RatioPerBlock<BasisPoints32, M>,
#[traversable(flatten)] #[traversable(flatten)]
pub percentiles: RatioPerBlockPercentiles<M>, pub percentiles: RatioPerBlockPercentiles<M>,
} }

View File

@@ -13,22 +13,23 @@ use crate::{
use super::{super::ComputedPerBlock, RatioPerBlock}; use super::{super::ComputedPerBlock, RatioPerBlock};
#[derive(Traversable)]
pub struct RatioBand<M: StorageMode = Rw> {
#[traversable(flatten)]
pub ratio: RatioPerBlock<BasisPoints32, M>,
pub price: Price<ComputedPerBlock<Cents, M>>,
}
#[derive(Traversable)] #[derive(Traversable)]
pub struct RatioPerBlockPercentiles<M: StorageMode = Rw> { pub struct RatioPerBlockPercentiles<M: StorageMode = Rw> {
pub ratio_sma_1w: RatioPerBlock<M>, pub sma_1w: RatioPerBlock<BasisPoints32, M>,
pub ratio_sma_1m: RatioPerBlock<M>, pub sma_1m: RatioPerBlock<BasisPoints32, M>,
pub ratio_pct99: RatioPerBlock<M>, pub pct99: RatioBand<M>,
pub ratio_pct98: RatioPerBlock<M>, pub pct98: RatioBand<M>,
pub ratio_pct95: RatioPerBlock<M>, pub pct95: RatioBand<M>,
pub ratio_pct5: RatioPerBlock<M>, pub pct5: RatioBand<M>,
pub ratio_pct2: RatioPerBlock<M>, pub pct2: RatioBand<M>,
pub ratio_pct1: RatioPerBlock<M>, pub pct1: RatioBand<M>,
pub ratio_pct99_price: Price<ComputedPerBlock<Cents, M>>,
pub ratio_pct98_price: Price<ComputedPerBlock<Cents, M>>,
pub ratio_pct95_price: Price<ComputedPerBlock<Cents, M>>,
pub ratio_pct5_price: Price<ComputedPerBlock<Cents, M>>,
pub ratio_pct2_price: Price<ComputedPerBlock<Cents, M>>,
pub ratio_pct1_price: Price<ComputedPerBlock<Cents, M>>,
#[traversable(skip)] #[traversable(skip)]
expanding_pct: ExpandingPercentiles, expanding_pct: ExpandingPercentiles,
@@ -62,21 +63,24 @@ impl RatioPerBlockPercentiles {
}; };
} }
macro_rules! import_band {
($suffix:expr) => {
RatioBand {
ratio: import_ratio!($suffix),
price: import_price!($suffix),
}
};
}
Ok(Self { Ok(Self {
ratio_sma_1w: import_ratio!("ratio_sma_1w"), sma_1w: import_ratio!("ratio_sma_1w"),
ratio_sma_1m: import_ratio!("ratio_sma_1m"), sma_1m: import_ratio!("ratio_sma_1m"),
ratio_pct99: import_ratio!("ratio_pct99"), pct99: import_band!("ratio_pct99"),
ratio_pct98: import_ratio!("ratio_pct98"), pct98: import_band!("ratio_pct98"),
ratio_pct95: import_ratio!("ratio_pct95"), pct95: import_band!("ratio_pct95"),
ratio_pct5: import_ratio!("ratio_pct5"), pct5: import_band!("ratio_pct5"),
ratio_pct2: import_ratio!("ratio_pct2"), pct2: import_band!("ratio_pct2"),
ratio_pct1: import_ratio!("ratio_pct1"), pct1: import_band!("ratio_pct1"),
ratio_pct99_price: import_price!("ratio_pct99"),
ratio_pct98_price: import_price!("ratio_pct98"),
ratio_pct95_price: import_price!("ratio_pct95"),
ratio_pct5_price: import_price!("ratio_pct5"),
ratio_pct2_price: import_price!("ratio_pct2"),
ratio_pct1_price: import_price!("ratio_pct1"),
expanding_pct: ExpandingPercentiles::default(), expanding_pct: ExpandingPercentiles::default(),
}) })
} }
@@ -89,14 +93,14 @@ impl RatioPerBlockPercentiles {
ratio_source: &impl ReadableVec<Height, StoredF32>, ratio_source: &impl ReadableVec<Height, StoredF32>,
metric_price: &impl ReadableVec<Height, Cents>, metric_price: &impl ReadableVec<Height, Cents>,
) -> Result<()> { ) -> Result<()> {
self.ratio_sma_1w.bps.height.compute_rolling_average( self.sma_1w.bps.height.compute_rolling_average(
starting_indexes.height, starting_indexes.height,
&blocks.lookback.height_1w_ago, &blocks.lookback.height_1w_ago,
ratio_source, ratio_source,
exit, exit,
)?; )?;
self.ratio_sma_1m.bps.height.compute_rolling_average( self.sma_1m.bps.height.compute_rolling_average(
starting_indexes.height, starting_indexes.height,
&blocks.lookback.height_1m_ago, &blocks.lookback.height_1m_ago,
ratio_source, ratio_source,
@@ -131,12 +135,12 @@ impl RatioPerBlockPercentiles {
let new_ratios = ratio_source.collect_range_at(start, ratio_len); let new_ratios = ratio_source.collect_range_at(start, ratio_len);
let mut pct_vecs: [&mut EagerVec<PcoVec<Height, BasisPoints32>>; 6] = [ let mut pct_vecs: [&mut EagerVec<PcoVec<Height, BasisPoints32>>; 6] = [
&mut self.ratio_pct1.bps.height, &mut self.pct1.ratio.bps.height,
&mut self.ratio_pct2.bps.height, &mut self.pct2.ratio.bps.height,
&mut self.ratio_pct5.bps.height, &mut self.pct5.ratio.bps.height,
&mut self.ratio_pct95.bps.height, &mut self.pct95.ratio.bps.height,
&mut self.ratio_pct98.bps.height, &mut self.pct98.ratio.bps.height,
&mut self.ratio_pct99.bps.height, &mut self.pct99.ratio.bps.height,
]; ];
const PCTS: [f64; 6] = [0.01, 0.02, 0.05, 0.95, 0.98, 0.99]; const PCTS: [f64; 6] = [0.01, 0.02, 0.05, 0.95, 0.98, 0.99];
let mut out = [0u32; 6]; let mut out = [0u32; 6];
@@ -158,24 +162,25 @@ impl RatioPerBlockPercentiles {
// Cents bands // Cents bands
macro_rules! compute_band { macro_rules! compute_band {
($usd_field:ident, $band_source:expr) => { ($band:ident) => {
self.$usd_field self.$band
.price
.cents .cents
.compute_binary::<Cents, BasisPoints32, PriceTimesRatioBp32Cents>( .compute_binary::<Cents, BasisPoints32, PriceTimesRatioBp32Cents>(
starting_indexes.height, starting_indexes.height,
metric_price, metric_price,
$band_source, &self.$band.ratio.bps.height,
exit, exit,
)?; )?;
}; };
} }
compute_band!(ratio_pct99_price, &self.ratio_pct99.bps.height); compute_band!(pct99);
compute_band!(ratio_pct98_price, &self.ratio_pct98.bps.height); compute_band!(pct98);
compute_band!(ratio_pct95_price, &self.ratio_pct95.bps.height); compute_band!(pct95);
compute_band!(ratio_pct5_price, &self.ratio_pct5.bps.height); compute_band!(pct5);
compute_band!(ratio_pct2_price, &self.ratio_pct2.bps.height); compute_band!(pct2);
compute_band!(ratio_pct1_price, &self.ratio_pct1.bps.height); compute_band!(pct1);
Ok(()) Ok(())
} }
@@ -184,12 +189,12 @@ impl RatioPerBlockPercentiles {
&mut self, &mut self,
) -> impl Iterator<Item = &mut EagerVec<PcoVec<Height, BasisPoints32>>> { ) -> impl Iterator<Item = &mut EagerVec<PcoVec<Height, BasisPoints32>>> {
[ [
&mut self.ratio_pct1.bps.height, &mut self.pct1.ratio.bps.height,
&mut self.ratio_pct2.bps.height, &mut self.pct2.ratio.bps.height,
&mut self.ratio_pct5.bps.height, &mut self.pct5.ratio.bps.height,
&mut self.ratio_pct95.bps.height, &mut self.pct95.ratio.bps.height,
&mut self.ratio_pct98.bps.height, &mut self.pct98.ratio.bps.height,
&mut self.ratio_pct99.bps.height, &mut self.pct99.ratio.bps.height,
] ]
.into_iter() .into_iter()
} }

View File

@@ -1,6 +1,6 @@
use brk_error::Result; use brk_error::Result;
use brk_traversable::Traversable; use brk_traversable::Traversable;
use brk_types::{Cents, Height, Indexes, Version}; use brk_types::{BasisPoints32, Cents, Height, Indexes, Version};
use derive_more::{Deref, DerefMut}; use derive_more::{Deref, DerefMut};
use vecdb::{Database, EagerVec, Exit, PcoVec, Rw, StorageMode}; use vecdb::{Database, EagerVec, Exit, PcoVec, Rw, StorageMode};
@@ -14,7 +14,7 @@ pub struct PriceWithRatioPerBlock<M: StorageMode = Rw> {
#[deref] #[deref]
#[deref_mut] #[deref_mut]
#[traversable(flatten)] #[traversable(flatten)]
pub inner: RatioPerBlock<M>, pub inner: RatioPerBlock<BasisPoints32, M>,
pub price: Price<ComputedPerBlock<Cents, M>>, pub price: Price<ComputedPerBlock<Cents, M>>,
} }

View File

@@ -7,10 +7,10 @@ use crate::{blocks, indexes, internal::StdDevPerBlockExtended};
#[derive(Traversable)] #[derive(Traversable)]
pub struct RatioPerBlockStdDevBands<M: StorageMode = Rw> { pub struct RatioPerBlockStdDevBands<M: StorageMode = Rw> {
pub ratio_sd: StdDevPerBlockExtended<M>, pub all: StdDevPerBlockExtended<M>,
pub ratio_sd_4y: StdDevPerBlockExtended<M>, pub _4y: StdDevPerBlockExtended<M>,
pub ratio_sd_2y: StdDevPerBlockExtended<M>, pub _2y: StdDevPerBlockExtended<M>,
pub ratio_sd_1y: StdDevPerBlockExtended<M>, pub _1y: StdDevPerBlockExtended<M>,
} }
const VERSION: Version = Version::new(4); const VERSION: Version = Version::new(4);
@@ -38,10 +38,10 @@ impl RatioPerBlockStdDevBands {
} }
Ok(Self { Ok(Self {
ratio_sd: import_sd!("ratio", "", usize::MAX), all: import_sd!("ratio", "", usize::MAX),
ratio_sd_1y: import_sd!("ratio", "1y", 365), _1y: import_sd!("ratio", "1y", 365),
ratio_sd_2y: import_sd!("ratio", "2y", 2 * 365), _2y: import_sd!("ratio", "2y", 2 * 365),
ratio_sd_4y: import_sd!("ratio", "4y", 4 * 365), _4y: import_sd!("ratio", "4y", 4 * 365),
}) })
} }
@@ -54,10 +54,10 @@ impl RatioPerBlockStdDevBands {
metric_price: &impl ReadableVec<Height, Cents>, metric_price: &impl ReadableVec<Height, Cents>,
) -> Result<()> { ) -> Result<()> {
for sd in [ for sd in [
&mut self.ratio_sd, &mut self.all,
&mut self.ratio_sd_4y, &mut self._4y,
&mut self.ratio_sd_2y, &mut self._2y,
&mut self.ratio_sd_1y, &mut self._1y,
] { ] {
sd.compute_all(blocks, starting_indexes, exit, ratio_source)?; sd.compute_all(blocks, starting_indexes, exit, ratio_source)?;
sd.compute_cents_bands(starting_indexes, metric_price, exit)?; sd.compute_cents_bands(starting_indexes, metric_price, exit)?;

View File

@@ -15,7 +15,7 @@ use vecdb::{Database, EagerVec, Exit, PcoVec, ReadableVec, Rw, StorageMode};
use crate::{ use crate::{
indexes, indexes,
internal::{ComputedPerBlock, ComputedVecValue, NumericValue, Windows}, internal::{ComputedPerBlock, ComputedVecValue, NumericValue, RollingWindow24h, Windows, WindowsFrom1w},
}; };
/// Rolling window start heights — the 4 height-ago vecs (24h, 1w, 1m, 1y). /// Rolling window start heights — the 4 height-ago vecs (24h, 1w, 1m, 1y).
@@ -61,16 +61,16 @@ where
} }
} }
/// Single 24h rolling window (1 stored vec). /// Single 24h rolling window backed by ComputedPerBlock (1 stored vec).
#[derive(Traversable)] #[derive(Deref, DerefMut, Traversable)]
pub struct RollingWindow24h<T, M: StorageMode = Rw> #[traversable(transparent)]
pub struct RollingWindow24hPerBlock<T, M: StorageMode = Rw>(
pub RollingWindow24h<ComputedPerBlock<T, M>>,
)
where where
T: ComputedVecValue + PartialOrd + JsonSchema, T: ComputedVecValue + PartialOrd + JsonSchema;
{
pub _24h: ComputedPerBlock<T, M>,
}
impl<T> RollingWindow24h<T> impl<T> RollingWindow24hPerBlock<T>
where where
T: NumericValue + JsonSchema, T: NumericValue + JsonSchema,
{ {
@@ -80,14 +80,14 @@ where
version: Version, version: Version,
indexes: &indexes::Vecs, indexes: &indexes::Vecs,
) -> Result<Self> { ) -> Result<Self> {
Ok(Self { Ok(Self(RollingWindow24h {
_24h: ComputedPerBlock::forced_import( _24h: ComputedPerBlock::forced_import(
db, db,
&format!("{name}_24h"), &format!("{name}_24h"),
version, version,
indexes, indexes,
)?, )?,
}) }))
} }
pub(crate) fn compute_rolling_sum( pub(crate) fn compute_rolling_sum(
@@ -108,15 +108,11 @@ where
} }
/// Extended rolling windows: 1w + 1m + 1y (3 stored vecs). /// Extended rolling windows: 1w + 1m + 1y (3 stored vecs).
#[derive(Traversable)] #[derive(Deref, DerefMut, Traversable)]
pub struct RollingWindowsFrom1w<T, M: StorageMode = Rw> #[traversable(transparent)]
pub struct RollingWindowsFrom1w<T, M: StorageMode = Rw>(pub WindowsFrom1w<ComputedPerBlock<T, M>>)
where where
T: ComputedVecValue + PartialOrd + JsonSchema, T: ComputedVecValue + PartialOrd + JsonSchema;
{
pub _1w: ComputedPerBlock<T, M>,
pub _1m: ComputedPerBlock<T, M>,
pub _1y: ComputedPerBlock<T, M>,
}
impl<T> RollingWindowsFrom1w<T> impl<T> RollingWindowsFrom1w<T>
where where
@@ -128,34 +124,9 @@ where
version: Version, version: Version,
indexes: &indexes::Vecs, indexes: &indexes::Vecs,
) -> Result<Self> { ) -> Result<Self> {
Ok(Self { Ok(Self(WindowsFrom1w::try_from_fn(|suffix| {
_1w: ComputedPerBlock::forced_import( ComputedPerBlock::forced_import(db, &format!("{name}_{suffix}"), version, indexes)
db, })?))
&format!("{name}_1w"),
version,
indexes,
)?,
_1m: ComputedPerBlock::forced_import(
db,
&format!("{name}_1m"),
version,
indexes,
)?,
_1y: ComputedPerBlock::forced_import(
db,
&format!("{name}_1y"),
version,
indexes,
)?,
})
}
pub fn as_array(&self) -> [&ComputedPerBlock<T>; 3] {
[&self._1w, &self._1m, &self._1y]
}
pub fn as_mut_array(&mut self) -> [&mut ComputedPerBlock<T>; 3] {
[&mut self._1w, &mut self._1m, &mut self._1y]
} }
pub(crate) fn compute_rolling_sum( pub(crate) fn compute_rolling_sum(
@@ -168,15 +139,11 @@ where
where where
T: Default + SubAssign, T: Default + SubAssign,
{ {
self._1w let starts = [windows._1w, windows._1m, windows._1y];
.height for (w, starts) in self.0.as_mut_array().into_iter().zip(starts) {
.compute_rolling_sum(max_from, windows._1w, source, exit)?; w.height
self._1m .compute_rolling_sum(max_from, starts, source, exit)?;
.height }
.compute_rolling_sum(max_from, windows._1m, source, exit)?;
self._1y
.height
.compute_rolling_sum(max_from, windows._1y, source, exit)?;
Ok(()) Ok(())
} }
} }

View File

@@ -2,17 +2,31 @@ use brk_error::Result;
use brk_traversable::Traversable; use brk_traversable::Traversable;
use brk_types::{Cents, Height, Indexes, StoredF32, Version}; use brk_types::{Cents, Height, Indexes, StoredF32, Version};
use vecdb::{ use vecdb::{
AnyStoredVec, AnyVec, Database, EagerVec, Exit, PcoVec, ReadableVec, Rw, StorageMode, VecIndex, AnyStoredVec, AnyVec, Database, EagerVec, Exit, Ident, PcoVec, ReadableCloneableVec,
WritableVec, ReadableVec, Rw, StorageMode, VecIndex, WritableVec,
}; };
use crate::{ use crate::{
blocks, indexes, blocks, indexes,
internal::{ComputedPerBlock, Price, PriceTimesRatioCents}, internal::{ComputedPerBlock, LazyPerBlock, Price, PriceTimesRatioCents},
}; };
use super::StdDevPerBlock; use super::StdDevPerBlock;
#[derive(Traversable)]
pub struct StdDevBand<M: StorageMode = Rw> {
#[traversable(flatten)]
pub value: ComputedPerBlock<StoredF32, M>,
pub price: Price<ComputedPerBlock<Cents, M>>,
}
#[derive(Traversable)]
pub struct LazyStdDevBand<M: StorageMode = Rw> {
#[traversable(flatten)]
pub value: LazyPerBlock<StoredF32>,
pub price: Price<ComputedPerBlock<Cents, M>>,
}
#[derive(Traversable)] #[derive(Traversable)]
pub struct StdDevPerBlockExtended<M: StorageMode = Rw> { pub struct StdDevPerBlockExtended<M: StorageMode = Rw> {
#[traversable(flatten)] #[traversable(flatten)]
@@ -20,32 +34,19 @@ pub struct StdDevPerBlockExtended<M: StorageMode = Rw> {
pub zscore: ComputedPerBlock<StoredF32, M>, pub zscore: ComputedPerBlock<StoredF32, M>,
pub p0_5sd: ComputedPerBlock<StoredF32, M>, pub _0sd: LazyStdDevBand<M>,
pub p1sd: ComputedPerBlock<StoredF32, M>, pub p0_5sd: StdDevBand<M>,
pub p1_5sd: ComputedPerBlock<StoredF32, M>, pub p1sd: StdDevBand<M>,
pub p2sd: ComputedPerBlock<StoredF32, M>, pub p1_5sd: StdDevBand<M>,
pub p2_5sd: ComputedPerBlock<StoredF32, M>, pub p2sd: StdDevBand<M>,
pub p3sd: ComputedPerBlock<StoredF32, M>, pub p2_5sd: StdDevBand<M>,
pub m0_5sd: ComputedPerBlock<StoredF32, M>, pub p3sd: StdDevBand<M>,
pub m1sd: ComputedPerBlock<StoredF32, M>, pub m0_5sd: StdDevBand<M>,
pub m1_5sd: ComputedPerBlock<StoredF32, M>, pub m1sd: StdDevBand<M>,
pub m2sd: ComputedPerBlock<StoredF32, M>, pub m1_5sd: StdDevBand<M>,
pub m2_5sd: ComputedPerBlock<StoredF32, M>, pub m2sd: StdDevBand<M>,
pub m3sd: ComputedPerBlock<StoredF32, M>, pub m2_5sd: StdDevBand<M>,
pub m3sd: StdDevBand<M>,
pub _0sd_price: Price<ComputedPerBlock<Cents, M>>,
pub p0_5sd_price: Price<ComputedPerBlock<Cents, M>>,
pub p1sd_price: Price<ComputedPerBlock<Cents, M>>,
pub p1_5sd_price: Price<ComputedPerBlock<Cents, M>>,
pub p2sd_price: Price<ComputedPerBlock<Cents, M>>,
pub p2_5sd_price: Price<ComputedPerBlock<Cents, M>>,
pub p3sd_price: Price<ComputedPerBlock<Cents, M>>,
pub m0_5sd_price: Price<ComputedPerBlock<Cents, M>>,
pub m1sd_price: Price<ComputedPerBlock<Cents, M>>,
pub m1_5sd_price: Price<ComputedPerBlock<Cents, M>>,
pub m2sd_price: Price<ComputedPerBlock<Cents, M>>,
pub m2_5sd_price: Price<ComputedPerBlock<Cents, M>>,
pub m3sd_price: Price<ComputedPerBlock<Cents, M>>,
} }
impl StdDevPerBlockExtended { impl StdDevPerBlockExtended {
@@ -77,41 +78,50 @@ impl StdDevPerBlockExtended {
}; };
} }
Ok(Self { macro_rules! import_band {
base: StdDevPerBlock::forced_import( ($suffix:expr) => {
StdDevBand {
value: import!($suffix),
price: import_price!($suffix),
}
};
}
let base = StdDevPerBlock::forced_import(
db, db,
name, name,
period, period,
days, days,
parent_version, parent_version,
indexes, indexes,
)?, )?;
let _0sd = LazyStdDevBand {
value: LazyPerBlock::from_computed::<Ident>(
&format!("{name}_0sd{p}"),
version,
base.sma.height.read_only_boxed_clone(),
&base.sma,
),
price: import_price!("0sd"),
};
Ok(Self {
base,
zscore: import!("zscore"), zscore: import!("zscore"),
p0_5sd: import!("p0_5sd"), _0sd,
p1sd: import!("p1sd"), p0_5sd: import_band!("p0_5sd"),
p1_5sd: import!("p1_5sd"), p1sd: import_band!("p1sd"),
p2sd: import!("p2sd"), p1_5sd: import_band!("p1_5sd"),
p2_5sd: import!("p2_5sd"), p2sd: import_band!("p2sd"),
p3sd: import!("p3sd"), p2_5sd: import_band!("p2_5sd"),
m0_5sd: import!("m0_5sd"), p3sd: import_band!("p3sd"),
m1sd: import!("m1sd"), m0_5sd: import_band!("m0_5sd"),
m1_5sd: import!("m1_5sd"), m1sd: import_band!("m1sd"),
m2sd: import!("m2sd"), m1_5sd: import_band!("m1_5sd"),
m2_5sd: import!("m2_5sd"), m2sd: import_band!("m2sd"),
m3sd: import!("m3sd"), m2_5sd: import_band!("m2_5sd"),
_0sd_price: import_price!("0sd"), m3sd: import_band!("m3sd"),
p0_5sd_price: import_price!("p0_5sd"),
p1sd_price: import_price!("p1sd"),
p1_5sd_price: import_price!("p1_5sd"),
p2sd_price: import_price!("p2sd"),
p2_5sd_price: import_price!("p2_5sd"),
p3sd_price: import_price!("p3sd"),
m0_5sd_price: import_price!("m0_5sd"),
m1sd_price: import_price!("m1sd"),
m1_5sd_price: import_price!("m1_5sd"),
m2sd_price: import_price!("m2sd"),
m2_5sd_price: import_price!("m2_5sd"),
m3sd_price: import_price!("m3sd"),
}) })
} }
@@ -214,9 +224,9 @@ impl StdDevPerBlockExtended {
metric_price: &impl ReadableVec<Height, Cents>, metric_price: &impl ReadableVec<Height, Cents>,
exit: &Exit, exit: &Exit,
) -> Result<()> { ) -> Result<()> {
macro_rules! compute_band { macro_rules! compute_band_price {
($usd_field:ident, $band_source:expr) => { ($price:expr, $band_source:expr) => {
self.$usd_field $price
.cents .cents
.compute_binary::<Cents, StoredF32, PriceTimesRatioCents>( .compute_binary::<Cents, StoredF32, PriceTimesRatioCents>(
starting_indexes.height, starting_indexes.height,
@@ -227,19 +237,19 @@ impl StdDevPerBlockExtended {
}; };
} }
compute_band!(_0sd_price, &self.base.sma.height); compute_band_price!(&mut self._0sd.price, &self.base.sma.height);
compute_band!(p0_5sd_price, &self.p0_5sd.height); compute_band_price!(&mut self.p0_5sd.price, &self.p0_5sd.value.height);
compute_band!(p1sd_price, &self.p1sd.height); compute_band_price!(&mut self.p1sd.price, &self.p1sd.value.height);
compute_band!(p1_5sd_price, &self.p1_5sd.height); compute_band_price!(&mut self.p1_5sd.price, &self.p1_5sd.value.height);
compute_band!(p2sd_price, &self.p2sd.height); compute_band_price!(&mut self.p2sd.price, &self.p2sd.value.height);
compute_band!(p2_5sd_price, &self.p2_5sd.height); compute_band_price!(&mut self.p2_5sd.price, &self.p2_5sd.value.height);
compute_band!(p3sd_price, &self.p3sd.height); compute_band_price!(&mut self.p3sd.price, &self.p3sd.value.height);
compute_band!(m0_5sd_price, &self.m0_5sd.height); compute_band_price!(&mut self.m0_5sd.price, &self.m0_5sd.value.height);
compute_band!(m1sd_price, &self.m1sd.height); compute_band_price!(&mut self.m1sd.price, &self.m1sd.value.height);
compute_band!(m1_5sd_price, &self.m1_5sd.height); compute_band_price!(&mut self.m1_5sd.price, &self.m1_5sd.value.height);
compute_band!(m2sd_price, &self.m2sd.height); compute_band_price!(&mut self.m2sd.price, &self.m2sd.value.height);
compute_band!(m2_5sd_price, &self.m2_5sd.height); compute_band_price!(&mut self.m2_5sd.price, &self.m2_5sd.value.height);
compute_band!(m3sd_price, &self.m3sd.height); compute_band_price!(&mut self.m3sd.price, &self.m3sd.value.height);
Ok(()) Ok(())
} }
@@ -248,18 +258,18 @@ impl StdDevPerBlockExtended {
&mut self, &mut self,
) -> impl Iterator<Item = &mut EagerVec<PcoVec<Height, StoredF32>>> { ) -> impl Iterator<Item = &mut EagerVec<PcoVec<Height, StoredF32>>> {
[ [
&mut self.p0_5sd.height, &mut self.p0_5sd.value.height,
&mut self.p1sd.height, &mut self.p1sd.value.height,
&mut self.p1_5sd.height, &mut self.p1_5sd.value.height,
&mut self.p2sd.height, &mut self.p2sd.value.height,
&mut self.p2_5sd.height, &mut self.p2_5sd.value.height,
&mut self.p3sd.height, &mut self.p3sd.value.height,
&mut self.m0_5sd.height, &mut self.m0_5sd.value.height,
&mut self.m1sd.height, &mut self.m1sd.value.height,
&mut self.m1_5sd.height, &mut self.m1_5sd.value.height,
&mut self.m2sd.height, &mut self.m2sd.value.height,
&mut self.m2_5sd.height, &mut self.m2_5sd.value.height,
&mut self.m3sd.height, &mut self.m3sd.value.height,
] ]
.into_iter() .into_iter()
} }

View File

@@ -13,16 +13,16 @@ impl Vecs {
starting_indexes: &Indexes, starting_indexes: &Indexes,
exit: &Exit, exit: &Exit,
) -> Result<()> { ) -> Result<()> {
self.price_ath.cents.height.compute_all_time_high( self.price.cents.height.compute_all_time_high(
starting_indexes.height, starting_indexes.height,
&prices.price.cents.height, &prices.price.cents.height,
exit, exit,
)?; )?;
let mut ath_ts: Option<Timestamp> = None; let mut ath_ts: Option<Timestamp> = None;
self.days_since_price_ath.height.compute_transform3( self.days_since.height.compute_transform3(
starting_indexes.height, starting_indexes.height,
&self.price_ath.cents.height, &self.price.cents.height,
&prices.price.cents.height, &prices.price.cents.height,
&blocks.time.timestamp_monotonic, &blocks.time.timestamp_monotonic,
|(i, ath, price, ts, slf)| { |(i, ath, price, ts, slf)| {
@@ -47,9 +47,9 @@ impl Vecs {
)?; )?;
let mut prev = None; let mut prev = None;
self.max_days_between_price_ath.height.compute_transform( self.max_days_between.height.compute_transform(
starting_indexes.height, starting_indexes.height,
&self.days_since_price_ath.height, &self.days_since.height,
|(i, days, slf)| { |(i, days, slf)| {
if prev.is_none() { if prev.is_none() {
let i = i.to_usize(); let i = i.to_usize();
@@ -66,10 +66,10 @@ impl Vecs {
exit, exit,
)?; )?;
self.price_drawdown.compute_drawdown( self.drawdown.compute_drawdown(
starting_indexes.height, starting_indexes.height,
&prices.price.cents.height, &prices.price.cents.height,
&self.price_ath.cents.height, &self.price.cents.height,
exit, exit,
)?; )?;

View File

@@ -18,35 +18,35 @@ impl Vecs {
) -> Result<Self> { ) -> Result<Self> {
let v = version + VERSION; let v = version + VERSION;
let price_ath = Price::forced_import(db, "price_ath", v, indexes)?; let price = Price::forced_import(db, "price_ath", v, indexes)?;
let max_days_between_price_ath = let max_days_between =
ComputedPerBlock::forced_import(db, "max_days_between_price_ath", v, indexes)?; ComputedPerBlock::forced_import(db, "max_days_between_price_ath", v, indexes)?;
let max_years_between_price_ath = DerivedResolutions::from_computed::<DaysToYears>( let max_years_between = DerivedResolutions::from_computed::<DaysToYears>(
"max_years_between_price_ath", "max_years_between_price_ath",
v, v,
&max_days_between_price_ath, &max_days_between,
); );
let days_since_price_ath = let days_since =
ComputedPerBlock::forced_import(db, "days_since_price_ath", v, indexes)?; ComputedPerBlock::forced_import(db, "days_since_price_ath", v, indexes)?;
let years_since_price_ath = DerivedResolutions::from_computed::<DaysToYears>( let years_since = DerivedResolutions::from_computed::<DaysToYears>(
"years_since_price_ath", "years_since_price_ath",
v, v,
&days_since_price_ath, &days_since,
); );
let price_drawdown = PercentPerBlock::forced_import(db, "price_drawdown", v, indexes)?; let drawdown = PercentPerBlock::forced_import(db, "price_drawdown", v, indexes)?;
Ok(Self { Ok(Self {
price_ath, price,
price_drawdown, drawdown,
days_since_price_ath, days_since,
years_since_price_ath, years_since,
max_days_between_price_ath, max_days_between,
max_years_between_price_ath, max_years_between,
}) })
} }
} }

View File

@@ -6,10 +6,10 @@ use crate::internal::{ComputedPerBlock, DerivedResolutions, PercentPerBlock, Pri
#[derive(Traversable)] #[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw> { pub struct Vecs<M: StorageMode = Rw> {
pub price_ath: Price<ComputedPerBlock<Cents, M>>, pub price: Price<ComputedPerBlock<Cents, M>>,
pub price_drawdown: PercentPerBlock<BasisPointsSigned16, M>, pub drawdown: PercentPerBlock<BasisPointsSigned16, M>,
pub days_since_price_ath: ComputedPerBlock<StoredF32, M>, pub days_since: ComputedPerBlock<StoredF32, M>,
pub years_since_price_ath: DerivedResolutions<StoredF32, StoredF32>, pub years_since: DerivedResolutions<StoredF32, StoredF32>,
pub max_days_between_price_ath: ComputedPerBlock<StoredF32, M>, pub max_days_between: ComputedPerBlock<StoredF32, M>,
pub max_years_between_price_ath: DerivedResolutions<StoredF32, StoredF32>, pub max_years_between: DerivedResolutions<StoredF32, StoredF32>,
} }

View File

@@ -40,8 +40,8 @@ impl Vecs {
self.stoch_k.bps.height.compute_transform3( self.stoch_k.bps.height.compute_transform3(
starting_indexes.height, starting_indexes.height,
price, price,
&range.price_min_2w.usd.height, &range.min._2w.usd.height,
&range.price_max_2w.usd.height, &range.max._2w.usd.height,
|(h, close, low, high, ..)| { |(h, close, low, high, ..)| {
let range = *high - *low; let range = *high - *low;
let stoch = if range == 0.0 { let stoch = if range == 0.0 {
@@ -127,8 +127,8 @@ impl Vecs {
.bps .bps
.compute_binary::<Dollars, Dollars, RatioDollarsBp32>( .compute_binary::<Dollars, Dollars, RatioDollarsBp32>(
starting_indexes.height, starting_indexes.height,
&moving_average.price_sma_111d.price.usd.height, &moving_average.sma._111d.price.usd.height,
&moving_average.price_sma_350d_x2.usd.height, &moving_average.sma._350d_x2.usd.height,
exit, exit,
)?; )?;

View File

@@ -1,5 +1,5 @@
use brk_traversable::Traversable; use brk_traversable::Traversable;
use brk_types::{BasisPoints16, StoredF32}; use brk_types::{BasisPoints16, BasisPoints32, StoredF32};
use vecdb::{Rw, StorageMode}; use vecdb::{Rw, StorageMode};
use crate::internal::{ComputedPerBlock, RatioPerBlock, PercentPerBlock, Windows}; use crate::internal::{ComputedPerBlock, RatioPerBlock, PercentPerBlock, Windows};
@@ -29,15 +29,15 @@ pub struct MacdChain<M: StorageMode = Rw> {
#[derive(Traversable)] #[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw> { pub struct Vecs<M: StorageMode = Rw> {
pub puell_multiple: RatioPerBlock<M>, pub puell_multiple: RatioPerBlock<BasisPoints32, M>,
pub nvt: RatioPerBlock<M>, pub nvt: RatioPerBlock<BasisPoints32, M>,
pub rsi: Windows<RsiChain<M>>, pub rsi: Windows<RsiChain<M>>,
pub stoch_k: PercentPerBlock<BasisPoints16, M>, pub stoch_k: PercentPerBlock<BasisPoints16, M>,
pub stoch_d: PercentPerBlock<BasisPoints16, M>, pub stoch_d: PercentPerBlock<BasisPoints16, M>,
pub pi_cycle: RatioPerBlock<M>, pub pi_cycle: RatioPerBlock<BasisPoints32, M>,
pub macd: Windows<MacdChain<M>>, pub macd: Windows<MacdChain<M>>,

View File

@@ -16,22 +16,22 @@ impl Vecs {
let close = &prices.price.cents.height; let close = &prices.price.cents.height;
for (sma, period) in [ for (sma, period) in [
(&mut self.price_sma_1w, 7), (&mut self.sma._1w, 7),
(&mut self.price_sma_8d, 8), (&mut self.sma._8d, 8),
(&mut self.price_sma_13d, 13), (&mut self.sma._13d, 13),
(&mut self.price_sma_21d, 21), (&mut self.sma._21d, 21),
(&mut self.price_sma_1m, 30), (&mut self.sma._1m, 30),
(&mut self.price_sma_34d, 34), (&mut self.sma._34d, 34),
(&mut self.price_sma_55d, 55), (&mut self.sma._55d, 55),
(&mut self.price_sma_89d, 89), (&mut self.sma._89d, 89),
(&mut self.price_sma_111d, 111), (&mut self.sma._111d, 111),
(&mut self.price_sma_144d, 144), (&mut self.sma._144d, 144),
(&mut self.price_sma_200d, 200), (&mut self.sma._200d, 200),
(&mut self.price_sma_350d, 350), (&mut self.sma._350d, 350),
(&mut self.price_sma_1y, 365), (&mut self.sma._1y, 365),
(&mut self.price_sma_2y, 2 * 365), (&mut self.sma._2y, 2 * 365),
(&mut self.price_sma_200w, 200 * 7), (&mut self.sma._200w, 200 * 7),
(&mut self.price_sma_4y, 4 * 365), (&mut self.sma._4y, 4 * 365),
] { ] {
let window_starts = blocks.lookback.start_vec(period); let window_starts = blocks.lookback.start_vec(period);
sma.compute_all(prices, starting_indexes, exit, |v| { sma.compute_all(prices, starting_indexes, exit, |v| {
@@ -41,22 +41,22 @@ impl Vecs {
} }
for (ema, period) in [ for (ema, period) in [
(&mut self.price_ema_1w, 7), (&mut self.ema._1w, 7),
(&mut self.price_ema_8d, 8), (&mut self.ema._8d, 8),
(&mut self.price_ema_12d, 12), (&mut self.ema._12d, 12),
(&mut self.price_ema_13d, 13), (&mut self.ema._13d, 13),
(&mut self.price_ema_21d, 21), (&mut self.ema._21d, 21),
(&mut self.price_ema_26d, 26), (&mut self.ema._26d, 26),
(&mut self.price_ema_1m, 30), (&mut self.ema._1m, 30),
(&mut self.price_ema_34d, 34), (&mut self.ema._34d, 34),
(&mut self.price_ema_55d, 55), (&mut self.ema._55d, 55),
(&mut self.price_ema_89d, 89), (&mut self.ema._89d, 89),
(&mut self.price_ema_144d, 144), (&mut self.ema._144d, 144),
(&mut self.price_ema_200d, 200), (&mut self.ema._200d, 200),
(&mut self.price_ema_1y, 365), (&mut self.ema._1y, 365),
(&mut self.price_ema_2y, 2 * 365), (&mut self.ema._2y, 2 * 365),
(&mut self.price_ema_200w, 200 * 7), (&mut self.ema._200w, 200 * 7),
(&mut self.price_ema_4y, 4 * 365), (&mut self.ema._4y, 4 * 365),
] { ] {
let window_starts = blocks.lookback.start_vec(period); let window_starts = blocks.lookback.start_vec(period);
ema.compute_all(prices, starting_indexes, exit, |v| { ema.compute_all(prices, starting_indexes, exit, |v| {

View File

@@ -2,10 +2,13 @@ use brk_error::Result;
use brk_types::Version; use brk_types::Version;
use vecdb::Database; use vecdb::Database;
use super::Vecs; use super::{
vecs::{EmaVecs, SmaVecs},
Vecs,
};
use crate::{ use crate::{
indexes, indexes,
internal::{CentsTimesTenths, PriceWithRatioPerBlock, Price}, internal::{CentsTimesTenths, Price, PriceWithRatioPerBlock},
}; };
impl Vecs { impl Vecs {
@@ -16,72 +19,73 @@ impl Vecs {
) -> Result<Self> { ) -> Result<Self> {
macro_rules! import { macro_rules! import {
($name:expr) => { ($name:expr) => {
PriceWithRatioPerBlock::forced_import( PriceWithRatioPerBlock::forced_import(db, $name, version, indexes)?
db, $name, version, indexes,
)?
}; };
} }
let price_sma_200d = import!("price_sma_200d"); let sma_200d = import!("price_sma_200d");
let price_sma_350d = import!("price_sma_350d"); let sma_350d = import!("price_sma_350d");
let price_sma_200d_source = &price_sma_200d.price.cents; let price_sma_200d_source = &sma_200d.price.cents;
let price_sma_200d_x2_4 = Price::from_cents_source::<CentsTimesTenths<24>>( let _200d_x2_4 = Price::from_cents_source::<CentsTimesTenths<24>>(
"price_sma_200d_x2_4", "price_sma_200d_x2_4",
version, version,
price_sma_200d_source, price_sma_200d_source,
); );
let price_sma_200d_x0_8 = Price::from_cents_source::<CentsTimesTenths<8>>( let _200d_x0_8 = Price::from_cents_source::<CentsTimesTenths<8>>(
"price_sma_200d_x0_8", "price_sma_200d_x0_8",
version, version,
price_sma_200d_source, price_sma_200d_source,
); );
let price_sma_350d_source = &price_sma_350d.price.cents; let price_sma_350d_source = &sma_350d.price.cents;
let price_sma_350d_x2 = Price::from_cents_source::<CentsTimesTenths<20>>( let _350d_x2 = Price::from_cents_source::<CentsTimesTenths<20>>(
"price_sma_350d_x2", "price_sma_350d_x2",
version, version,
price_sma_350d_source, price_sma_350d_source,
); );
Ok(Self { let sma = SmaVecs {
price_sma_1w: import!("price_sma_1w"), _1w: import!("price_sma_1w"),
price_sma_8d: import!("price_sma_8d"), _8d: import!("price_sma_8d"),
price_sma_13d: import!("price_sma_13d"), _13d: import!("price_sma_13d"),
price_sma_21d: import!("price_sma_21d"), _21d: import!("price_sma_21d"),
price_sma_1m: import!("price_sma_1m"), _1m: import!("price_sma_1m"),
price_sma_34d: import!("price_sma_34d"), _34d: import!("price_sma_34d"),
price_sma_55d: import!("price_sma_55d"), _55d: import!("price_sma_55d"),
price_sma_89d: import!("price_sma_89d"), _89d: import!("price_sma_89d"),
price_sma_111d: import!("price_sma_111d"), _111d: import!("price_sma_111d"),
price_sma_144d: import!("price_sma_144d"), _144d: import!("price_sma_144d"),
price_sma_200d, _200d: sma_200d,
price_sma_350d, _350d: sma_350d,
price_sma_1y: import!("price_sma_1y"), _1y: import!("price_sma_1y"),
price_sma_2y: import!("price_sma_2y"), _2y: import!("price_sma_2y"),
price_sma_200w: import!("price_sma_200w"), _200w: import!("price_sma_200w"),
price_sma_4y: import!("price_sma_4y"), _4y: import!("price_sma_4y"),
_200d_x2_4,
_200d_x0_8,
_350d_x2,
};
price_ema_1w: import!("price_ema_1w"), let ema = EmaVecs {
price_ema_8d: import!("price_ema_8d"), _1w: import!("price_ema_1w"),
price_ema_12d: import!("price_ema_12d"), _8d: import!("price_ema_8d"),
price_ema_13d: import!("price_ema_13d"), _12d: import!("price_ema_12d"),
price_ema_21d: import!("price_ema_21d"), _13d: import!("price_ema_13d"),
price_ema_26d: import!("price_ema_26d"), _21d: import!("price_ema_21d"),
price_ema_1m: import!("price_ema_1m"), _26d: import!("price_ema_26d"),
price_ema_34d: import!("price_ema_34d"), _1m: import!("price_ema_1m"),
price_ema_55d: import!("price_ema_55d"), _34d: import!("price_ema_34d"),
price_ema_89d: import!("price_ema_89d"), _55d: import!("price_ema_55d"),
price_ema_144d: import!("price_ema_144d"), _89d: import!("price_ema_89d"),
price_ema_200d: import!("price_ema_200d"), _144d: import!("price_ema_144d"),
price_ema_1y: import!("price_ema_1y"), _200d: import!("price_ema_200d"),
price_ema_2y: import!("price_ema_2y"), _1y: import!("price_ema_1y"),
price_ema_200w: import!("price_ema_200w"), _2y: import!("price_ema_2y"),
price_ema_4y: import!("price_ema_4y"), _200w: import!("price_ema_200w"),
_4y: import!("price_ema_4y"),
};
price_sma_200d_x2_4, Ok(Self { sma, ema })
price_sma_200d_x0_8,
price_sma_350d_x2,
})
} }
} }

View File

@@ -2,44 +2,53 @@ use brk_traversable::Traversable;
use brk_types::Cents; use brk_types::Cents;
use vecdb::{Rw, StorageMode}; use vecdb::{Rw, StorageMode};
use crate::internal::{PriceWithRatioPerBlock, LazyPerBlock, Price}; use crate::internal::{LazyPerBlock, Price, PriceWithRatioPerBlock};
#[derive(Traversable)]
pub struct SmaVecs<M: StorageMode = Rw> {
pub _1w: PriceWithRatioPerBlock<M>,
pub _8d: PriceWithRatioPerBlock<M>,
pub _13d: PriceWithRatioPerBlock<M>,
pub _21d: PriceWithRatioPerBlock<M>,
pub _1m: PriceWithRatioPerBlock<M>,
pub _34d: PriceWithRatioPerBlock<M>,
pub _55d: PriceWithRatioPerBlock<M>,
pub _89d: PriceWithRatioPerBlock<M>,
pub _111d: PriceWithRatioPerBlock<M>,
pub _144d: PriceWithRatioPerBlock<M>,
pub _200d: PriceWithRatioPerBlock<M>,
pub _350d: PriceWithRatioPerBlock<M>,
pub _1y: PriceWithRatioPerBlock<M>,
pub _2y: PriceWithRatioPerBlock<M>,
pub _200w: PriceWithRatioPerBlock<M>,
pub _4y: PriceWithRatioPerBlock<M>,
pub _200d_x2_4: Price<LazyPerBlock<Cents, Cents>>,
pub _200d_x0_8: Price<LazyPerBlock<Cents, Cents>>,
pub _350d_x2: Price<LazyPerBlock<Cents, Cents>>,
}
#[derive(Traversable)]
pub struct EmaVecs<M: StorageMode = Rw> {
pub _1w: PriceWithRatioPerBlock<M>,
pub _8d: PriceWithRatioPerBlock<M>,
pub _12d: PriceWithRatioPerBlock<M>,
pub _13d: PriceWithRatioPerBlock<M>,
pub _21d: PriceWithRatioPerBlock<M>,
pub _26d: PriceWithRatioPerBlock<M>,
pub _1m: PriceWithRatioPerBlock<M>,
pub _34d: PriceWithRatioPerBlock<M>,
pub _55d: PriceWithRatioPerBlock<M>,
pub _89d: PriceWithRatioPerBlock<M>,
pub _144d: PriceWithRatioPerBlock<M>,
pub _200d: PriceWithRatioPerBlock<M>,
pub _1y: PriceWithRatioPerBlock<M>,
pub _2y: PriceWithRatioPerBlock<M>,
pub _200w: PriceWithRatioPerBlock<M>,
pub _4y: PriceWithRatioPerBlock<M>,
}
#[derive(Traversable)] #[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw> { pub struct Vecs<M: StorageMode = Rw> {
pub price_sma_1w: PriceWithRatioPerBlock<M>, pub sma: SmaVecs<M>,
pub price_sma_8d: PriceWithRatioPerBlock<M>, pub ema: EmaVecs<M>,
pub price_sma_13d: PriceWithRatioPerBlock<M>,
pub price_sma_21d: PriceWithRatioPerBlock<M>,
pub price_sma_1m: PriceWithRatioPerBlock<M>,
pub price_sma_34d: PriceWithRatioPerBlock<M>,
pub price_sma_55d: PriceWithRatioPerBlock<M>,
pub price_sma_89d: PriceWithRatioPerBlock<M>,
pub price_sma_111d: PriceWithRatioPerBlock<M>,
pub price_sma_144d: PriceWithRatioPerBlock<M>,
pub price_sma_200d: PriceWithRatioPerBlock<M>,
pub price_sma_350d: PriceWithRatioPerBlock<M>,
pub price_sma_1y: PriceWithRatioPerBlock<M>,
pub price_sma_2y: PriceWithRatioPerBlock<M>,
pub price_sma_200w: PriceWithRatioPerBlock<M>,
pub price_sma_4y: PriceWithRatioPerBlock<M>,
pub price_ema_1w: PriceWithRatioPerBlock<M>,
pub price_ema_8d: PriceWithRatioPerBlock<M>,
pub price_ema_12d: PriceWithRatioPerBlock<M>,
pub price_ema_13d: PriceWithRatioPerBlock<M>,
pub price_ema_21d: PriceWithRatioPerBlock<M>,
pub price_ema_26d: PriceWithRatioPerBlock<M>,
pub price_ema_1m: PriceWithRatioPerBlock<M>,
pub price_ema_34d: PriceWithRatioPerBlock<M>,
pub price_ema_55d: PriceWithRatioPerBlock<M>,
pub price_ema_89d: PriceWithRatioPerBlock<M>,
pub price_ema_144d: PriceWithRatioPerBlock<M>,
pub price_ema_200d: PriceWithRatioPerBlock<M>,
pub price_ema_1y: PriceWithRatioPerBlock<M>,
pub price_ema_2y: PriceWithRatioPerBlock<M>,
pub price_ema_200w: PriceWithRatioPerBlock<M>,
pub price_ema_4y: PriceWithRatioPerBlock<M>,
pub price_sma_200d_x2_4: Price<LazyPerBlock<Cents, Cents>>,
pub price_sma_200d_x0_8: Price<LazyPerBlock<Cents, Cents>>,
pub price_sma_350d_x2: Price<LazyPerBlock<Cents, Cents>>,
} }

View File

@@ -17,23 +17,23 @@ impl Vecs {
for (min_vec, max_vec, starts) in [ for (min_vec, max_vec, starts) in [
( (
&mut self.price_min_1w.cents.height, &mut self.min._1w.cents.height,
&mut self.price_max_1w.cents.height, &mut self.max._1w.cents.height,
&blocks.lookback.height_1w_ago, &blocks.lookback.height_1w_ago,
), ),
( (
&mut self.price_min_2w.cents.height, &mut self.min._2w.cents.height,
&mut self.price_max_2w.cents.height, &mut self.max._2w.cents.height,
&blocks.lookback.height_2w_ago, &blocks.lookback.height_2w_ago,
), ),
( (
&mut self.price_min_1m.cents.height, &mut self.min._1m.cents.height,
&mut self.price_max_1m.cents.height, &mut self.max._1m.cents.height,
&blocks.lookback.height_1m_ago, &blocks.lookback.height_1m_ago,
), ),
( (
&mut self.price_min_1y.cents.height, &mut self.min._1y.cents.height,
&mut self.price_max_1y.cents.height, &mut self.max._1y.cents.height,
&blocks.lookback.height_1y_ago, &blocks.lookback.height_1y_ago,
), ),
] { ] {
@@ -53,7 +53,7 @@ impl Vecs {
// True range at block level: |price[h] - price[h-1]| // True range at block level: |price[h] - price[h-1]|
let mut prev_price = None; let mut prev_price = None;
self.price_true_range.height.compute_transform( self.true_range.height.compute_transform(
starting_indexes.height, starting_indexes.height,
price, price,
|(h, current, ..)| { |(h, current, ..)| {
@@ -73,21 +73,21 @@ impl Vecs {
)?; )?;
// 2w rolling sum of true range // 2w rolling sum of true range
self.price_true_range_sum_2w.height.compute_rolling_sum( self.true_range_sum_2w.height.compute_rolling_sum(
starting_indexes.height, starting_indexes.height,
&blocks.lookback.height_2w_ago, &blocks.lookback.height_2w_ago,
&self.price_true_range.height, &self.true_range.height,
exit, exit,
)?; )?;
self.price_choppiness_index_2w self.choppiness_index_2w
.bps .bps
.height .height
.compute_transform4( .compute_transform4(
starting_indexes.height, starting_indexes.height,
&self.price_true_range_sum_2w.height, &self.true_range_sum_2w.height,
&self.price_max_2w.cents.height, &self.max._2w.cents.height,
&self.price_min_2w.cents.height, &self.min._2w.cents.height,
&blocks.lookback.height_2w_ago, &blocks.lookback.height_2w_ago,
|(h, tr_sum, max, min, window_start, ..)| { |(h, tr_sum, max, min, window_start, ..)| {
let range = f64::from(max) - f64::from(min); let range = f64::from(max) - f64::from(min);

View File

@@ -2,7 +2,7 @@ use brk_error::Result;
use brk_types::Version; use brk_types::Version;
use vecdb::Database; use vecdb::Database;
use super::Vecs; use super::{vecs::PriceMinMaxVecs, Vecs};
use crate::{ use crate::{
indexes, indexes,
internal::{ComputedPerBlock, PercentPerBlock, Price}, internal::{ComputedPerBlock, PercentPerBlock, Price},
@@ -17,27 +17,31 @@ impl Vecs {
let v1 = Version::ONE; let v1 = Version::ONE;
Ok(Self { Ok(Self {
price_min_1w: Price::forced_import(db, "price_min_1w", version + v1, indexes)?, min: PriceMinMaxVecs {
price_max_1w: Price::forced_import(db, "price_max_1w", version + v1, indexes)?, _1w: Price::forced_import(db, "price_min_1w", version + v1, indexes)?,
price_min_2w: Price::forced_import(db, "price_min_2w", version + v1, indexes)?, _2w: Price::forced_import(db, "price_min_2w", version + v1, indexes)?,
price_max_2w: Price::forced_import(db, "price_max_2w", version + v1, indexes)?, _1m: Price::forced_import(db, "price_min_1m", version + v1, indexes)?,
price_min_1m: Price::forced_import(db, "price_min_1m", version + v1, indexes)?, _1y: Price::forced_import(db, "price_min_1y", version + v1, indexes)?,
price_max_1m: Price::forced_import(db, "price_max_1m", version + v1, indexes)?, },
price_min_1y: Price::forced_import(db, "price_min_1y", version + v1, indexes)?, max: PriceMinMaxVecs {
price_max_1y: Price::forced_import(db, "price_max_1y", version + v1, indexes)?, _1w: Price::forced_import(db, "price_max_1w", version + v1, indexes)?,
price_true_range: ComputedPerBlock::forced_import( _2w: Price::forced_import(db, "price_max_2w", version + v1, indexes)?,
_1m: Price::forced_import(db, "price_max_1m", version + v1, indexes)?,
_1y: Price::forced_import(db, "price_max_1y", version + v1, indexes)?,
},
true_range: ComputedPerBlock::forced_import(
db, db,
"price_true_range", "price_true_range",
version + v1, version + v1,
indexes, indexes,
)?, )?,
price_true_range_sum_2w: ComputedPerBlock::forced_import( true_range_sum_2w: ComputedPerBlock::forced_import(
db, db,
"price_true_range_sum_2w", "price_true_range_sum_2w",
version + v1, version + v1,
indexes, indexes,
)?, )?,
price_choppiness_index_2w: PercentPerBlock::forced_import( choppiness_index_2w: PercentPerBlock::forced_import(
db, db,
"price_choppiness_index_2w", "price_choppiness_index_2w",
version + v1, version + v1,

View File

@@ -3,17 +3,20 @@ use brk_types::{BasisPoints16, Cents, StoredF32};
use vecdb::{Rw, StorageMode}; use vecdb::{Rw, StorageMode};
use crate::internal::{ComputedPerBlock, PercentPerBlock, Price}; use crate::internal::{ComputedPerBlock, PercentPerBlock, Price};
#[derive(Traversable)]
pub struct PriceMinMaxVecs<M: StorageMode = Rw> {
pub _1w: Price<ComputedPerBlock<Cents, M>>,
pub _2w: Price<ComputedPerBlock<Cents, M>>,
pub _1m: Price<ComputedPerBlock<Cents, M>>,
pub _1y: Price<ComputedPerBlock<Cents, M>>,
}
#[derive(Traversable)] #[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw> { pub struct Vecs<M: StorageMode = Rw> {
pub price_min_1w: Price<ComputedPerBlock<Cents, M>>, pub min: PriceMinMaxVecs<M>,
pub price_max_1w: Price<ComputedPerBlock<Cents, M>>, pub max: PriceMinMaxVecs<M>,
pub price_min_2w: Price<ComputedPerBlock<Cents, M>>, pub true_range: ComputedPerBlock<StoredF32, M>,
pub price_max_2w: Price<ComputedPerBlock<Cents, M>>, pub true_range_sum_2w: ComputedPerBlock<StoredF32, M>,
pub price_min_1m: Price<ComputedPerBlock<Cents, M>>, pub choppiness_index_2w: PercentPerBlock<BasisPoints16, M>,
pub price_max_1m: Price<ComputedPerBlock<Cents, M>>,
pub price_min_1y: Price<ComputedPerBlock<Cents, M>>,
pub price_max_1y: Price<ComputedPerBlock<Cents, M>>,
pub price_true_range: ComputedPerBlock<StoredF32, M>,
pub price_true_range_sum_2w: ComputedPerBlock<StoredF32, M>,
pub price_choppiness_index_2w: PercentPerBlock<BasisPoints16, M>,
} }

View File

@@ -47,9 +47,9 @@ impl Vecs {
let _24h_price_return_ratio = &self.price_return._24h.ratio.height; let _24h_price_return_ratio = &self.price_return._24h.ratio.height;
for sd in [ for sd in [
&mut self.price_return_24h_sd_1w, &mut self.price_return_24h_sd._1w,
&mut self.price_return_24h_sd_1m, &mut self.price_return_24h_sd._1m,
&mut self.price_return_24h_sd_1y, &mut self.price_return_24h_sd._1y,
] { ] {
sd.compute_all(blocks, starting_indexes, exit, _24h_price_return_ratio)?; sd.compute_all(blocks, starting_indexes, exit, _24h_price_return_ratio)?;
} }

View File

@@ -3,7 +3,7 @@ use brk_types::Version;
use vecdb::Database; use vecdb::Database;
use super::super::lookback::ByLookbackPeriod; use super::super::lookback::ByLookbackPeriod;
use super::Vecs; use super::{vecs::PriceReturn24hSdVecs, Vecs};
use crate::{ use crate::{
indexes, indexes,
internal::{StdDevPerBlock, PercentPerBlock}, internal::{StdDevPerBlock, PercentPerBlock},
@@ -27,37 +27,37 @@ impl Vecs {
PercentPerBlock::forced_import(db, &format!("price_cagr_{name}"), version, indexes) PercentPerBlock::forced_import(db, &format!("price_cagr_{name}"), version, indexes)
})?; })?;
let price_return_24h_sd_1w = StdDevPerBlock::forced_import( let price_return_24h_sd = PriceReturn24hSdVecs {
_1w: StdDevPerBlock::forced_import(
db, db,
"price_return_24h", "price_return_24h",
"1w", "1w",
7, 7,
version + v1, version + v1,
indexes, indexes,
)?; )?,
let price_return_24h_sd_1m = StdDevPerBlock::forced_import( _1m: StdDevPerBlock::forced_import(
db, db,
"price_return_24h", "price_return_24h",
"1m", "1m",
30, 30,
version + v1, version + v1,
indexes, indexes,
)?; )?,
let price_return_24h_sd_1y = StdDevPerBlock::forced_import( _1y: StdDevPerBlock::forced_import(
db, db,
"price_return_24h", "price_return_24h",
"1y", "1y",
365, 365,
version + v1, version + v1,
indexes, indexes,
)?; )?,
};
Ok(Self { Ok(Self {
price_return, price_return,
price_cagr, price_cagr,
price_return_24h_sd_1w, price_return_24h_sd,
price_return_24h_sd_1m,
price_return_24h_sd_1y,
}) })
} }
} }

View File

@@ -3,19 +3,20 @@ use brk_types::BasisPointsSigned32;
use vecdb::{Rw, StorageMode}; use vecdb::{Rw, StorageMode};
use crate::{ use crate::{
internal::{StdDevPerBlock, PercentPerBlock}, internal::{PercentPerBlock, StdDevPerBlock},
market::{dca::ByDcaCagr, lookback::ByLookbackPeriod}, market::{dca::ByDcaCagr, lookback::ByLookbackPeriod},
}; };
#[derive(Traversable)]
pub struct PriceReturn24hSdVecs<M: StorageMode = Rw> {
pub _1w: StdDevPerBlock<M>,
pub _1m: StdDevPerBlock<M>,
pub _1y: StdDevPerBlock<M>,
}
#[derive(Traversable)] #[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw> { pub struct Vecs<M: StorageMode = Rw> {
pub price_return: ByLookbackPeriod<PercentPerBlock<BasisPointsSigned32, M>>, pub price_return: ByLookbackPeriod<PercentPerBlock<BasisPointsSigned32, M>>,
// CAGR (computed from returns, 2y+ only)
pub price_cagr: ByDcaCagr<PercentPerBlock<BasisPointsSigned32, M>>, pub price_cagr: ByDcaCagr<PercentPerBlock<BasisPointsSigned32, M>>,
pub price_return_24h_sd: PriceReturn24hSdVecs<M>,
// Returns standard deviation (computed from 24h returns)
pub price_return_24h_sd_1w: StdDevPerBlock<M>,
pub price_return_24h_sd_1m: StdDevPerBlock<M>,
pub price_return_24h_sd_1y: StdDevPerBlock<M>,
} }

View File

@@ -10,43 +10,42 @@ impl Vecs {
pub(crate) fn forced_import(version: Version, returns: &returns::Vecs) -> Result<Self> { pub(crate) fn forced_import(version: Version, returns: &returns::Vecs) -> Result<Self> {
let v2 = Version::TWO; let v2 = Version::TWO;
let price_volatility_1w = LazyPerBlock::from_computed::<TimesSqrt<Days7>>( let _1w = LazyPerBlock::from_computed::<TimesSqrt<Days7>>(
"price_volatility_1w", "price_volatility_1w",
version + v2, version + v2,
returns returns
.price_return_24h_sd_1w .price_return_24h_sd
._1w
.sd .sd
.height .height
.read_only_boxed_clone(), .read_only_boxed_clone(),
&returns.price_return_24h_sd_1w.sd, &returns.price_return_24h_sd._1w.sd,
); );
let price_volatility_1m = LazyPerBlock::from_computed::<TimesSqrt<Days30>>( let _1m = LazyPerBlock::from_computed::<TimesSqrt<Days30>>(
"price_volatility_1m", "price_volatility_1m",
version + v2, version + v2,
returns returns
.price_return_24h_sd_1m .price_return_24h_sd
._1m
.sd .sd
.height .height
.read_only_boxed_clone(), .read_only_boxed_clone(),
&returns.price_return_24h_sd_1m.sd, &returns.price_return_24h_sd._1m.sd,
); );
let price_volatility_1y = LazyPerBlock::from_computed::<TimesSqrt<Days365>>( let _1y = LazyPerBlock::from_computed::<TimesSqrt<Days365>>(
"price_volatility_1y", "price_volatility_1y",
version + v2, version + v2,
returns returns
.price_return_24h_sd_1y .price_return_24h_sd
._1y
.sd .sd
.height .height
.read_only_boxed_clone(), .read_only_boxed_clone(),
&returns.price_return_24h_sd_1y.sd, &returns.price_return_24h_sd._1y.sd,
); );
Ok(Self { Ok(Self { _1w, _1m, _1y })
price_volatility_1w,
price_volatility_1m,
price_volatility_1y,
})
} }
} }

View File

@@ -5,7 +5,7 @@ use crate::internal::LazyPerBlock;
use brk_types::StoredF32; use brk_types::StoredF32;
#[derive(Clone, Traversable)] #[derive(Clone, Traversable)]
pub struct Vecs { pub struct Vecs {
pub price_volatility_1w: LazyPerBlock<StoredF32>, pub _1w: LazyPerBlock<StoredF32>,
pub price_volatility_1m: LazyPerBlock<StoredF32>, pub _1m: LazyPerBlock<StoredF32>,
pub price_volatility_1y: LazyPerBlock<StoredF32>, pub _1y: LazyPerBlock<StoredF32>,
} }

View File

@@ -38,10 +38,10 @@ impl Vecs {
let hash_rate = &self.hash_rate.height; let hash_rate = &self.hash_rate.height;
for (sma, window) in [ for (sma, window) in [
(&mut self.hash_rate_sma_1w.height, &lookback.height_1w_ago), (&mut self.hash_rate_sma._1w.height, &lookback.height_1w_ago),
(&mut self.hash_rate_sma_1m.height, &lookback.height_1m_ago), (&mut self.hash_rate_sma._1m.height, &lookback.height_1m_ago),
(&mut self.hash_rate_sma_2m.height, &lookback.height_2m_ago), (&mut self.hash_rate_sma._2m.height, &lookback.height_2m_ago),
(&mut self.hash_rate_sma_1y.height, &lookback.height_1y_ago), (&mut self.hash_rate_sma._1y.height, &lookback.height_1y_ago),
] { ] {
sma.compute_rolling_average(starting_indexes.height, window, hash_rate, exit)?; sma.compute_rolling_average(starting_indexes.height, window, hash_rate, exit)?;
} }
@@ -59,7 +59,7 @@ impl Vecs {
exit, exit,
)?; )?;
self.hash_price_ths.height.compute_transform2( self.hash_price.ths.height.compute_transform2(
starting_indexes.height, starting_indexes.height,
coinbase_usd_24h_sum, coinbase_usd_24h_sum,
&self.hash_rate.height, &self.hash_rate.height,
@@ -75,14 +75,14 @@ impl Vecs {
exit, exit,
)?; )?;
self.hash_price_phs.height.compute_transform( self.hash_price.phs.height.compute_transform(
starting_indexes.height, starting_indexes.height,
&self.hash_price_ths.height, &self.hash_price.ths.height,
|(i, price, ..)| (i, (*price * 1000.0).into()), |(i, price, ..)| (i, (*price * 1000.0).into()),
exit, exit,
)?; )?;
self.hash_value_ths.height.compute_transform2( self.hash_value.ths.height.compute_transform2(
starting_indexes.height, starting_indexes.height,
coinbase_sats_24h_sum, coinbase_sats_24h_sum,
&self.hash_rate.height, &self.hash_rate.height,
@@ -98,47 +98,49 @@ impl Vecs {
exit, exit,
)?; )?;
self.hash_value_phs.height.compute_transform( self.hash_value.phs.height.compute_transform(
starting_indexes.height, starting_indexes.height,
&self.hash_value_ths.height, &self.hash_value.ths.height,
|(i, value, ..)| (i, (*value * 1000.0).into()), |(i, value, ..)| (i, (*value * 1000.0).into()),
exit, exit,
)?; )?;
for (min_vec, src_vec) in [ for (min_vec, src_vec) in [
( (
&mut self.hash_price_ths_min.height, &mut self.hash_price.ths_min.height,
&self.hash_price_ths.height, &self.hash_price.ths.height,
), ),
( (
&mut self.hash_price_phs_min.height, &mut self.hash_price.phs_min.height,
&self.hash_price_phs.height, &self.hash_price.phs.height,
), ),
( (
&mut self.hash_value_ths_min.height, &mut self.hash_value.ths_min.height,
&self.hash_value_ths.height, &self.hash_value.ths.height,
), ),
( (
&mut self.hash_value_phs_min.height, &mut self.hash_value.phs_min.height,
&self.hash_value_phs.height, &self.hash_value.phs.height,
), ),
] { ] {
min_vec.compute_all_time_low_(starting_indexes.height, src_vec, exit, true)?; min_vec.compute_all_time_low_(starting_indexes.height, src_vec, exit, true)?;
} }
self.hash_price_rebound self.hash_price
.rebound
.compute_binary::<StoredF32, StoredF32, RatioDiffF32Bps32>( .compute_binary::<StoredF32, StoredF32, RatioDiffF32Bps32>(
starting_indexes.height, starting_indexes.height,
&self.hash_price_phs.height, &self.hash_price.phs.height,
&self.hash_price_phs_min.height, &self.hash_price.phs_min.height,
exit, exit,
)?; )?;
self.hash_value_rebound self.hash_value
.rebound
.compute_binary::<StoredF32, StoredF32, RatioDiffF32Bps32>( .compute_binary::<StoredF32, StoredF32, RatioDiffF32Bps32>(
starting_indexes.height, starting_indexes.height,
&self.hash_value_phs.height, &self.hash_value.phs.height,
&self.hash_value_phs_min.height, &self.hash_value.phs_min.height,
exit, exit,
)?; )?;

View File

@@ -2,7 +2,10 @@ use brk_error::Result;
use brk_types::Version; use brk_types::Version;
use vecdb::Database; use vecdb::Database;
use super::Vecs; use super::{
vecs::{HashPriceValueVecs, HashRateSmaVecs},
Vecs,
};
use crate::{ use crate::{
indexes, indexes,
internal::{ComputedPerBlock, PercentPerBlock}, internal::{ComputedPerBlock, PercentPerBlock},
@@ -19,30 +22,32 @@ impl Vecs {
Ok(Self { Ok(Self {
hash_rate: ComputedPerBlock::forced_import(db, "hash_rate", version + v5, indexes)?, hash_rate: ComputedPerBlock::forced_import(db, "hash_rate", version + v5, indexes)?,
hash_rate_sma_1w: ComputedPerBlock::forced_import( hash_rate_sma: HashRateSmaVecs {
_1w: ComputedPerBlock::forced_import(
db, db,
"hash_rate_sma_1w", "hash_rate_sma_1w",
version, version,
indexes, indexes,
)?, )?,
hash_rate_sma_1m: ComputedPerBlock::forced_import( _1m: ComputedPerBlock::forced_import(
db, db,
"hash_rate_sma_1m", "hash_rate_sma_1m",
version, version,
indexes, indexes,
)?, )?,
hash_rate_sma_2m: ComputedPerBlock::forced_import( _2m: ComputedPerBlock::forced_import(
db, db,
"hash_rate_sma_2m", "hash_rate_sma_2m",
version, version,
indexes, indexes,
)?, )?,
hash_rate_sma_1y: ComputedPerBlock::forced_import( _1y: ComputedPerBlock::forced_import(
db, db,
"hash_rate_sma_1y", "hash_rate_sma_1y",
version, version,
indexes, indexes,
)?, )?,
},
hash_rate_ath: ComputedPerBlock::forced_import( hash_rate_ath: ComputedPerBlock::forced_import(
db, db,
"hash_rate_ath", "hash_rate_ath",
@@ -55,66 +60,70 @@ impl Vecs {
version, version,
indexes, indexes,
)?, )?,
hash_price_ths: ComputedPerBlock::forced_import( hash_price: HashPriceValueVecs {
ths: ComputedPerBlock::forced_import(
db, db,
"hash_price_ths", "hash_price_ths",
version + v4, version + v4,
indexes, indexes,
)?, )?,
hash_price_ths_min: ComputedPerBlock::forced_import( ths_min: ComputedPerBlock::forced_import(
db, db,
"hash_price_ths_min", "hash_price_ths_min",
version + v4, version + v4,
indexes, indexes,
)?, )?,
hash_price_phs: ComputedPerBlock::forced_import( phs: ComputedPerBlock::forced_import(
db, db,
"hash_price_phs", "hash_price_phs",
version + v4, version + v4,
indexes, indexes,
)?, )?,
hash_price_phs_min: ComputedPerBlock::forced_import( phs_min: ComputedPerBlock::forced_import(
db, db,
"hash_price_phs_min", "hash_price_phs_min",
version + v4, version + v4,
indexes, indexes,
)?, )?,
hash_price_rebound: PercentPerBlock::forced_import( rebound: PercentPerBlock::forced_import(
db, db,
"hash_price_rebound", "hash_price_rebound",
version + v4, version + v4,
indexes, indexes,
)?, )?,
hash_value_ths: ComputedPerBlock::forced_import( },
hash_value: HashPriceValueVecs {
ths: ComputedPerBlock::forced_import(
db, db,
"hash_value_ths", "hash_value_ths",
version + v4, version + v4,
indexes, indexes,
)?, )?,
hash_value_ths_min: ComputedPerBlock::forced_import( ths_min: ComputedPerBlock::forced_import(
db, db,
"hash_value_ths_min", "hash_value_ths_min",
version + v4, version + v4,
indexes, indexes,
)?, )?,
hash_value_phs: ComputedPerBlock::forced_import( phs: ComputedPerBlock::forced_import(
db, db,
"hash_value_phs", "hash_value_phs",
version + v4, version + v4,
indexes, indexes,
)?, )?,
hash_value_phs_min: ComputedPerBlock::forced_import( phs_min: ComputedPerBlock::forced_import(
db, db,
"hash_value_phs_min", "hash_value_phs_min",
version + v4, version + v4,
indexes, indexes,
)?, )?,
hash_value_rebound: PercentPerBlock::forced_import( rebound: PercentPerBlock::forced_import(
db, db,
"hash_value_rebound", "hash_value_rebound",
version + v4, version + v4,
indexes, indexes,
)?, )?,
},
}) })
} }
} }

View File

@@ -4,23 +4,29 @@ use vecdb::{Rw, StorageMode};
use crate::internal::{ComputedPerBlock, PercentPerBlock}; use crate::internal::{ComputedPerBlock, PercentPerBlock};
#[derive(Traversable)]
pub struct HashRateSmaVecs<M: StorageMode = Rw> {
pub _1w: ComputedPerBlock<StoredF64, M>,
pub _1m: ComputedPerBlock<StoredF64, M>,
pub _2m: ComputedPerBlock<StoredF64, M>,
pub _1y: ComputedPerBlock<StoredF64, M>,
}
#[derive(Traversable)]
pub struct HashPriceValueVecs<M: StorageMode = Rw> {
pub ths: ComputedPerBlock<StoredF32, M>,
pub ths_min: ComputedPerBlock<StoredF32, M>,
pub phs: ComputedPerBlock<StoredF32, M>,
pub phs_min: ComputedPerBlock<StoredF32, M>,
pub rebound: PercentPerBlock<BasisPointsSigned32, M>,
}
#[derive(Traversable)] #[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw> { pub struct Vecs<M: StorageMode = Rw> {
pub hash_rate: ComputedPerBlock<StoredF64, M>, pub hash_rate: ComputedPerBlock<StoredF64, M>,
pub hash_rate_sma_1w: ComputedPerBlock<StoredF64, M>, pub hash_rate_sma: HashRateSmaVecs<M>,
pub hash_rate_sma_1m: ComputedPerBlock<StoredF64, M>,
pub hash_rate_sma_2m: ComputedPerBlock<StoredF64, M>,
pub hash_rate_sma_1y: ComputedPerBlock<StoredF64, M>,
pub hash_rate_ath: ComputedPerBlock<StoredF64, M>, pub hash_rate_ath: ComputedPerBlock<StoredF64, M>,
pub hash_rate_drawdown: PercentPerBlock<BasisPointsSigned16, M>, pub hash_rate_drawdown: PercentPerBlock<BasisPointsSigned16, M>,
pub hash_price_ths: ComputedPerBlock<StoredF32, M>, pub hash_price: HashPriceValueVecs<M>,
pub hash_price_ths_min: ComputedPerBlock<StoredF32, M>, pub hash_value: HashPriceValueVecs<M>,
pub hash_price_phs: ComputedPerBlock<StoredF32, M>,
pub hash_price_phs_min: ComputedPerBlock<StoredF32, M>,
pub hash_price_rebound: PercentPerBlock<BasisPointsSigned32, M>,
pub hash_value_ths: ComputedPerBlock<StoredF32, M>,
pub hash_value_ths_min: ComputedPerBlock<StoredF32, M>,
pub hash_value_phs: ComputedPerBlock<StoredF32, M>,
pub hash_value_phs_min: ComputedPerBlock<StoredF32, M>,
pub hash_value_rebound: PercentPerBlock<BasisPointsSigned32, M>,
} }

View File

@@ -61,10 +61,10 @@ impl Vecs {
// 24h, 1w, 1y from extended; 1m from core delta // 24h, 1w, 1y from extended; 1m from core delta
let rcr_rates = [ let rcr_rates = [
&all_realized.cap_delta_extended.rate_24h.bps.height, &all_realized.cap_delta_extended.rate._24h.bps.height,
&all_realized.cap_delta_extended.rate_1w.bps.height, &all_realized.cap_delta_extended.rate._1w.bps.height,
&all_realized.cap_delta.rate_1m.bps.height, &all_realized.cap_delta.rate_1m.bps.height,
&all_realized.cap_delta_extended.rate_1y.bps.height, &all_realized.cap_delta_extended.rate._1y.bps.height,
]; ];
for i in 0..4 { for i in 0..4 {

View File

@@ -1,6 +1,6 @@
#![doc = include_str!("../README.md")] #![doc = include_str!("../README.md")]
use std::{io, path::PathBuf, result, time}; use std::{fmt, io, path::PathBuf, result, time};
use thiserror::Error; use thiserror::Error;
@@ -127,13 +127,10 @@ pub enum Error {
AuthFailed, AuthFailed,
// Metric-specific errors // Metric-specific errors
#[error("'{metric}' not found{}", suggestion.as_ref().map(|s| format!(", did you mean '{s}'?")).unwrap_or_default())] #[error("{0}")]
MetricNotFound { MetricNotFound(MetricNotFound),
metric: String,
suggestion: Option<String>,
},
#[error("'{metric}' doesn't support the requested index. Supported indexes: {supported}")] #[error("'{metric}' doesn't support the requested index. Try: {supported}")]
MetricUnsupportedIndex { metric: String, supported: String }, MetricUnsupportedIndex { metric: String, supported: String },
#[error("No metrics specified")] #[error("No metrics specified")]
@@ -226,3 +223,46 @@ fn is_io_error_permanent(e: &std::io::Error) -> bool {
} }
} }
} }
#[derive(Debug)]
pub struct MetricNotFound {
pub metric: String,
pub suggestions: Vec<String>,
pub total_matches: usize,
}
impl MetricNotFound {
pub fn new(metric: String, all_matches: Vec<String>) -> Self {
let total_matches = all_matches.len();
let suggestions = all_matches.into_iter().take(3).collect();
Self {
metric,
suggestions,
total_matches,
}
}
}
impl fmt::Display for MetricNotFound {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "'{}' not found", self.metric)?;
if self.suggestions.is_empty() {
return Ok(());
}
let quoted: Vec<_> = self.suggestions.iter().map(|s| format!("'{s}'")).collect();
write!(f, ", did you mean {}?", quoted.join(", "))?;
let remaining = self.total_matches.saturating_sub(self.suggestions.len());
if remaining > 0 {
write!(
f,
" ({remaining} more — /api/metrics/search/{} for all)",
self.metric
)?;
}
Ok(())
}
}

View File

@@ -39,7 +39,7 @@ impl Query {
} }
pub fn blocks(&self, start_height: Option<Height>) -> Result<Vec<BlockInfo>> { pub fn blocks(&self, start_height: Option<Height>) -> Result<Vec<BlockInfo>> {
let max_height = self.height(); let max_height = self.indexed_height();
let start = start_height.unwrap_or(max_height); let start = start_height.unwrap_or(max_height);
let start = start.min(max_height); let start = start.min(max_height);

View File

@@ -10,7 +10,7 @@ impl Query {
let indexer = self.indexer(); let indexer = self.indexer();
let computer = self.computer(); let computer = self.computer();
let max_height = self.height(); let max_height = self.indexed_height();
let max_height_usize: usize = max_height.into(); let max_height_usize: usize = max_height.into();
if max_height_usize == 0 { if max_height_usize == 0 {

View File

@@ -26,7 +26,7 @@ impl Query {
fn block_txids_by_height(&self, height: Height) -> Result<Vec<Txid>> { fn block_txids_by_height(&self, height: Height) -> Result<Vec<Txid>> {
let indexer = self.indexer(); let indexer = self.indexer();
let max_height = self.height(); let max_height = self.indexed_height();
if height > max_height { if height > max_height {
return Err(Error::OutOfRange("Block height out of range".into())); return Err(Error::OutOfRange("Block height out of range".into()));
} }
@@ -55,7 +55,7 @@ impl Query {
fn block_txs_by_height(&self, height: Height, start_index: usize) -> Result<Vec<Transaction>> { fn block_txs_by_height(&self, height: Height, start_index: usize) -> Result<Vec<Transaction>> {
let indexer = self.indexer(); let indexer = self.indexer();
let max_height = self.height(); let max_height = self.indexed_height();
if height > max_height { if height > max_height {
return Err(Error::OutOfRange("Block height out of range".into())); return Err(Error::OutOfRange("Block height out of range".into()));
} }
@@ -97,7 +97,7 @@ impl Query {
fn block_txid_at_index_by_height(&self, height: Height, index: usize) -> Result<Txid> { fn block_txid_at_index_by_height(&self, height: Height, index: usize) -> Result<Txid> {
let indexer = self.indexer(); let indexer = self.indexer();
let max_height = self.height(); let max_height = self.indexed_height();
if height > max_height { if height > max_height {
return Err(Error::OutOfRange("Block height out of range".into())); return Err(Error::OutOfRange("Block height out of range".into()));
} }

View File

@@ -37,7 +37,10 @@ impl Query {
.join(format!("utxo_{cohort}_cost_basis/by_date")); .join(format!("utxo_{cohort}_cost_basis/by_date"));
if !dir.exists() { if !dir.exists() {
return Err(Error::NotFound(format!("Unknown cohort '{cohort}'"))); let valid = self.cost_basis_cohorts().unwrap_or_default().join(", ");
return Err(Error::NotFound(format!(
"Unknown cohort '{cohort}'. Available: {valid}"
)));
} }
Ok(dir) Ok(dir)

View File

@@ -27,21 +27,24 @@ impl Query {
pub fn metric_not_found_error(&self, metric: &Metric) -> Error { pub fn metric_not_found_error(&self, metric: &Metric) -> Error {
// Check if metric exists but with different indexes // Check if metric exists but with different indexes
if let Some(indexes) = self.vecs().metric_to_indexes(metric.clone()) { if let Some(indexes) = self.vecs().metric_to_indexes(metric.clone()) {
let index_list: Vec<_> = indexes.iter().map(|i| i.to_string()).collect(); let supported = indexes
.iter()
.map(|i| format!("/api/metric/{metric}/{i}"))
.collect::<Vec<_>>()
.join(", ");
return Error::MetricUnsupportedIndex { return Error::MetricUnsupportedIndex {
metric: metric.to_string(), metric: metric.to_string(),
supported: index_list.join(", "), supported,
}; };
} }
// Metric doesn't exist, suggest alternatives // Metric doesn't exist, suggest alternatives
Error::MetricNotFound { let matches = self
metric: metric.to_string(), .match_metric(metric, Limit::DEFAULT)
suggestion: self .into_iter()
.match_metric(metric, Limit::MIN) .map(|s| s.to_string())
.first() .collect();
.map(|s| s.to_string()), Error::MetricNotFound(brk_error::MetricNotFound::new(metric.to_string(), matches))
}
} }
pub(crate) fn columns_to_csv( pub(crate) fn columns_to_csv(
@@ -334,4 +337,9 @@ impl ResolvedQuery {
pub fn format(&self) -> Format { pub fn format(&self) -> Format {
self.format self.format
} }
pub fn csv_filename(&self) -> String {
let names: Vec<_> = self.vecs.iter().map(|v| v.name()).collect();
format!("{}-{}.csv", names.join("_"), self.index)
}
} }

Some files were not shown because too many files have changed in this diff Show More