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(
starting_indexes.height,
&self.coinblocks_created.height,
&all_metrics.activity.coinblocks_destroyed.height,
&all_metrics.activity.coinblocks_destroyed.raw.height,
exit,
)?;
Ok(())
@@ -42,7 +42,7 @@ impl Vecs {
self.liveliness.height.compute_divide(
starting_indexes.height,
&all_metrics.activity.coinblocks_destroyed_cumulative.height,
&all_metrics.activity.coinblocks_destroyed.cumulative.height,
&self.coinblocks_created.cumulative.height,
exit,
)?;

View File

@@ -18,7 +18,7 @@ impl Vecs {
exit: &Exit,
) -> Result<()> {
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;
self.thermo_cap.cents.height.compute_transform(

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,84 @@
use brk_cohort::{Filter, Filtered};
use brk_error::Result;
use brk_types::{Cents, Height, Indexes, Version};
use vecdb::{Exit, ReadableVec};
use crate::{blocks, distribution::{cohorts::traits::DynCohortVecs, metrics::CoreCohortMetrics}, prices};
use super::UTXOCohortVecs;
impl Filtered for UTXOCohortVecs<CoreCohortMetrics> {
fn filter(&self) -> &Filter {
&self.metrics.filter
}
}
impl DynCohortVecs for UTXOCohortVecs<CoreCohortMetrics> {
fn min_stateful_height_len(&self) -> usize {
self.metrics.min_stateful_height_len()
}
fn reset_state_starting_height(&mut self) {
self.reset_state_impl();
}
impl_import_state!();
fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> {
self.metrics.validate_computed_versions(base_version)
}
fn truncate_push(&mut self, height: Height) -> Result<()> {
if self.state_starting_height.is_some_and(|h| h > height) {
return Ok(());
}
if let Some(state) = self.state.as_ref() {
self.metrics.supply.truncate_push(height, state)?;
self.metrics.outputs.truncate_push(height, state)?;
self.metrics.activity.truncate_push(height, state)?;
self.metrics.realized.truncate_push(height, state)?;
}
Ok(())
}
fn compute_then_truncate_push_unrealized_states(
&mut self,
height: Height,
height_price: Cents,
_is_day_boundary: bool,
) -> Result<()> {
if let Some(state) = self.state.as_mut() {
state.apply_pending();
let unrealized_state = state.compute_unrealized_state(height_price);
self.metrics
.unrealized
.truncate_push(height, &unrealized_state)?;
}
Ok(())
}
fn compute_rest_part1(
&mut self,
blocks: &blocks::Vecs,
prices: &prices::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
self.metrics
.compute_rest_part1(blocks, prices, starting_indexes, exit)
}
fn write_state(&mut self, height: Height, cleanup: bool) -> Result<()> {
self.write_state_impl(height, cleanup)
}
fn reset_cost_basis_data_if_needed(&mut self) -> Result<()> {
self.reset_cost_basis_impl()
}
fn reset_single_iteration_values(&mut self) {
self.reset_iteration_impl();
}
}

View File

@@ -0,0 +1,80 @@
use brk_cohort::{Filter, Filtered};
use brk_error::Result;
use brk_types::{Cents, Height, Indexes, Version};
use vecdb::{Exit, ReadableVec};
use crate::{
blocks,
distribution::{cohorts::traits::DynCohortVecs, metrics::MinimalCohortMetrics},
prices,
};
use super::UTXOCohortVecs;
impl Filtered for UTXOCohortVecs<MinimalCohortMetrics> {
fn filter(&self) -> &Filter {
&self.metrics.filter
}
}
impl DynCohortVecs for UTXOCohortVecs<MinimalCohortMetrics> {
fn min_stateful_height_len(&self) -> usize {
self.metrics.min_stateful_height_len()
}
fn reset_state_starting_height(&mut self) {
self.reset_state_impl();
}
impl_import_state!();
fn validate_computed_versions(&mut self, _base_version: Version) -> Result<()> {
Ok(())
}
fn truncate_push(&mut self, height: Height) -> Result<()> {
if self.state_starting_height.is_some_and(|h| h > height) {
return Ok(());
}
if let Some(state) = self.state.as_ref() {
self.metrics.supply.truncate_push(height, state)?;
self.metrics.outputs.truncate_push(height, state)?;
self.metrics.realized.truncate_push(height, state)?;
}
Ok(())
}
fn compute_then_truncate_push_unrealized_states(
&mut self,
_height: Height,
_height_price: Cents,
_is_day_boundary: bool,
) -> Result<()> {
Ok(())
}
fn compute_rest_part1(
&mut self,
blocks: &blocks::Vecs,
prices: &prices::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
self.metrics
.compute_rest_part1(blocks, prices, starting_indexes, exit)
}
fn write_state(&mut self, height: Height, cleanup: bool) -> Result<()> {
self.write_state_impl(height, cleanup)
}
fn reset_cost_basis_data_if_needed(&mut self) -> Result<()> {
self.reset_cost_basis_impl()
}
fn reset_single_iteration_values(&mut self) {
self.reset_iteration_impl();
}
}

View File

@@ -1,17 +1,63 @@
macro_rules! impl_import_state {
() => {
fn import_state(&mut self, starting_height: Height) -> Result<Height> {
if let Some(state) = self.state.as_mut() {
if let Some(mut prev_height) = starting_height.decremented() {
prev_height = state.import_at_or_before(prev_height)?;
state.supply.value = self
.metrics
.supply
.total
.sats
.height
.collect_one(prev_height)
.unwrap();
state.supply.utxo_count = *self
.metrics
.outputs
.utxo_count
.height
.collect_one(prev_height)
.unwrap();
state.restore_realized_cap();
let result = prev_height.incremented();
self.state_starting_height = Some(result);
Ok(result)
} else {
self.state_starting_height = Some(Height::ZERO);
Ok(Height::ZERO)
}
} else {
self.state_starting_height = Some(starting_height);
Ok(starting_height)
}
}
};
}
mod core;
mod minimal;
mod r#type;
use brk_cohort::{Filter, Filtered};
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Cents, Height, Indexes, Version};
use vecdb::{Exit, ReadableVec};
use crate::{blocks, distribution::state::UTXOCohortState, prices};
use crate::distribution::metrics::{
CohortMetricsBase, CohortMetricsState, CoreCohortMetrics, MinimalCohortMetrics,
use crate::{
blocks,
distribution::{
cohorts::traits::DynCohortVecs,
metrics::{CohortMetricsBase, CohortMetricsState},
state::UTXOCohortState,
},
prices,
};
use super::super::traits::DynCohortVecs;
#[derive(Traversable)]
pub struct UTXOCohortVecs<M: CohortMetricsState> {
#[traversable(skip)]
@@ -24,8 +70,6 @@ pub struct UTXOCohortVecs<M: CohortMetricsState> {
pub metrics: M,
}
// --- Shared state helpers (identical across all DynCohortVecs impls) ---
impl<M: CohortMetricsState> UTXOCohortVecs<M> {
pub(crate) fn new(state: Option<Box<UTXOCohortState<M::Realized>>>, metrics: M) -> Self {
Self {
@@ -172,213 +216,3 @@ impl<M: CohortMetricsBase + Traversable> DynCohortVecs for UTXOCohortVecs<M> {
self.reset_iteration_impl();
}
}
// --- Shared import_state for non-blanket impls (direct field access) ---
macro_rules! impl_import_state {
() => {
fn import_state(&mut self, starting_height: Height) -> Result<Height> {
if let Some(state) = self.state.as_mut() {
if let Some(mut prev_height) = starting_height.decremented() {
prev_height = state.import_at_or_before(prev_height)?;
state.supply.value = self
.metrics
.supply
.total
.sats
.height
.collect_one(prev_height)
.unwrap();
state.supply.utxo_count = *self
.metrics
.outputs
.utxo_count
.height
.collect_one(prev_height)
.unwrap();
state.restore_realized_cap();
let result = prev_height.incremented();
self.state_starting_height = Some(result);
Ok(result)
} else {
self.state_starting_height = Some(Height::ZERO);
Ok(Height::ZERO)
}
} else {
self.state_starting_height = Some(starting_height);
Ok(starting_height)
}
}
};
}
// --- MinimalCohortMetrics: uses MinimalRealizedState ---
impl Filtered for UTXOCohortVecs<MinimalCohortMetrics> {
fn filter(&self) -> &Filter {
&self.metrics.filter
}
}
impl DynCohortVecs for UTXOCohortVecs<MinimalCohortMetrics> {
fn min_stateful_height_len(&self) -> usize {
self.metrics.min_stateful_height_len()
}
fn reset_state_starting_height(&mut self) {
self.reset_state_impl();
}
impl_import_state!();
fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> {
self.metrics.validate_computed_versions(base_version)
}
fn truncate_push(&mut self, height: Height) -> Result<()> {
if self.state_starting_height.is_some_and(|h| h > height) {
return Ok(());
}
if let Some(state) = self.state.as_ref() {
self.metrics
.supply
.truncate_push(height, state.supply.value)?;
self.metrics
.outputs
.truncate_push(height, state.supply.utxo_count)?;
self.metrics.activity.truncate_push(height, state.sent)?;
self.metrics
.realized
.truncate_push(height, &state.realized)?;
}
Ok(())
}
fn compute_then_truncate_push_unrealized_states(
&mut self,
height: Height,
height_price: Cents,
_is_day_boundary: bool,
) -> Result<()> {
if let Some(state) = self.state.as_mut() {
state.apply_pending();
let unrealized_state = state.compute_unrealized_state(height_price);
self.metrics
.unrealized
.truncate_push(height, &unrealized_state)?;
}
Ok(())
}
fn compute_rest_part1(
&mut self,
blocks: &blocks::Vecs,
prices: &prices::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
self.metrics
.compute_rest_part1(blocks, prices, starting_indexes, exit)
}
fn write_state(&mut self, height: Height, cleanup: bool) -> Result<()> {
self.write_state_impl(height, cleanup)
}
fn reset_cost_basis_data_if_needed(&mut self) -> Result<()> {
self.reset_cost_basis_impl()
}
fn reset_single_iteration_values(&mut self) {
self.reset_iteration_impl();
}
}
// --- CoreCohortMetrics: uses CoreRealizedState ---
impl Filtered for UTXOCohortVecs<CoreCohortMetrics> {
fn filter(&self) -> &Filter {
&self.metrics.filter
}
}
impl DynCohortVecs for UTXOCohortVecs<CoreCohortMetrics> {
fn min_stateful_height_len(&self) -> usize {
self.metrics.min_stateful_height_len()
}
fn reset_state_starting_height(&mut self) {
self.reset_state_impl();
}
impl_import_state!();
fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> {
self.metrics.validate_computed_versions(base_version)
}
fn truncate_push(&mut self, height: Height) -> Result<()> {
if self.state_starting_height.is_some_and(|h| h > height) {
return Ok(());
}
if let Some(state) = self.state.as_ref() {
self.metrics
.supply
.truncate_push(height, state.supply.value)?;
self.metrics
.outputs
.truncate_push(height, state.supply.utxo_count)?;
self.metrics.activity.truncate_push(height, state.sent)?;
self.metrics
.realized
.truncate_push(height, &state.realized)?;
}
Ok(())
}
fn compute_then_truncate_push_unrealized_states(
&mut self,
height: Height,
height_price: Cents,
_is_day_boundary: bool,
) -> Result<()> {
if let Some(state) = self.state.as_mut() {
state.apply_pending();
let unrealized_state = state.compute_unrealized_state(height_price);
self.metrics
.unrealized
.truncate_push(height, &unrealized_state)?;
}
Ok(())
}
fn compute_rest_part1(
&mut self,
blocks: &blocks::Vecs,
prices: &prices::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
self.metrics
.compute_rest_part1(blocks, prices, starting_indexes, exit)
}
fn write_state(&mut self, height: Height, cleanup: bool) -> Result<()> {
self.write_state_impl(height, cleanup)
}
fn reset_cost_basis_data_if_needed(&mut self) -> Result<()> {
self.reset_cost_basis_impl()
}
fn reset_single_iteration_values(&mut self) {
self.reset_iteration_impl();
}
}

View File

@@ -0,0 +1,83 @@
use brk_cohort::{Filter, Filtered};
use brk_error::Result;
use brk_types::{Cents, Height, Indexes, Version};
use vecdb::{Exit, ReadableVec};
use crate::{blocks, distribution::cohorts::traits::DynCohortVecs, distribution::metrics::TypeCohortMetrics, prices};
use super::UTXOCohortVecs;
impl Filtered for UTXOCohortVecs<TypeCohortMetrics> {
fn filter(&self) -> &Filter {
&self.metrics.filter
}
}
impl DynCohortVecs for UTXOCohortVecs<TypeCohortMetrics> {
fn min_stateful_height_len(&self) -> usize {
self.metrics.min_stateful_height_len()
}
fn reset_state_starting_height(&mut self) {
self.reset_state_impl();
}
impl_import_state!();
fn validate_computed_versions(&mut self, _base_version: Version) -> Result<()> {
Ok(())
}
fn truncate_push(&mut self, height: Height) -> Result<()> {
if self.state_starting_height.is_some_and(|h| h > height) {
return Ok(());
}
if let Some(state) = self.state.as_ref() {
self.metrics.supply.truncate_push(height, state)?;
self.metrics.outputs.truncate_push(height, state)?;
self.metrics.realized.truncate_push(height, state)?;
}
Ok(())
}
fn compute_then_truncate_push_unrealized_states(
&mut self,
height: Height,
height_price: Cents,
_is_day_boundary: bool,
) -> Result<()> {
if let Some(state) = self.state.as_mut() {
state.apply_pending();
let unrealized_state = state.compute_unrealized_state(height_price);
self.metrics
.unrealized
.truncate_push(height, &unrealized_state)?;
}
Ok(())
}
fn compute_rest_part1(
&mut self,
blocks: &blocks::Vecs,
prices: &prices::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
self.metrics
.compute_rest_part1(blocks, prices, starting_indexes, exit)
}
fn write_state(&mut self, height: Height, cleanup: bool) -> Result<()> {
self.write_state_impl(height, cleanup)
}
fn reset_cost_basis_data_if_needed(&mut self) -> Result<()> {
self.reset_cost_basis_impl()
}
fn reset_single_iteration_values(&mut self) {
self.reset_iteration_impl();
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -14,7 +14,7 @@ pub struct Vecs<M: StorageMode = Rw> {
impl Vecs {
pub(crate) fn forced_import(db: &Database, version: Version) -> Result<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)?,
height_count: EagerVec::forced_import(db, "height_count", version)?,
})

View File

@@ -13,7 +13,7 @@ pub struct Vecs<M: StorageMode = Rw> {
impl Vecs {
pub(crate) fn forced_import(db: &Database, version: Version) -> Result<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)?,
})
}

View File

@@ -39,7 +39,7 @@ impl Vecs {
hour12: EagerVec::forced_import(db, "hour12", version)?,
day1: EagerVec::forced_import(db, "day1", 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)?,
week1: EagerVec::forced_import(db, "week1", version)?,
month1: EagerVec::forced_import(db, "month1", version)?,

View File

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

View File

@@ -1,7 +1,13 @@
mod distribution_stats;
mod per_resolution;
mod window_24h;
mod windows;
mod windows_except_1m;
mod windows_from_1w;
pub use distribution_stats::*;
pub use per_resolution::*;
pub use window_24h::*;
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_sum;
mod windows;
mod with_sum_24h;
pub use base::*;
pub use cumulative::*;
@@ -19,3 +20,4 @@ pub use rolling::*;
pub use rolling_full::*;
pub use rolling_sum::*;
pub use windows::*;
pub use with_sum_24h::*;

View File

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

View File

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

View File

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

View File

@@ -90,12 +90,7 @@ where
break;
}
let target = S1I::max_from(I::from(i), source_len);
if cursor.position() <= target {
cursor.advance(target - cursor.position());
if let Some(v) = cursor.next() {
f(v);
}
} else if let Some(v) = source.collect_one_at(target) {
if let Some(v) = cursor.get(target) {
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 lazy;
mod with_sum_24h;
pub use base::*;
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::{
indexes,
internal::{Bp32ToFloat, ComputedPerBlock, LazyPerBlock},
internal::{BpsType, ComputedPerBlock, LazyPerBlock},
};
#[derive(Traversable)]
pub struct RatioPerBlock<M: StorageMode = Rw> {
pub bps: ComputedPerBlock<BasisPoints32, M>,
pub ratio: LazyPerBlock<StoredF32, BasisPoints32>,
pub struct RatioPerBlock<B: BpsType = BasisPoints32, M: StorageMode = Rw> {
pub bps: ComputedPerBlock<B, M>,
pub ratio: LazyPerBlock<StoredF32, B>,
}
const VERSION: Version = Version::TWO;
impl RatioPerBlock {
impl<B: BpsType> RatioPerBlock<B> {
pub(crate) fn forced_import(
db: &Database,
name: &str,
@@ -36,7 +36,7 @@ impl RatioPerBlock {
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,
v,
bps.height.read_only_boxed_clone(),
@@ -45,7 +45,9 @@ impl RatioPerBlock {
Ok(Self { bps, ratio })
}
}
impl RatioPerBlock<BasisPoints32> {
pub(crate) fn compute_ratio(
&mut self,
starting_indexes: &Indexes,

View File

@@ -1,6 +1,6 @@
use brk_error::Result;
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 vecdb::{Database, Exit, ReadableVec, Rw, StorageMode};
@@ -13,7 +13,7 @@ pub struct RatioPerBlockExtended<M: StorageMode = Rw> {
#[deref]
#[deref_mut]
#[traversable(flatten)]
pub base: RatioPerBlock<M>,
pub base: RatioPerBlock<BasisPoints32, M>,
#[traversable(flatten)]
pub percentiles: RatioPerBlockPercentiles<M>,
}

View File

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

View File

@@ -1,6 +1,6 @@
use brk_error::Result;
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 vecdb::{Database, EagerVec, Exit, PcoVec, Rw, StorageMode};
@@ -14,7 +14,7 @@ pub struct PriceWithRatioPerBlock<M: StorageMode = Rw> {
#[deref]
#[deref_mut]
#[traversable(flatten)]
pub inner: RatioPerBlock<M>,
pub inner: RatioPerBlock<BasisPoints32, M>,
pub price: Price<ComputedPerBlock<Cents, M>>,
}

View File

@@ -7,10 +7,10 @@ use crate::{blocks, indexes, internal::StdDevPerBlockExtended};
#[derive(Traversable)]
pub struct RatioPerBlockStdDevBands<M: StorageMode = Rw> {
pub ratio_sd: StdDevPerBlockExtended<M>,
pub ratio_sd_4y: StdDevPerBlockExtended<M>,
pub ratio_sd_2y: StdDevPerBlockExtended<M>,
pub ratio_sd_1y: StdDevPerBlockExtended<M>,
pub all: StdDevPerBlockExtended<M>,
pub _4y: StdDevPerBlockExtended<M>,
pub _2y: StdDevPerBlockExtended<M>,
pub _1y: StdDevPerBlockExtended<M>,
}
const VERSION: Version = Version::new(4);
@@ -38,10 +38,10 @@ impl RatioPerBlockStdDevBands {
}
Ok(Self {
ratio_sd: import_sd!("ratio", "", usize::MAX),
ratio_sd_1y: import_sd!("ratio", "1y", 365),
ratio_sd_2y: import_sd!("ratio", "2y", 2 * 365),
ratio_sd_4y: import_sd!("ratio", "4y", 4 * 365),
all: import_sd!("ratio", "", usize::MAX),
_1y: import_sd!("ratio", "1y", 365),
_2y: import_sd!("ratio", "2y", 2 * 365),
_4y: import_sd!("ratio", "4y", 4 * 365),
})
}
@@ -54,10 +54,10 @@ impl RatioPerBlockStdDevBands {
metric_price: &impl ReadableVec<Height, Cents>,
) -> Result<()> {
for sd in [
&mut self.ratio_sd,
&mut self.ratio_sd_4y,
&mut self.ratio_sd_2y,
&mut self.ratio_sd_1y,
&mut self.all,
&mut self._4y,
&mut self._2y,
&mut self._1y,
] {
sd.compute_all(blocks, starting_indexes, exit, ratio_source)?;
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::{
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).
@@ -61,16 +61,16 @@ where
}
}
/// Single 24h rolling window (1 stored vec).
#[derive(Traversable)]
pub struct RollingWindow24h<T, M: StorageMode = Rw>
/// Single 24h rolling window backed by ComputedPerBlock (1 stored vec).
#[derive(Deref, DerefMut, Traversable)]
#[traversable(transparent)]
pub struct RollingWindow24hPerBlock<T, M: StorageMode = Rw>(
pub RollingWindow24h<ComputedPerBlock<T, M>>,
)
where
T: ComputedVecValue + PartialOrd + JsonSchema,
{
pub _24h: ComputedPerBlock<T, M>,
}
T: ComputedVecValue + PartialOrd + JsonSchema;
impl<T> RollingWindow24h<T>
impl<T> RollingWindow24hPerBlock<T>
where
T: NumericValue + JsonSchema,
{
@@ -80,14 +80,14 @@ where
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
Ok(Self {
Ok(Self(RollingWindow24h {
_24h: ComputedPerBlock::forced_import(
db,
&format!("{name}_24h"),
version,
indexes,
)?,
})
}))
}
pub(crate) fn compute_rolling_sum(
@@ -108,15 +108,11 @@ where
}
/// Extended rolling windows: 1w + 1m + 1y (3 stored vecs).
#[derive(Traversable)]
pub struct RollingWindowsFrom1w<T, M: StorageMode = Rw>
#[derive(Deref, DerefMut, Traversable)]
#[traversable(transparent)]
pub struct RollingWindowsFrom1w<T, M: StorageMode = Rw>(pub WindowsFrom1w<ComputedPerBlock<T, M>>)
where
T: ComputedVecValue + PartialOrd + JsonSchema,
{
pub _1w: ComputedPerBlock<T, M>,
pub _1m: ComputedPerBlock<T, M>,
pub _1y: ComputedPerBlock<T, M>,
}
T: ComputedVecValue + PartialOrd + JsonSchema;
impl<T> RollingWindowsFrom1w<T>
where
@@ -128,34 +124,9 @@ where
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
Ok(Self {
_1w: ComputedPerBlock::forced_import(
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]
Ok(Self(WindowsFrom1w::try_from_fn(|suffix| {
ComputedPerBlock::forced_import(db, &format!("{name}_{suffix}"), version, indexes)
})?))
}
pub(crate) fn compute_rolling_sum(
@@ -168,15 +139,11 @@ where
where
T: Default + SubAssign,
{
self._1w
.height
.compute_rolling_sum(max_from, windows._1w, source, exit)?;
self._1m
.height
.compute_rolling_sum(max_from, windows._1m, source, exit)?;
self._1y
.height
.compute_rolling_sum(max_from, windows._1y, source, exit)?;
let starts = [windows._1w, windows._1m, windows._1y];
for (w, starts) in self.0.as_mut_array().into_iter().zip(starts) {
w.height
.compute_rolling_sum(max_from, starts, source, exit)?;
}
Ok(())
}
}

View File

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

View File

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

View File

@@ -18,35 +18,35 @@ impl Vecs {
) -> Result<Self> {
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)?;
let max_years_between_price_ath = DerivedResolutions::from_computed::<DaysToYears>(
let max_years_between = DerivedResolutions::from_computed::<DaysToYears>(
"max_years_between_price_ath",
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)?;
let years_since_price_ath = DerivedResolutions::from_computed::<DaysToYears>(
let years_since = DerivedResolutions::from_computed::<DaysToYears>(
"years_since_price_ath",
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 {
price_ath,
price_drawdown,
days_since_price_ath,
years_since_price_ath,
max_days_between_price_ath,
max_years_between_price_ath,
price,
drawdown,
days_since,
years_since,
max_days_between,
max_years_between,
})
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,44 +2,53 @@ use brk_traversable::Traversable;
use brk_types::Cents;
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)]
pub struct Vecs<M: StorageMode = Rw> {
pub price_sma_1w: PriceWithRatioPerBlock<M>,
pub price_sma_8d: PriceWithRatioPerBlock<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>>,
pub sma: SmaVecs<M>,
pub ema: EmaVecs<M>,
}

View File

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

View File

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

View File

@@ -3,17 +3,20 @@ use brk_types::{BasisPoints16, Cents, StoredF32};
use vecdb::{Rw, StorageMode};
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)]
pub struct Vecs<M: StorageMode = Rw> {
pub price_min_1w: Price<ComputedPerBlock<Cents, M>>,
pub price_max_1w: Price<ComputedPerBlock<Cents, M>>,
pub price_min_2w: Price<ComputedPerBlock<Cents, M>>,
pub price_max_2w: Price<ComputedPerBlock<Cents, M>>,
pub price_min_1m: Price<ComputedPerBlock<Cents, 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>,
pub min: PriceMinMaxVecs<M>,
pub max: PriceMinMaxVecs<M>,
pub true_range: ComputedPerBlock<StoredF32, M>,
pub true_range_sum_2w: ComputedPerBlock<StoredF32, M>,
pub 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;
for sd in [
&mut self.price_return_24h_sd_1w,
&mut self.price_return_24h_sd_1m,
&mut self.price_return_24h_sd_1y,
&mut self.price_return_24h_sd._1w,
&mut self.price_return_24h_sd._1m,
&mut self.price_return_24h_sd._1y,
] {
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 super::super::lookback::ByLookbackPeriod;
use super::Vecs;
use super::{vecs::PriceReturn24hSdVecs, Vecs};
use crate::{
indexes,
internal::{StdDevPerBlock, PercentPerBlock},
@@ -27,37 +27,37 @@ impl Vecs {
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,
"price_return_24h",
"1w",
7,
version + v1,
indexes,
)?;
let price_return_24h_sd_1m = StdDevPerBlock::forced_import(
)?,
_1m: StdDevPerBlock::forced_import(
db,
"price_return_24h",
"1m",
30,
version + v1,
indexes,
)?;
let price_return_24h_sd_1y = StdDevPerBlock::forced_import(
)?,
_1y: StdDevPerBlock::forced_import(
db,
"price_return_24h",
"1y",
365,
version + v1,
indexes,
)?;
)?,
};
Ok(Self {
price_return,
price_cagr,
price_return_24h_sd_1w,
price_return_24h_sd_1m,
price_return_24h_sd_1y,
price_return_24h_sd,
})
}
}

View File

@@ -3,19 +3,20 @@ use brk_types::BasisPointsSigned32;
use vecdb::{Rw, StorageMode};
use crate::{
internal::{StdDevPerBlock, PercentPerBlock},
internal::{PercentPerBlock, StdDevPerBlock},
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)]
pub struct Vecs<M: StorageMode = Rw> {
pub price_return: ByLookbackPeriod<PercentPerBlock<BasisPointsSigned32, M>>,
// CAGR (computed from returns, 2y+ only)
pub price_cagr: ByDcaCagr<PercentPerBlock<BasisPointsSigned32, 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>,
pub price_return_24h_sd: PriceReturn24hSdVecs<M>,
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,23 +4,29 @@ use vecdb::{Rw, StorageMode};
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)]
pub struct Vecs<M: StorageMode = Rw> {
pub hash_rate: ComputedPerBlock<StoredF64, M>,
pub hash_rate_sma_1w: ComputedPerBlock<StoredF64, 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_sma: HashRateSmaVecs<M>,
pub hash_rate_ath: ComputedPerBlock<StoredF64, M>,
pub hash_rate_drawdown: PercentPerBlock<BasisPointsSigned16, M>,
pub hash_price_ths: ComputedPerBlock<StoredF32, M>,
pub hash_price_ths_min: ComputedPerBlock<StoredF32, 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>,
pub hash_price: HashPriceValueVecs<M>,
pub hash_value: HashPriceValueVecs<M>,
}

View File

@@ -61,10 +61,10 @@ impl Vecs {
// 24h, 1w, 1y from extended; 1m from core delta
let rcr_rates = [
&all_realized.cap_delta_extended.rate_24h.bps.height,
&all_realized.cap_delta_extended.rate_1w.bps.height,
&all_realized.cap_delta_extended.rate._24h.bps.height,
&all_realized.cap_delta_extended.rate._1w.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 {

View File

@@ -1,6 +1,6 @@
#![doc = include_str!("../README.md")]
use std::{io, path::PathBuf, result, time};
use std::{fmt, io, path::PathBuf, result, time};
use thiserror::Error;
@@ -127,13 +127,10 @@ pub enum Error {
AuthFailed,
// Metric-specific errors
#[error("'{metric}' not found{}", suggestion.as_ref().map(|s| format!(", did you mean '{s}'?")).unwrap_or_default())]
MetricNotFound {
metric: String,
suggestion: Option<String>,
},
#[error("{0}")]
MetricNotFound(MetricNotFound),
#[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 },
#[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>> {
let max_height = self.height();
let max_height = self.indexed_height();
let start = start_height.unwrap_or(max_height);
let start = start.min(max_height);

View File

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

View File

@@ -26,7 +26,7 @@ impl Query {
fn block_txids_by_height(&self, height: Height) -> Result<Vec<Txid>> {
let indexer = self.indexer();
let max_height = self.height();
let max_height = self.indexed_height();
if height > max_height {
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>> {
let indexer = self.indexer();
let max_height = self.height();
let max_height = self.indexed_height();
if height > max_height {
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> {
let indexer = self.indexer();
let max_height = self.height();
let max_height = self.indexed_height();
if height > max_height {
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"));
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)

View File

@@ -27,21 +27,24 @@ impl Query {
pub fn metric_not_found_error(&self, metric: &Metric) -> Error {
// Check if metric exists but with different indexes
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 {
metric: metric.to_string(),
supported: index_list.join(", "),
supported,
};
}
// Metric doesn't exist, suggest alternatives
Error::MetricNotFound {
metric: metric.to_string(),
suggestion: self
.match_metric(metric, Limit::MIN)
.first()
.map(|s| s.to_string()),
}
let matches = self
.match_metric(metric, Limit::DEFAULT)
.into_iter()
.map(|s| s.to_string())
.collect();
Error::MetricNotFound(brk_error::MetricNotFound::new(metric.to_string(), matches))
}
pub(crate) fn columns_to_csv(
@@ -334,4 +337,9 @@ impl ResolvedQuery {
pub fn 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