mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-24 06:39:58 -07:00
computer: distribution: replace Option but distinct structs
This commit is contained in:
@@ -8,7 +8,8 @@ use crate::internal::{ComputedFromHeightSumCum, ConstantVecs, RollingWindows, Wi
|
||||
pub struct Vecs<M: StorageMode = Rw> {
|
||||
pub block_count_target: ConstantVecs<StoredU64>,
|
||||
pub block_count: ComputedFromHeightSumCum<StoredU32, M>,
|
||||
// Rolling window starts (height-indexed only, no date aggregation needed)
|
||||
pub block_count_sum: RollingWindows<StoredU32, M>,
|
||||
|
||||
pub height_24h_ago: M::Stored<EagerVec<PcoVec<Height, Height>>>,
|
||||
pub height_3d_ago: M::Stored<EagerVec<PcoVec<Height, Height>>>,
|
||||
pub height_1w_ago: M::Stored<EagerVec<PcoVec<Height, Height>>>,
|
||||
@@ -39,8 +40,6 @@ pub struct Vecs<M: StorageMode = Rw> {
|
||||
pub height_6y_ago: M::Stored<EagerVec<PcoVec<Height, Height>>>,
|
||||
pub height_8y_ago: M::Stored<EagerVec<PcoVec<Height, Height>>>,
|
||||
pub height_10y_ago: M::Stored<EagerVec<PcoVec<Height, Height>>>,
|
||||
// Rolling window block counts
|
||||
pub block_count_sum: RollingWindows<StoredU32, M>,
|
||||
}
|
||||
|
||||
impl Vecs {
|
||||
|
||||
@@ -7,7 +7,6 @@ use crate::internal::{ComputedFromHeightLast, ComputedHeightDerivedLast};
|
||||
/// Difficulty metrics: raw difficulty, derived stats, adjustment, and countdown
|
||||
#[derive(Traversable)]
|
||||
pub struct Vecs<M: StorageMode = Rw> {
|
||||
/// Raw difficulty with day1/period stats - merges with indexer's raw
|
||||
pub raw: ComputedHeightDerivedLast<StoredF64>,
|
||||
pub as_hash: ComputedFromHeightLast<StoredF32, M>,
|
||||
pub adjustment: ComputedFromHeightLast<StoredF32, M>,
|
||||
|
||||
@@ -12,7 +12,7 @@ use vecdb::{AnyStoredVec, Database, Exit, ReadableVec, Rw, StorageMode};
|
||||
|
||||
use crate::{ComputeIndexes, blocks, distribution::DynCohortVecs, indexes, prices};
|
||||
|
||||
use crate::distribution::metrics::SupplyMetrics;
|
||||
use crate::distribution::metrics::{CohortMetricsBase, SupplyMetrics};
|
||||
|
||||
use super::{super::traits::CohortVecs, vecs::AddressCohortVecs};
|
||||
|
||||
@@ -33,7 +33,7 @@ impl AddressCohorts {
|
||||
indexes: &indexes::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
states_path: &Path,
|
||||
all_supply: Option<&SupplyMetrics>,
|
||||
all_supply: &SupplyMetrics,
|
||||
) -> Result<Self> {
|
||||
let v = version + VERSION;
|
||||
|
||||
@@ -140,7 +140,7 @@ impl AddressCohorts {
|
||||
blocks: &blocks::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
height_to_market_cap: Option<&HM>,
|
||||
height_to_market_cap: &HM,
|
||||
exit: &Exit,
|
||||
) -> Result<()>
|
||||
where
|
||||
@@ -198,12 +198,8 @@ impl AddressCohorts {
|
||||
|
||||
/// Reset cost_basis_data for all separate cohorts (called during fresh start).
|
||||
pub(crate) fn reset_separate_cost_basis_data(&mut self) -> Result<()> {
|
||||
self.par_iter_separate_mut().try_for_each(|v| {
|
||||
if let Some(state) = v.state.as_mut() {
|
||||
state.reset_cost_basis_data_if_needed()?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
self.par_iter_separate_mut()
|
||||
.try_for_each(|v| v.reset_cost_basis_data_if_needed())
|
||||
}
|
||||
|
||||
/// Validate computed versions for all separate cohorts.
|
||||
|
||||
@@ -15,7 +15,7 @@ use crate::{
|
||||
prices,
|
||||
};
|
||||
|
||||
use crate::distribution::metrics::{CohortMetrics, ImportConfig, SupplyMetrics};
|
||||
use crate::distribution::metrics::{BasicCohortMetrics, CohortMetricsBase, ImportConfig, SupplyMetrics};
|
||||
|
||||
use super::super::traits::{CohortVecs, DynCohortVecs};
|
||||
|
||||
@@ -33,7 +33,7 @@ pub struct AddressCohortVecs<M: StorageMode = Rw> {
|
||||
|
||||
/// Metric vectors
|
||||
#[traversable(flatten)]
|
||||
pub metrics: CohortMetrics<M>,
|
||||
pub metrics: BasicCohortMetrics<M>,
|
||||
|
||||
pub addr_count: ComputedFromHeightLast<StoredU64, M>,
|
||||
pub addr_count_30d_change: ComputedFromHeightLast<StoredF64, M>,
|
||||
@@ -43,7 +43,7 @@ impl AddressCohortVecs {
|
||||
/// Import address cohort from database.
|
||||
///
|
||||
/// `all_supply` is the supply metrics from the "all" cohort, used as global
|
||||
/// sources for `*_rel_to_market_cap` ratios. Pass `None` if not available.
|
||||
/// sources for `*_rel_to_market_cap` ratios.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
@@ -53,7 +53,7 @@ impl AddressCohortVecs {
|
||||
indexes: &indexes::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
states_path: Option<&Path>,
|
||||
all_supply: Option<&SupplyMetrics>,
|
||||
all_supply: &SupplyMetrics,
|
||||
) -> Result<Self> {
|
||||
let full_name = CohortContext::Address.full_name(&filter, name);
|
||||
|
||||
@@ -65,7 +65,6 @@ impl AddressCohortVecs {
|
||||
version,
|
||||
indexes,
|
||||
prices,
|
||||
up_to_1h_realized: None,
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
@@ -74,7 +73,7 @@ impl AddressCohortVecs {
|
||||
state: states_path
|
||||
.map(|path| Box::new(AddressCohortState::new(path, &full_name))),
|
||||
|
||||
metrics: CohortMetrics::forced_import(&cfg, all_supply)?,
|
||||
metrics: BasicCohortMetrics::forced_import(&cfg, all_supply)?,
|
||||
|
||||
addr_count: ComputedFromHeightLast::forced_import(
|
||||
db,
|
||||
@@ -227,6 +226,35 @@ impl DynCohortVecs for AddressCohortVecs {
|
||||
.compute_rest_part1(blocks, prices, starting_indexes, exit)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compute_net_sentiment_height(
|
||||
&mut self,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.metrics
|
||||
.compute_net_sentiment_height(starting_indexes, exit)
|
||||
}
|
||||
|
||||
fn write_state(&mut self, height: Height, cleanup: bool) -> Result<()> {
|
||||
if let Some(state) = self.state.as_mut() {
|
||||
state.inner.write(height, cleanup)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reset_cost_basis_data_if_needed(&mut self) -> Result<()> {
|
||||
if let Some(state) = self.state.as_mut() {
|
||||
state.inner.reset_cost_basis_data_if_needed()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reset_single_iteration_values(&mut self) {
|
||||
if let Some(state) = self.state.as_mut() {
|
||||
state.inner.reset_single_iteration_values();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CohortVecs for AddressCohortVecs {
|
||||
@@ -258,7 +286,7 @@ impl CohortVecs for AddressCohortVecs {
|
||||
blocks: &blocks::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
height_to_market_cap: Option<&impl ReadableVec<Height, Dollars>>,
|
||||
height_to_market_cap: &impl ReadableVec<Height, Dollars>,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.metrics.compute_rest_part2(
|
||||
|
||||
@@ -38,9 +38,27 @@ pub trait DynCohortVecs: Send + Sync {
|
||||
starting_indexes: &ComputeIndexes,
|
||||
exit: &Exit,
|
||||
) -> Result<()>;
|
||||
|
||||
/// Compute net_sentiment.height for separate cohorts (greed - pain).
|
||||
fn compute_net_sentiment_height(
|
||||
&mut self,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
exit: &Exit,
|
||||
) -> Result<()>;
|
||||
|
||||
/// Write state checkpoint to disk.
|
||||
fn write_state(&mut self, height: Height, cleanup: bool) -> Result<()>;
|
||||
|
||||
/// Reset cost basis data (called during fresh start).
|
||||
fn reset_cost_basis_data_if_needed(&mut self) -> Result<()>;
|
||||
|
||||
/// Reset per-block iteration values.
|
||||
fn reset_single_iteration_values(&mut self);
|
||||
}
|
||||
|
||||
/// Static dispatch trait for cohort vectors with additional methods.
|
||||
///
|
||||
/// Used by address cohorts where all cohorts share the same concrete type.
|
||||
pub trait CohortVecs: DynCohortVecs {
|
||||
/// Compute aggregate cohort from component cohorts.
|
||||
fn compute_from_stateful(
|
||||
@@ -56,7 +74,7 @@ pub trait CohortVecs: DynCohortVecs {
|
||||
blocks: &blocks::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
height_to_market_cap: Option<&impl ReadableVec<Height, Dollars>>,
|
||||
height_to_market_cap: &impl ReadableVec<Height, Dollars>,
|
||||
exit: &Exit,
|
||||
) -> Result<()>;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,11 @@
|
||||
use brk_types::{Cents, Height, Timestamp};
|
||||
use vecdb::Rw;
|
||||
|
||||
use crate::distribution::state::Transacted;
|
||||
|
||||
use super::groups::UTXOCohorts;
|
||||
|
||||
impl UTXOCohorts {
|
||||
impl UTXOCohorts<Rw> {
|
||||
/// Process received outputs for this block.
|
||||
///
|
||||
/// New UTXOs are added to:
|
||||
@@ -23,15 +24,9 @@ impl UTXOCohorts {
|
||||
let supply_state = received.spendable_supply;
|
||||
|
||||
// New UTXOs go into up_to_1h, current epoch, and current year
|
||||
[
|
||||
&mut self.0.age_range.up_to_1h,
|
||||
self.0.epoch.mut_vec_from_height(height),
|
||||
self.0.year.mut_vec_from_timestamp(timestamp),
|
||||
]
|
||||
.into_iter()
|
||||
.for_each(|v| {
|
||||
v.state.as_mut().unwrap().receive_utxo(&supply_state, price);
|
||||
});
|
||||
self.age_range.up_to_1h.state.as_mut().unwrap().receive_utxo(&supply_state, price);
|
||||
self.epoch.mut_vec_from_height(height).state.as_mut().unwrap().receive_utxo(&supply_state, price);
|
||||
self.year.mut_vec_from_timestamp(timestamp).state.as_mut().unwrap().receive_utxo(&supply_state, price);
|
||||
|
||||
// Update output type cohorts
|
||||
self.type_
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use brk_types::{Age, Height};
|
||||
use rustc_hash::FxHashMap;
|
||||
use vecdb::VecIndex;
|
||||
use vecdb::{Rw, VecIndex};
|
||||
|
||||
use crate::distribution::{
|
||||
compute::PriceRangeMax,
|
||||
@@ -9,7 +9,7 @@ use crate::distribution::{
|
||||
|
||||
use super::groups::UTXOCohorts;
|
||||
|
||||
impl UTXOCohorts {
|
||||
impl UTXOCohorts<Rw> {
|
||||
/// Process spent inputs for this block.
|
||||
///
|
||||
/// Each input references a UTXO created at some previous height.
|
||||
@@ -47,7 +47,7 @@ impl UTXOCohorts {
|
||||
let peak_price = price_range_max.max_between(receive_height, send_height);
|
||||
|
||||
// Update age range cohort (direct index lookup)
|
||||
self.0.age_range.get_mut(age).state.as_mut().unwrap().send_utxo(
|
||||
self.age_range.get_mut(age).state.as_mut().unwrap().send_utxo(
|
||||
&sent.spendable_supply,
|
||||
current_price,
|
||||
prev_price,
|
||||
@@ -56,8 +56,7 @@ impl UTXOCohorts {
|
||||
);
|
||||
|
||||
// Update epoch cohort (direct lookup by height)
|
||||
self.0
|
||||
.epoch
|
||||
self.epoch
|
||||
.mut_vec_from_height(receive_height)
|
||||
.state
|
||||
.as_mut().unwrap()
|
||||
@@ -70,8 +69,7 @@ impl UTXOCohorts {
|
||||
);
|
||||
|
||||
// Update year cohort (direct lookup by timestamp)
|
||||
self.0
|
||||
.year
|
||||
self.year
|
||||
.mut_vec_from_timestamp(block_state.timestamp)
|
||||
.state
|
||||
.as_mut().unwrap()
|
||||
@@ -88,7 +86,7 @@ impl UTXOCohorts {
|
||||
.spendable
|
||||
.iter_typed()
|
||||
.for_each(|(output_type, supply_state)| {
|
||||
self.0.type_.get_mut(output_type).state.as_mut().unwrap().send_utxo(
|
||||
self.type_.get_mut(output_type).state.as_mut().unwrap().send_utxo(
|
||||
supply_state,
|
||||
current_price,
|
||||
prev_price,
|
||||
@@ -101,7 +99,7 @@ impl UTXOCohorts {
|
||||
sent.by_size_group
|
||||
.iter_typed()
|
||||
.for_each(|(group, supply_state)| {
|
||||
self.0.amount_range.get_mut(group).state.as_mut().unwrap().send_utxo(
|
||||
self.amount_range.get_mut(group).state.as_mut().unwrap().send_utxo(
|
||||
supply_state,
|
||||
current_price,
|
||||
prev_price,
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use brk_cohort::AGE_BOUNDARIES;
|
||||
use brk_types::{ONE_HOUR_IN_SEC, Timestamp};
|
||||
use vecdb::Rw;
|
||||
|
||||
use crate::distribution::state::BlockState;
|
||||
|
||||
use super::groups::UTXOCohorts;
|
||||
|
||||
impl UTXOCohorts {
|
||||
impl UTXOCohorts<Rw> {
|
||||
/// Handle age transitions when processing a new block.
|
||||
///
|
||||
/// UTXOs age with each block. When they cross hour boundaries,
|
||||
@@ -32,7 +33,7 @@ impl UTXOCohorts {
|
||||
// Cohort i covers hours [BOUNDARIES[i-1], BOUNDARIES[i])
|
||||
// Cohort 0 covers [0, 1) hours
|
||||
// Cohort 20 covers [15*365*24, infinity) hours
|
||||
let mut age_cohorts: Vec<_> = self.0.age_range.iter_mut().map(|v| &mut v.state).collect();
|
||||
let mut age_cohorts: Vec<_> = self.age_range.iter_mut().map(|v| &mut v.state).collect();
|
||||
|
||||
// For each boundary (in hours), find blocks that just crossed it
|
||||
for (boundary_idx, &boundary_hours) in AGE_BOUNDARIES.iter().enumerate() {
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
use std::path::Path;
|
||||
|
||||
use brk_cohort::{CohortContext, Filter, Filtered, StateLevel};
|
||||
use brk_cohort::{Filter, Filtered};
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Cents, Dollars, Height, Version};
|
||||
use rayon::prelude::*;
|
||||
use vecdb::{AnyStoredVec, Database, Exit, ReadableVec, Rw, StorageMode};
|
||||
use brk_types::{Cents, Height, Version};
|
||||
use vecdb::{Exit, ReadableVec};
|
||||
|
||||
use crate::{ComputeIndexes, blocks, distribution::state::UTXOCohortState, indexes, prices};
|
||||
use crate::{ComputeIndexes, blocks, distribution::state::UTXOCohortState, prices};
|
||||
|
||||
use crate::distribution::metrics::{CohortMetrics, ImportConfig, RealizedMetrics, SupplyMetrics};
|
||||
use crate::distribution::metrics::CohortMetricsBase;
|
||||
|
||||
use super::super::traits::{CohortVecs, DynCohortVecs};
|
||||
use super::super::traits::DynCohortVecs;
|
||||
|
||||
/// UTXO cohort with metrics and optional runtime state.
|
||||
///
|
||||
/// Generic over the metrics type to support different cohort configurations
|
||||
/// (e.g. AllCohortMetrics, ExtendedCohortMetrics, BasicCohortMetrics, etc.)
|
||||
#[derive(Traversable)]
|
||||
pub struct UTXOCohortVecs<M: StorageMode = Rw> {
|
||||
pub struct UTXOCohortVecs<Metrics> {
|
||||
/// Starting height when state was imported
|
||||
#[traversable(skip)]
|
||||
state_starting_height: Option<Height>,
|
||||
|
||||
/// Runtime state for block-by-block processing (separate cohorts only)
|
||||
@@ -25,85 +26,28 @@ pub struct UTXOCohortVecs<M: StorageMode = Rw> {
|
||||
|
||||
/// Metric vectors
|
||||
#[traversable(flatten)]
|
||||
pub metrics: CohortMetrics<M>,
|
||||
pub metrics: Metrics,
|
||||
}
|
||||
|
||||
impl UTXOCohortVecs {
|
||||
/// Import UTXO cohort from database.
|
||||
///
|
||||
/// `all_supply` is the supply metrics from the "all" cohort, used as global
|
||||
/// sources for `*_rel_to_market_cap` ratios. Pass `None` for the "all" cohort itself.
|
||||
///
|
||||
/// `up_to_1h_realized` is used for cohorts where `compute_adjusted()` is true,
|
||||
/// to create lazy adjusted vecs: adjusted = cohort - up_to_1h.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
filter: Filter,
|
||||
name: &str,
|
||||
version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
states_path: &Path,
|
||||
state_level: StateLevel,
|
||||
all_supply: Option<&SupplyMetrics>,
|
||||
up_to_1h_realized: Option<&RealizedMetrics>,
|
||||
) -> Result<Self> {
|
||||
let full_name = CohortContext::Utxo.full_name(&filter, name);
|
||||
|
||||
let cfg = ImportConfig {
|
||||
db,
|
||||
filter,
|
||||
full_name: &full_name,
|
||||
context: CohortContext::Utxo,
|
||||
version,
|
||||
indexes,
|
||||
prices,
|
||||
up_to_1h_realized,
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
impl<Metrics> UTXOCohortVecs<Metrics> {
|
||||
/// Create a new UTXOCohortVecs with state and metrics.
|
||||
pub(crate) fn new(state: Option<Box<UTXOCohortState>>, metrics: Metrics) -> Self {
|
||||
Self {
|
||||
state_starting_height: None,
|
||||
|
||||
state: if state_level.is_full() {
|
||||
Some(Box::new(UTXOCohortState::new(states_path, &full_name)))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
|
||||
metrics: CohortMetrics::forced_import(&cfg, all_supply)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Reset state starting height to zero and reset state values.
|
||||
pub(crate) fn reset_state_starting_height(&mut self) {
|
||||
self.state_starting_height = Some(Height::ZERO);
|
||||
if let Some(state) = self.state.as_mut() {
|
||||
state.reset();
|
||||
state,
|
||||
metrics,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a parallel iterator over all vecs for parallel writing.
|
||||
pub(crate) fn par_iter_vecs_mut(&mut self) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
|
||||
self.metrics.par_iter_mut()
|
||||
}
|
||||
|
||||
/// Commit state to disk (separate from vec writes for parallelization).
|
||||
pub(crate) fn write_state(&mut self, height: Height, cleanup: bool) -> Result<()> {
|
||||
if let Some(state) = self.state.as_mut() {
|
||||
state.write(height, cleanup)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Filtered for UTXOCohortVecs {
|
||||
impl<Metrics: CohortMetricsBase + Traversable> Filtered for UTXOCohortVecs<Metrics> {
|
||||
fn filter(&self) -> &Filter {
|
||||
&self.metrics.filter
|
||||
self.metrics.filter()
|
||||
}
|
||||
}
|
||||
|
||||
impl DynCohortVecs for UTXOCohortVecs {
|
||||
impl<Metrics: CohortMetricsBase + Traversable> DynCohortVecs for UTXOCohortVecs<Metrics> {
|
||||
fn min_stateful_height_len(&self) -> usize {
|
||||
self.metrics.min_stateful_height_len()
|
||||
}
|
||||
@@ -116,18 +60,13 @@ impl DynCohortVecs for UTXOCohortVecs {
|
||||
}
|
||||
|
||||
fn import_state(&mut self, starting_height: Height) -> Result<Height> {
|
||||
// Import state from runtime state if present
|
||||
if let Some(state) = self.state.as_mut() {
|
||||
// State files are saved AT height H, so to resume at H+1 we need to import at H
|
||||
// Decrement first, then increment result to match expected starting_height
|
||||
if let Some(mut prev_height) = starting_height.decremented() {
|
||||
// Import cost_basis_data state file (may adjust prev_height to actual file found)
|
||||
prev_height = state.import_at_or_before(prev_height)?;
|
||||
|
||||
// Restore supply state from height-indexed vectors
|
||||
state.supply.value = self
|
||||
.metrics
|
||||
.supply
|
||||
.supply()
|
||||
.total
|
||||
.sats
|
||||
.height
|
||||
@@ -135,20 +74,18 @@ impl DynCohortVecs for UTXOCohortVecs {
|
||||
.unwrap();
|
||||
state.supply.utxo_count = *self
|
||||
.metrics
|
||||
.outputs
|
||||
.outputs()
|
||||
.utxo_count
|
||||
.height
|
||||
.collect_one(prev_height)
|
||||
.unwrap();
|
||||
|
||||
// Restore realized cap from persisted exact values
|
||||
state.restore_realized_cap();
|
||||
|
||||
let result = prev_height.incremented();
|
||||
self.state_starting_height = Some(result);
|
||||
Ok(result)
|
||||
} else {
|
||||
// starting_height is 0, nothing to import
|
||||
self.state_starting_height = Some(Height::ZERO);
|
||||
Ok(Height::ZERO)
|
||||
}
|
||||
@@ -167,7 +104,6 @@ impl DynCohortVecs for UTXOCohortVecs {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Push from state to metrics
|
||||
if let Some(state) = self.state.as_ref() {
|
||||
self.metrics.truncate_push(height, state)?;
|
||||
}
|
||||
@@ -181,11 +117,8 @@ impl DynCohortVecs for UTXOCohortVecs {
|
||||
height_price: Cents,
|
||||
) -> Result<()> {
|
||||
if let Some(state) = self.state.as_mut() {
|
||||
self.metrics.compute_then_truncate_push_unrealized_states(
|
||||
height,
|
||||
height_price,
|
||||
state,
|
||||
)?;
|
||||
self.metrics
|
||||
.compute_then_truncate_push_unrealized_states(height, height_price, state)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -200,36 +133,33 @@ impl DynCohortVecs for UTXOCohortVecs {
|
||||
self.metrics
|
||||
.compute_rest_part1(blocks, prices, starting_indexes, exit)
|
||||
}
|
||||
}
|
||||
|
||||
impl CohortVecs for UTXOCohortVecs {
|
||||
fn compute_from_stateful(
|
||||
fn compute_net_sentiment_height(
|
||||
&mut self,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
others: &[&Self],
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.metrics.compute_from_stateful(
|
||||
starting_indexes,
|
||||
&others.iter().map(|v| &v.metrics).collect::<Vec<_>>(),
|
||||
exit,
|
||||
)
|
||||
self.metrics
|
||||
.compute_net_sentiment_height(starting_indexes, exit)
|
||||
}
|
||||
|
||||
fn compute_rest_part2(
|
||||
&mut self,
|
||||
blocks: &blocks::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
height_to_market_cap: Option<&impl ReadableVec<Height, Dollars>>,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.metrics.compute_rest_part2(
|
||||
blocks,
|
||||
prices,
|
||||
starting_indexes,
|
||||
height_to_market_cap,
|
||||
exit,
|
||||
)
|
||||
fn write_state(&mut self, height: Height, cleanup: bool) -> Result<()> {
|
||||
if let Some(state) = self.state.as_mut() {
|
||||
state.write(height, cleanup)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reset_cost_basis_data_if_needed(&mut self) -> Result<()> {
|
||||
if let Some(state) = self.state.as_mut() {
|
||||
state.reset_cost_basis_data_if_needed()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reset_single_iteration_values(&mut self) {
|
||||
if let Some(state) = self.state.as_mut() {
|
||||
state.reset_single_iteration_values();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ pub(crate) fn compute_rest_part2<HM>(
|
||||
blocks: &blocks::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
height_to_market_cap: Option<&HM>,
|
||||
height_to_market_cap: &HM,
|
||||
exit: &Exit,
|
||||
) -> Result<()>
|
||||
where
|
||||
|
||||
@@ -520,17 +520,13 @@ pub(crate) fn process_blocks(
|
||||
|
||||
/// Reset per-block values for all separate cohorts.
|
||||
fn reset_block_values(utxo_cohorts: &mut UTXOCohorts, address_cohorts: &mut AddressCohorts) {
|
||||
utxo_cohorts.iter_separate_mut().for_each(|v| {
|
||||
if let Some(state) = v.state.as_mut() {
|
||||
state.reset_single_iteration_values();
|
||||
}
|
||||
});
|
||||
utxo_cohorts
|
||||
.iter_separate_mut()
|
||||
.for_each(|v| v.reset_single_iteration_values());
|
||||
|
||||
address_cohorts.iter_separate_mut().for_each(|v| {
|
||||
if let Some(state) = v.state.as_mut() {
|
||||
state.inner.reset_single_iteration_values();
|
||||
}
|
||||
});
|
||||
address_cohorts
|
||||
.iter_separate_mut()
|
||||
.for_each(|v| v.reset_single_iteration_values());
|
||||
}
|
||||
|
||||
/// Push cohort states to height-indexed vectors.
|
||||
|
||||
122
crates/brk_computer/src/distribution/metrics/cohort/adjusted.rs
Normal file
122
crates/brk_computer/src/distribution/metrics/cohort/adjusted.rs
Normal file
@@ -0,0 +1,122 @@
|
||||
use brk_cohort::Filter;
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Cents, Dollars, Height, Version};
|
||||
use rayon::prelude::*;
|
||||
use vecdb::{AnyStoredVec, Exit, ReadableVec, Rw, StorageMode};
|
||||
|
||||
use crate::{ComputeIndexes, blocks, distribution::state::CohortState, prices};
|
||||
|
||||
use crate::distribution::metrics::{
|
||||
ActivityMetrics, CohortMetricsBase, CostBasisBase, ImportConfig, OutputsMetrics, RealizedBase,
|
||||
RealizedWithAdjusted, RelativeWithPeakRegret, SupplyMetrics, UnrealizedBase,
|
||||
UnrealizedWithPeakRegret,
|
||||
};
|
||||
|
||||
/// Cohort metrics with adjusted realized + peak regret (no extended).
|
||||
/// Used by: max_age cohorts.
|
||||
#[derive(Traversable)]
|
||||
pub struct AdjustedCohortMetrics<M: StorageMode = Rw> {
|
||||
#[traversable(skip)]
|
||||
pub filter: Filter,
|
||||
pub supply: Box<SupplyMetrics<M>>,
|
||||
pub outputs: Box<OutputsMetrics<M>>,
|
||||
pub activity: Box<ActivityMetrics<M>>,
|
||||
pub realized: Box<RealizedWithAdjusted<M>>,
|
||||
pub cost_basis: Box<CostBasisBase<M>>,
|
||||
pub unrealized: Box<UnrealizedWithPeakRegret<M>>,
|
||||
pub relative: Box<RelativeWithPeakRegret>,
|
||||
}
|
||||
|
||||
impl CohortMetricsBase for AdjustedCohortMetrics {
|
||||
fn filter(&self) -> &Filter { &self.filter }
|
||||
fn supply(&self) -> &SupplyMetrics { &self.supply }
|
||||
fn supply_mut(&mut self) -> &mut SupplyMetrics { &mut self.supply }
|
||||
fn outputs(&self) -> &OutputsMetrics { &self.outputs }
|
||||
fn outputs_mut(&mut self) -> &mut OutputsMetrics { &mut self.outputs }
|
||||
fn activity(&self) -> &ActivityMetrics { &self.activity }
|
||||
fn activity_mut(&mut self) -> &mut ActivityMetrics { &mut self.activity }
|
||||
fn realized_base(&self) -> &RealizedBase { &self.realized }
|
||||
fn realized_base_mut(&mut self) -> &mut RealizedBase { &mut self.realized }
|
||||
fn unrealized_base(&self) -> &UnrealizedBase { &self.unrealized }
|
||||
fn unrealized_base_mut(&mut self) -> &mut UnrealizedBase { &mut self.unrealized }
|
||||
fn cost_basis_base(&self) -> &CostBasisBase { &self.cost_basis }
|
||||
fn cost_basis_base_mut(&mut self) -> &mut CostBasisBase { &mut self.cost_basis }
|
||||
|
||||
fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> {
|
||||
self.supply.validate_computed_versions(base_version)?;
|
||||
self.activity.validate_computed_versions(base_version)?;
|
||||
Ok(())
|
||||
}
|
||||
fn compute_then_truncate_push_unrealized_states(
|
||||
&mut self, height: Height, height_price: Cents, state: &mut CohortState,
|
||||
) -> Result<()> {
|
||||
state.apply_pending();
|
||||
self.cost_basis.truncate_push_minmax(height, state)?;
|
||||
let (height_unrealized_state, _) = state.compute_unrealized_states(height_price, None);
|
||||
self.unrealized.base.truncate_push(height, &height_unrealized_state)?;
|
||||
Ok(())
|
||||
}
|
||||
fn collect_all_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
|
||||
let mut vecs: Vec<&mut dyn AnyStoredVec> = Vec::new();
|
||||
vecs.extend(self.supply.par_iter_mut().collect::<Vec<_>>());
|
||||
vecs.extend(self.outputs.par_iter_mut().collect::<Vec<_>>());
|
||||
vecs.extend(self.activity.par_iter_mut().collect::<Vec<_>>());
|
||||
vecs.extend(self.realized.collect_vecs_mut());
|
||||
vecs.extend(self.cost_basis.collect_vecs_mut());
|
||||
vecs.extend(self.unrealized.base.collect_vecs_mut());
|
||||
vecs.extend(self.unrealized.peak_regret_ext.collect_vecs_mut());
|
||||
vecs
|
||||
}
|
||||
}
|
||||
|
||||
impl AdjustedCohortMetrics {
|
||||
pub(crate) fn forced_import(
|
||||
cfg: &ImportConfig,
|
||||
all_supply: &SupplyMetrics,
|
||||
up_to_1h: &RealizedBase,
|
||||
) -> Result<Self> {
|
||||
let supply = SupplyMetrics::forced_import(cfg)?;
|
||||
let unrealized = UnrealizedWithPeakRegret::forced_import(cfg)?;
|
||||
let realized = RealizedWithAdjusted::forced_import(cfg, up_to_1h)?;
|
||||
|
||||
let relative = RelativeWithPeakRegret::forced_import(
|
||||
cfg,
|
||||
&unrealized.base,
|
||||
&supply,
|
||||
all_supply,
|
||||
&realized.base,
|
||||
&unrealized.peak_regret_ext.peak_regret,
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
filter: cfg.filter.clone(),
|
||||
supply: Box::new(supply),
|
||||
outputs: Box::new(OutputsMetrics::forced_import(cfg)?),
|
||||
activity: Box::new(ActivityMetrics::forced_import(cfg)?),
|
||||
realized: Box::new(realized),
|
||||
cost_basis: Box::new(CostBasisBase::forced_import(cfg)?),
|
||||
unrealized: Box::new(unrealized),
|
||||
relative: Box::new(relative),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn compute_rest_part2(
|
||||
&mut self,
|
||||
blocks: &blocks::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
height_to_market_cap: &impl ReadableVec<Height, Dollars>,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.realized.compute_rest_part2(
|
||||
blocks,
|
||||
prices,
|
||||
starting_indexes,
|
||||
&self.supply.total.btc.height,
|
||||
height_to_market_cap,
|
||||
exit,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
128
crates/brk_computer/src/distribution/metrics/cohort/all.rs
Normal file
128
crates/brk_computer/src/distribution/metrics/cohort/all.rs
Normal file
@@ -0,0 +1,128 @@
|
||||
use brk_cohort::Filter;
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Cents, Dollars, Height, Version};
|
||||
use rayon::prelude::*;
|
||||
use vecdb::{AnyStoredVec, Exit, ReadableVec, Rw, StorageMode};
|
||||
|
||||
use crate::{ComputeIndexes, blocks, distribution::state::CohortState, prices};
|
||||
|
||||
use crate::distribution::metrics::{
|
||||
ActivityMetrics, CohortMetricsBase, CostBasisBase, CostBasisWithExtended, ImportConfig,
|
||||
OutputsMetrics, RealizedBase, RealizedWithExtendedAdjusted, RelativeForAll, SupplyMetrics,
|
||||
UnrealizedBase, UnrealizedWithPeakRegret,
|
||||
};
|
||||
|
||||
/// All-cohort metrics: extended + adjusted realized, extended cost basis,
|
||||
/// peak regret, relative for-all (no rel_to_all).
|
||||
/// Used by: the "all" cohort.
|
||||
#[derive(Traversable)]
|
||||
pub struct AllCohortMetrics<M: StorageMode = Rw> {
|
||||
#[traversable(skip)]
|
||||
pub filter: Filter,
|
||||
pub supply: Box<SupplyMetrics<M>>,
|
||||
pub outputs: Box<OutputsMetrics<M>>,
|
||||
pub activity: Box<ActivityMetrics<M>>,
|
||||
pub realized: Box<RealizedWithExtendedAdjusted<M>>,
|
||||
pub cost_basis: Box<CostBasisWithExtended<M>>,
|
||||
pub unrealized: Box<UnrealizedWithPeakRegret<M>>,
|
||||
pub relative: Box<RelativeForAll>,
|
||||
}
|
||||
|
||||
impl CohortMetricsBase for AllCohortMetrics {
|
||||
fn filter(&self) -> &Filter { &self.filter }
|
||||
fn supply(&self) -> &SupplyMetrics { &self.supply }
|
||||
fn supply_mut(&mut self) -> &mut SupplyMetrics { &mut self.supply }
|
||||
fn outputs(&self) -> &OutputsMetrics { &self.outputs }
|
||||
fn outputs_mut(&mut self) -> &mut OutputsMetrics { &mut self.outputs }
|
||||
fn activity(&self) -> &ActivityMetrics { &self.activity }
|
||||
fn activity_mut(&mut self) -> &mut ActivityMetrics { &mut self.activity }
|
||||
fn realized_base(&self) -> &RealizedBase { &self.realized }
|
||||
fn realized_base_mut(&mut self) -> &mut RealizedBase { &mut self.realized }
|
||||
fn unrealized_base(&self) -> &UnrealizedBase { &self.unrealized }
|
||||
fn unrealized_base_mut(&mut self) -> &mut UnrealizedBase { &mut self.unrealized }
|
||||
fn cost_basis_base(&self) -> &CostBasisBase { &self.cost_basis }
|
||||
fn cost_basis_base_mut(&mut self) -> &mut CostBasisBase { &mut self.cost_basis }
|
||||
fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> {
|
||||
self.supply.validate_computed_versions(base_version)?;
|
||||
self.activity.validate_computed_versions(base_version)?;
|
||||
self.cost_basis.validate_computed_versions(base_version)?;
|
||||
Ok(())
|
||||
}
|
||||
fn compute_then_truncate_push_unrealized_states(
|
||||
&mut self, height: Height, height_price: Cents, state: &mut CohortState,
|
||||
) -> Result<()> {
|
||||
state.apply_pending();
|
||||
self.cost_basis.truncate_push_minmax(height, state)?;
|
||||
let (height_unrealized_state, _) = state.compute_unrealized_states(height_price, None);
|
||||
self.unrealized.base.truncate_push(height, &height_unrealized_state)?;
|
||||
let spot = height_price.to_dollars();
|
||||
self.cost_basis.extended.truncate_push_percentiles(height, state, spot)?;
|
||||
Ok(())
|
||||
}
|
||||
fn collect_all_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
|
||||
let mut vecs: Vec<&mut dyn AnyStoredVec> = Vec::new();
|
||||
vecs.extend(self.supply.par_iter_mut().collect::<Vec<_>>());
|
||||
vecs.extend(self.outputs.par_iter_mut().collect::<Vec<_>>());
|
||||
vecs.extend(self.activity.par_iter_mut().collect::<Vec<_>>());
|
||||
vecs.extend(self.realized.collect_vecs_mut());
|
||||
vecs.extend(self.cost_basis.base.collect_vecs_mut());
|
||||
vecs.extend(self.cost_basis.extended.collect_vecs_mut());
|
||||
vecs.extend(self.unrealized.base.collect_vecs_mut());
|
||||
vecs.extend(self.unrealized.peak_regret_ext.collect_vecs_mut());
|
||||
vecs
|
||||
}
|
||||
}
|
||||
|
||||
impl AllCohortMetrics {
|
||||
/// Import the "all" cohort metrics with a pre-imported supply.
|
||||
///
|
||||
/// Supply is imported first (before other cohorts) so it can be used as `all_supply`
|
||||
/// reference for relative metric lazy vecs in other cohorts.
|
||||
pub(crate) fn forced_import_with_supply(
|
||||
cfg: &ImportConfig,
|
||||
supply: SupplyMetrics,
|
||||
up_to_1h: &RealizedBase,
|
||||
) -> Result<Self> {
|
||||
let unrealized = UnrealizedWithPeakRegret::forced_import(cfg)?;
|
||||
let realized = RealizedWithExtendedAdjusted::forced_import(cfg, up_to_1h)?;
|
||||
|
||||
let relative = RelativeForAll::forced_import(
|
||||
cfg,
|
||||
&unrealized.base,
|
||||
&supply,
|
||||
&realized.base,
|
||||
&unrealized.peak_regret_ext.peak_regret,
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
filter: cfg.filter.clone(),
|
||||
supply: Box::new(supply),
|
||||
outputs: Box::new(OutputsMetrics::forced_import(cfg)?),
|
||||
activity: Box::new(ActivityMetrics::forced_import(cfg)?),
|
||||
realized: Box::new(realized),
|
||||
cost_basis: Box::new(CostBasisWithExtended::forced_import(cfg)?),
|
||||
unrealized: Box::new(unrealized),
|
||||
relative: Box::new(relative),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn compute_rest_part2(
|
||||
&mut self,
|
||||
blocks: &blocks::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
height_to_market_cap: &impl ReadableVec<Height, Dollars>,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.realized.compute_rest_part2(
|
||||
blocks,
|
||||
prices,
|
||||
starting_indexes,
|
||||
&self.supply.total.btc.height,
|
||||
height_to_market_cap,
|
||||
exit,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
155
crates/brk_computer/src/distribution/metrics/cohort/basic.rs
Normal file
155
crates/brk_computer/src/distribution/metrics/cohort/basic.rs
Normal file
@@ -0,0 +1,155 @@
|
||||
use brk_cohort::Filter;
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Cents, Dollars, Height, Version};
|
||||
use rayon::prelude::*;
|
||||
use vecdb::{AnyStoredVec, Exit, ReadableVec, Rw, StorageMode};
|
||||
|
||||
use crate::{ComputeIndexes, blocks, distribution::state::CohortState, prices};
|
||||
|
||||
use crate::distribution::metrics::{
|
||||
ActivityMetrics, CohortMetricsBase, CostBasisBase, ImportConfig, OutputsMetrics, RealizedBase,
|
||||
RelativeWithRelToAll, SupplyMetrics, UnrealizedBase,
|
||||
};
|
||||
|
||||
/// Basic cohort metrics: no extensions, with relative (rel_to_all).
|
||||
/// Used by: epoch, year, type (spendable), amount, address cohorts.
|
||||
#[derive(Traversable)]
|
||||
pub struct BasicCohortMetrics<M: StorageMode = Rw> {
|
||||
#[traversable(skip)]
|
||||
pub filter: Filter,
|
||||
pub supply: Box<SupplyMetrics<M>>,
|
||||
pub outputs: Box<OutputsMetrics<M>>,
|
||||
pub activity: Box<ActivityMetrics<M>>,
|
||||
pub realized: Box<RealizedBase<M>>,
|
||||
pub cost_basis: Box<CostBasisBase<M>>,
|
||||
pub unrealized: Box<UnrealizedBase<M>>,
|
||||
pub relative: Box<RelativeWithRelToAll>,
|
||||
}
|
||||
|
||||
impl CohortMetricsBase for BasicCohortMetrics {
|
||||
fn filter(&self) -> &Filter { &self.filter }
|
||||
fn supply(&self) -> &SupplyMetrics { &self.supply }
|
||||
fn supply_mut(&mut self) -> &mut SupplyMetrics { &mut self.supply }
|
||||
fn outputs(&self) -> &OutputsMetrics { &self.outputs }
|
||||
fn outputs_mut(&mut self) -> &mut OutputsMetrics { &mut self.outputs }
|
||||
fn activity(&self) -> &ActivityMetrics { &self.activity }
|
||||
fn activity_mut(&mut self) -> &mut ActivityMetrics { &mut self.activity }
|
||||
fn realized_base(&self) -> &RealizedBase { &self.realized }
|
||||
fn realized_base_mut(&mut self) -> &mut RealizedBase { &mut self.realized }
|
||||
fn unrealized_base(&self) -> &UnrealizedBase { &self.unrealized }
|
||||
fn unrealized_base_mut(&mut self) -> &mut UnrealizedBase { &mut self.unrealized }
|
||||
fn cost_basis_base(&self) -> &CostBasisBase { &self.cost_basis }
|
||||
fn cost_basis_base_mut(&mut self) -> &mut CostBasisBase { &mut self.cost_basis }
|
||||
fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> {
|
||||
self.supply.validate_computed_versions(base_version)?;
|
||||
self.activity.validate_computed_versions(base_version)?;
|
||||
Ok(())
|
||||
}
|
||||
fn compute_then_truncate_push_unrealized_states(
|
||||
&mut self, height: Height, height_price: Cents, state: &mut CohortState,
|
||||
) -> Result<()> {
|
||||
state.apply_pending();
|
||||
self.cost_basis.truncate_push_minmax(height, state)?;
|
||||
let (height_unrealized_state, _) = state.compute_unrealized_states(height_price, None);
|
||||
self.unrealized.truncate_push(height, &height_unrealized_state)?;
|
||||
Ok(())
|
||||
}
|
||||
fn collect_all_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
|
||||
let mut vecs: Vec<&mut dyn AnyStoredVec> = Vec::new();
|
||||
vecs.extend(self.supply.par_iter_mut().collect::<Vec<_>>());
|
||||
vecs.extend(self.outputs.par_iter_mut().collect::<Vec<_>>());
|
||||
vecs.extend(self.activity.par_iter_mut().collect::<Vec<_>>());
|
||||
vecs.extend(self.realized.collect_vecs_mut());
|
||||
vecs.extend(self.cost_basis.collect_vecs_mut());
|
||||
vecs.extend(self.unrealized.collect_vecs_mut());
|
||||
vecs
|
||||
}
|
||||
}
|
||||
|
||||
impl BasicCohortMetrics {
|
||||
pub(crate) fn forced_import(
|
||||
cfg: &ImportConfig,
|
||||
all_supply: &SupplyMetrics,
|
||||
) -> Result<Self> {
|
||||
let supply = SupplyMetrics::forced_import(cfg)?;
|
||||
let unrealized = UnrealizedBase::forced_import(cfg)?;
|
||||
let realized = RealizedBase::forced_import(cfg)?;
|
||||
|
||||
let relative = RelativeWithRelToAll::forced_import(
|
||||
cfg, &unrealized, &supply, all_supply, &realized,
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
filter: cfg.filter.clone(),
|
||||
supply: Box::new(supply),
|
||||
outputs: Box::new(OutputsMetrics::forced_import(cfg)?),
|
||||
activity: Box::new(ActivityMetrics::forced_import(cfg)?),
|
||||
realized: Box::new(realized),
|
||||
cost_basis: Box::new(CostBasisBase::forced_import(cfg)?),
|
||||
unrealized: Box::new(unrealized),
|
||||
relative: Box::new(relative),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
|
||||
self.collect_all_vecs_mut().into_par_iter()
|
||||
}
|
||||
|
||||
pub(crate) fn compute_rest_part2(
|
||||
&mut self,
|
||||
blocks: &blocks::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
height_to_market_cap: &impl ReadableVec<Height, Dollars>,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.realized.compute_rest_part2_base(
|
||||
blocks,
|
||||
prices,
|
||||
starting_indexes,
|
||||
&self.supply.total.btc.height,
|
||||
height_to_market_cap,
|
||||
exit,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn compute_from_stateful(
|
||||
&mut self,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
others: &[&Self],
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.supply.compute_from_stateful(
|
||||
starting_indexes,
|
||||
&others.iter().map(|v| &*v.supply).collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
self.outputs.compute_from_stateful(
|
||||
starting_indexes,
|
||||
&others.iter().map(|v| &*v.outputs).collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
self.activity.compute_from_stateful(
|
||||
starting_indexes,
|
||||
&others.iter().map(|v| &*v.activity).collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
self.realized.compute_from_stateful(
|
||||
starting_indexes,
|
||||
&others.iter().map(|v| &*v.realized).collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
self.unrealized.compute_from_stateful(
|
||||
starting_indexes,
|
||||
&others.iter().map(|v| &*v.unrealized).collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
self.cost_basis.compute_from_stateful(
|
||||
starting_indexes,
|
||||
&others.iter().map(|v| &*v.cost_basis).collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
125
crates/brk_computer/src/distribution/metrics/cohort/extended.rs
Normal file
125
crates/brk_computer/src/distribution/metrics/cohort/extended.rs
Normal file
@@ -0,0 +1,125 @@
|
||||
use brk_cohort::Filter;
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Cents, Dollars, Height, Version};
|
||||
use rayon::prelude::*;
|
||||
use vecdb::{AnyStoredVec, Exit, ReadableVec, Rw, StorageMode};
|
||||
|
||||
use crate::{ComputeIndexes, blocks, distribution::state::CohortState, prices};
|
||||
|
||||
use crate::distribution::metrics::{
|
||||
ActivityMetrics, CohortMetricsBase, CostBasisBase, CostBasisWithExtended, ImportConfig,
|
||||
OutputsMetrics, RealizedBase, RealizedWithExtended, RelativeWithExtended, SupplyMetrics,
|
||||
UnrealizedBase, UnrealizedWithPeakRegret,
|
||||
};
|
||||
|
||||
/// Cohort metrics with extended realized + extended cost basis + peak regret (no adjusted).
|
||||
/// Used by: lth, age_range cohorts.
|
||||
#[derive(Traversable)]
|
||||
pub struct ExtendedCohortMetrics<M: StorageMode = Rw> {
|
||||
#[traversable(skip)]
|
||||
pub filter: Filter,
|
||||
pub supply: Box<SupplyMetrics<M>>,
|
||||
pub outputs: Box<OutputsMetrics<M>>,
|
||||
pub activity: Box<ActivityMetrics<M>>,
|
||||
pub realized: Box<RealizedWithExtended<M>>,
|
||||
pub cost_basis: Box<CostBasisWithExtended<M>>,
|
||||
pub unrealized: Box<UnrealizedWithPeakRegret<M>>,
|
||||
pub relative: Box<RelativeWithExtended>,
|
||||
}
|
||||
|
||||
impl CohortMetricsBase for ExtendedCohortMetrics {
|
||||
fn filter(&self) -> &Filter { &self.filter }
|
||||
fn supply(&self) -> &SupplyMetrics { &self.supply }
|
||||
fn supply_mut(&mut self) -> &mut SupplyMetrics { &mut self.supply }
|
||||
fn outputs(&self) -> &OutputsMetrics { &self.outputs }
|
||||
fn outputs_mut(&mut self) -> &mut OutputsMetrics { &mut self.outputs }
|
||||
fn activity(&self) -> &ActivityMetrics { &self.activity }
|
||||
fn activity_mut(&mut self) -> &mut ActivityMetrics { &mut self.activity }
|
||||
fn realized_base(&self) -> &RealizedBase { &self.realized }
|
||||
fn realized_base_mut(&mut self) -> &mut RealizedBase { &mut self.realized }
|
||||
fn unrealized_base(&self) -> &UnrealizedBase { &self.unrealized }
|
||||
fn unrealized_base_mut(&mut self) -> &mut UnrealizedBase { &mut self.unrealized }
|
||||
fn cost_basis_base(&self) -> &CostBasisBase { &self.cost_basis }
|
||||
fn cost_basis_base_mut(&mut self) -> &mut CostBasisBase { &mut self.cost_basis }
|
||||
|
||||
fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> {
|
||||
self.supply.validate_computed_versions(base_version)?;
|
||||
self.activity.validate_computed_versions(base_version)?;
|
||||
self.cost_basis.validate_computed_versions(base_version)?;
|
||||
Ok(())
|
||||
}
|
||||
fn compute_then_truncate_push_unrealized_states(
|
||||
&mut self, height: Height, height_price: Cents, state: &mut CohortState,
|
||||
) -> Result<()> {
|
||||
state.apply_pending();
|
||||
self.cost_basis.truncate_push_minmax(height, state)?;
|
||||
let (height_unrealized_state, _) = state.compute_unrealized_states(height_price, None);
|
||||
self.unrealized.base.truncate_push(height, &height_unrealized_state)?;
|
||||
let spot = height_price.to_dollars();
|
||||
self.cost_basis.extended.truncate_push_percentiles(height, state, spot)?;
|
||||
Ok(())
|
||||
}
|
||||
fn collect_all_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
|
||||
let mut vecs: Vec<&mut dyn AnyStoredVec> = Vec::new();
|
||||
vecs.extend(self.supply.par_iter_mut().collect::<Vec<_>>());
|
||||
vecs.extend(self.outputs.par_iter_mut().collect::<Vec<_>>());
|
||||
vecs.extend(self.activity.par_iter_mut().collect::<Vec<_>>());
|
||||
vecs.extend(self.realized.collect_vecs_mut());
|
||||
vecs.extend(self.cost_basis.base.collect_vecs_mut());
|
||||
vecs.extend(self.cost_basis.extended.collect_vecs_mut());
|
||||
vecs.extend(self.unrealized.base.collect_vecs_mut());
|
||||
vecs.extend(self.unrealized.peak_regret_ext.collect_vecs_mut());
|
||||
vecs
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtendedCohortMetrics {
|
||||
pub(crate) fn forced_import(
|
||||
cfg: &ImportConfig,
|
||||
all_supply: &SupplyMetrics,
|
||||
) -> Result<Self> {
|
||||
let supply = SupplyMetrics::forced_import(cfg)?;
|
||||
let unrealized = UnrealizedWithPeakRegret::forced_import(cfg)?;
|
||||
let realized = RealizedWithExtended::forced_import(cfg)?;
|
||||
|
||||
let relative = RelativeWithExtended::forced_import(
|
||||
cfg,
|
||||
&unrealized.base,
|
||||
&supply,
|
||||
all_supply,
|
||||
&realized.base,
|
||||
&unrealized.peak_regret_ext.peak_regret,
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
filter: cfg.filter.clone(),
|
||||
supply: Box::new(supply),
|
||||
outputs: Box::new(OutputsMetrics::forced_import(cfg)?),
|
||||
activity: Box::new(ActivityMetrics::forced_import(cfg)?),
|
||||
realized: Box::new(realized),
|
||||
cost_basis: Box::new(CostBasisWithExtended::forced_import(cfg)?),
|
||||
unrealized: Box::new(unrealized),
|
||||
relative: Box::new(relative),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn compute_rest_part2(
|
||||
&mut self,
|
||||
blocks: &blocks::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
height_to_market_cap: &impl ReadableVec<Height, Dollars>,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.realized.compute_rest_part2(
|
||||
blocks,
|
||||
prices,
|
||||
starting_indexes,
|
||||
&self.supply.total.btc.height,
|
||||
height_to_market_cap,
|
||||
exit,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
use brk_cohort::Filter;
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Cents, Dollars, Height, Version};
|
||||
use rayon::prelude::*;
|
||||
use vecdb::{AnyStoredVec, Exit, ReadableVec, Rw, StorageMode};
|
||||
|
||||
use crate::{ComputeIndexes, blocks, distribution::state::CohortState, prices};
|
||||
|
||||
use crate::distribution::metrics::{
|
||||
ActivityMetrics, CohortMetricsBase, CostBasisBase, CostBasisWithExtended, ImportConfig,
|
||||
OutputsMetrics, RealizedBase, RealizedWithExtendedAdjusted, RelativeWithExtended,
|
||||
SupplyMetrics, UnrealizedBase, UnrealizedWithPeakRegret,
|
||||
};
|
||||
|
||||
/// Cohort metrics with extended + adjusted realized, extended cost basis, peak regret.
|
||||
/// Used by: sth cohort.
|
||||
#[derive(Traversable)]
|
||||
pub struct ExtendedAdjustedCohortMetrics<M: StorageMode = Rw> {
|
||||
#[traversable(skip)]
|
||||
pub filter: Filter,
|
||||
pub supply: Box<SupplyMetrics<M>>,
|
||||
pub outputs: Box<OutputsMetrics<M>>,
|
||||
pub activity: Box<ActivityMetrics<M>>,
|
||||
pub realized: Box<RealizedWithExtendedAdjusted<M>>,
|
||||
pub cost_basis: Box<CostBasisWithExtended<M>>,
|
||||
pub unrealized: Box<UnrealizedWithPeakRegret<M>>,
|
||||
pub relative: Box<RelativeWithExtended>,
|
||||
}
|
||||
|
||||
impl CohortMetricsBase for ExtendedAdjustedCohortMetrics {
|
||||
fn filter(&self) -> &Filter { &self.filter }
|
||||
fn supply(&self) -> &SupplyMetrics { &self.supply }
|
||||
fn supply_mut(&mut self) -> &mut SupplyMetrics { &mut self.supply }
|
||||
fn outputs(&self) -> &OutputsMetrics { &self.outputs }
|
||||
fn outputs_mut(&mut self) -> &mut OutputsMetrics { &mut self.outputs }
|
||||
fn activity(&self) -> &ActivityMetrics { &self.activity }
|
||||
fn activity_mut(&mut self) -> &mut ActivityMetrics { &mut self.activity }
|
||||
fn realized_base(&self) -> &RealizedBase { &self.realized }
|
||||
fn realized_base_mut(&mut self) -> &mut RealizedBase { &mut self.realized }
|
||||
fn unrealized_base(&self) -> &UnrealizedBase { &self.unrealized }
|
||||
fn unrealized_base_mut(&mut self) -> &mut UnrealizedBase { &mut self.unrealized }
|
||||
fn cost_basis_base(&self) -> &CostBasisBase { &self.cost_basis }
|
||||
fn cost_basis_base_mut(&mut self) -> &mut CostBasisBase { &mut self.cost_basis }
|
||||
fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> {
|
||||
self.supply.validate_computed_versions(base_version)?;
|
||||
self.activity.validate_computed_versions(base_version)?;
|
||||
self.cost_basis.validate_computed_versions(base_version)?;
|
||||
Ok(())
|
||||
}
|
||||
fn compute_then_truncate_push_unrealized_states(
|
||||
&mut self, height: Height, height_price: Cents, state: &mut CohortState,
|
||||
) -> Result<()> {
|
||||
state.apply_pending();
|
||||
self.cost_basis.truncate_push_minmax(height, state)?;
|
||||
let (height_unrealized_state, _) = state.compute_unrealized_states(height_price, None);
|
||||
self.unrealized.base.truncate_push(height, &height_unrealized_state)?;
|
||||
let spot = height_price.to_dollars();
|
||||
self.cost_basis.extended.truncate_push_percentiles(height, state, spot)?;
|
||||
Ok(())
|
||||
}
|
||||
fn collect_all_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
|
||||
let mut vecs: Vec<&mut dyn AnyStoredVec> = Vec::new();
|
||||
vecs.extend(self.supply.par_iter_mut().collect::<Vec<_>>());
|
||||
vecs.extend(self.outputs.par_iter_mut().collect::<Vec<_>>());
|
||||
vecs.extend(self.activity.par_iter_mut().collect::<Vec<_>>());
|
||||
vecs.extend(self.realized.collect_vecs_mut());
|
||||
vecs.extend(self.cost_basis.base.collect_vecs_mut());
|
||||
vecs.extend(self.cost_basis.extended.collect_vecs_mut());
|
||||
vecs.extend(self.unrealized.base.collect_vecs_mut());
|
||||
vecs.extend(self.unrealized.peak_regret_ext.collect_vecs_mut());
|
||||
vecs
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtendedAdjustedCohortMetrics {
|
||||
pub(crate) fn forced_import(
|
||||
cfg: &ImportConfig,
|
||||
all_supply: &SupplyMetrics,
|
||||
up_to_1h: &RealizedBase,
|
||||
) -> Result<Self> {
|
||||
let supply = SupplyMetrics::forced_import(cfg)?;
|
||||
let unrealized = UnrealizedWithPeakRegret::forced_import(cfg)?;
|
||||
let realized = RealizedWithExtendedAdjusted::forced_import(cfg, up_to_1h)?;
|
||||
|
||||
let relative = RelativeWithExtended::forced_import(
|
||||
cfg,
|
||||
&unrealized.base,
|
||||
&supply,
|
||||
all_supply,
|
||||
&realized.base,
|
||||
&unrealized.peak_regret_ext.peak_regret,
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
filter: cfg.filter.clone(),
|
||||
supply: Box::new(supply),
|
||||
outputs: Box::new(OutputsMetrics::forced_import(cfg)?),
|
||||
activity: Box::new(ActivityMetrics::forced_import(cfg)?),
|
||||
realized: Box::new(realized),
|
||||
cost_basis: Box::new(CostBasisWithExtended::forced_import(cfg)?),
|
||||
unrealized: Box::new(unrealized),
|
||||
relative: Box::new(relative),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn compute_rest_part2(
|
||||
&mut self,
|
||||
blocks: &blocks::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
height_to_market_cap: &impl ReadableVec<Height, Dollars>,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.realized.compute_rest_part2(
|
||||
blocks,
|
||||
prices,
|
||||
starting_indexes,
|
||||
&self.supply.total.btc.height,
|
||||
height_to_market_cap,
|
||||
exit,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
15
crates/brk_computer/src/distribution/metrics/cohort/mod.rs
Normal file
15
crates/brk_computer/src/distribution/metrics/cohort/mod.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
mod adjusted;
|
||||
mod all;
|
||||
mod basic;
|
||||
mod extended;
|
||||
mod extended_adjusted;
|
||||
|
||||
mod peak_regret;
|
||||
|
||||
pub use adjusted::*;
|
||||
pub use all::*;
|
||||
pub use basic::*;
|
||||
pub use extended::*;
|
||||
pub use extended_adjusted::*;
|
||||
|
||||
pub use peak_regret::*;
|
||||
@@ -0,0 +1,120 @@
|
||||
use brk_cohort::Filter;
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Cents, Dollars, Height, Version};
|
||||
use rayon::prelude::*;
|
||||
use vecdb::{AnyStoredVec, Exit, ReadableVec, Rw, StorageMode};
|
||||
|
||||
use crate::{ComputeIndexes, blocks, distribution::state::CohortState, prices};
|
||||
|
||||
use crate::distribution::metrics::{
|
||||
ActivityMetrics, CohortMetricsBase, CostBasisBase, ImportConfig, OutputsMetrics, RealizedBase,
|
||||
RelativeWithPeakRegret, SupplyMetrics, UnrealizedBase, UnrealizedWithPeakRegret,
|
||||
};
|
||||
|
||||
/// Cohort metrics with peak regret unrealized + relative (no extended, no adjusted).
|
||||
/// Used by: min_age cohorts.
|
||||
#[derive(Traversable)]
|
||||
pub struct PeakRegretCohortMetrics<M: StorageMode = Rw> {
|
||||
#[traversable(skip)]
|
||||
pub filter: Filter,
|
||||
pub supply: Box<SupplyMetrics<M>>,
|
||||
pub outputs: Box<OutputsMetrics<M>>,
|
||||
pub activity: Box<ActivityMetrics<M>>,
|
||||
pub realized: Box<RealizedBase<M>>,
|
||||
pub cost_basis: Box<CostBasisBase<M>>,
|
||||
pub unrealized: Box<UnrealizedWithPeakRegret<M>>,
|
||||
pub relative: Box<RelativeWithPeakRegret>,
|
||||
}
|
||||
|
||||
impl CohortMetricsBase for PeakRegretCohortMetrics {
|
||||
fn filter(&self) -> &Filter { &self.filter }
|
||||
fn supply(&self) -> &SupplyMetrics { &self.supply }
|
||||
fn supply_mut(&mut self) -> &mut SupplyMetrics { &mut self.supply }
|
||||
fn outputs(&self) -> &OutputsMetrics { &self.outputs }
|
||||
fn outputs_mut(&mut self) -> &mut OutputsMetrics { &mut self.outputs }
|
||||
fn activity(&self) -> &ActivityMetrics { &self.activity }
|
||||
fn activity_mut(&mut self) -> &mut ActivityMetrics { &mut self.activity }
|
||||
fn realized_base(&self) -> &RealizedBase { &self.realized }
|
||||
fn realized_base_mut(&mut self) -> &mut RealizedBase { &mut self.realized }
|
||||
fn unrealized_base(&self) -> &UnrealizedBase { &self.unrealized }
|
||||
fn unrealized_base_mut(&mut self) -> &mut UnrealizedBase { &mut self.unrealized }
|
||||
fn cost_basis_base(&self) -> &CostBasisBase { &self.cost_basis }
|
||||
fn cost_basis_base_mut(&mut self) -> &mut CostBasisBase { &mut self.cost_basis }
|
||||
|
||||
fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> {
|
||||
self.supply.validate_computed_versions(base_version)?;
|
||||
self.activity.validate_computed_versions(base_version)?;
|
||||
Ok(())
|
||||
}
|
||||
fn compute_then_truncate_push_unrealized_states(
|
||||
&mut self, height: Height, height_price: Cents, state: &mut CohortState,
|
||||
) -> Result<()> {
|
||||
state.apply_pending();
|
||||
self.cost_basis.truncate_push_minmax(height, state)?;
|
||||
let (height_unrealized_state, _) = state.compute_unrealized_states(height_price, None);
|
||||
self.unrealized.base.truncate_push(height, &height_unrealized_state)?;
|
||||
Ok(())
|
||||
}
|
||||
fn collect_all_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
|
||||
let mut vecs: Vec<&mut dyn AnyStoredVec> = Vec::new();
|
||||
vecs.extend(self.supply.par_iter_mut().collect::<Vec<_>>());
|
||||
vecs.extend(self.outputs.par_iter_mut().collect::<Vec<_>>());
|
||||
vecs.extend(self.activity.par_iter_mut().collect::<Vec<_>>());
|
||||
vecs.extend(self.realized.collect_vecs_mut());
|
||||
vecs.extend(self.cost_basis.collect_vecs_mut());
|
||||
vecs.extend(self.unrealized.base.collect_vecs_mut());
|
||||
vecs.extend(self.unrealized.peak_regret_ext.collect_vecs_mut());
|
||||
vecs
|
||||
}
|
||||
}
|
||||
|
||||
impl PeakRegretCohortMetrics {
|
||||
pub(crate) fn forced_import(
|
||||
cfg: &ImportConfig,
|
||||
all_supply: &SupplyMetrics,
|
||||
) -> Result<Self> {
|
||||
let supply = SupplyMetrics::forced_import(cfg)?;
|
||||
let unrealized = UnrealizedWithPeakRegret::forced_import(cfg)?;
|
||||
let realized = RealizedBase::forced_import(cfg)?;
|
||||
|
||||
let relative = RelativeWithPeakRegret::forced_import(
|
||||
cfg,
|
||||
&unrealized.base,
|
||||
&supply,
|
||||
all_supply,
|
||||
&realized,
|
||||
&unrealized.peak_regret_ext.peak_regret,
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
filter: cfg.filter.clone(),
|
||||
supply: Box::new(supply),
|
||||
outputs: Box::new(OutputsMetrics::forced_import(cfg)?),
|
||||
activity: Box::new(ActivityMetrics::forced_import(cfg)?),
|
||||
realized: Box::new(realized),
|
||||
cost_basis: Box::new(CostBasisBase::forced_import(cfg)?),
|
||||
unrealized: Box::new(unrealized),
|
||||
relative: Box::new(relative),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn compute_rest_part2(
|
||||
&mut self,
|
||||
blocks: &blocks::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
height_to_market_cap: &impl ReadableVec<Height, Dollars>,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.realized.compute_rest_part2_base(
|
||||
blocks,
|
||||
prices,
|
||||
starting_indexes,
|
||||
&self.supply.total.btc.height,
|
||||
height_to_market_cap,
|
||||
exit,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,8 +4,6 @@ use vecdb::Database;
|
||||
|
||||
use crate::{indexes, prices};
|
||||
|
||||
use super::RealizedMetrics;
|
||||
|
||||
/// Configuration for importing metrics.
|
||||
pub struct ImportConfig<'a> {
|
||||
pub db: &'a Database,
|
||||
@@ -15,9 +13,6 @@ pub struct ImportConfig<'a> {
|
||||
pub version: Version,
|
||||
pub indexes: &'a indexes::Vecs,
|
||||
pub prices: &'a prices::Vecs,
|
||||
/// Source for lazy adjusted computation: adjusted = cohort - up_to_1h.
|
||||
/// Required for cohorts where `compute_adjusted()` is true.
|
||||
pub up_to_1h_realized: Option<&'a RealizedMetrics>,
|
||||
}
|
||||
|
||||
impl<'a> ImportConfig<'a> {
|
||||
@@ -26,21 +21,6 @@ impl<'a> ImportConfig<'a> {
|
||||
self.filter.is_extended(self.context)
|
||||
}
|
||||
|
||||
/// Whether to compute relative-to-all metrics.
|
||||
pub(crate) fn compute_rel_to_all(&self) -> bool {
|
||||
self.filter.compute_rel_to_all()
|
||||
}
|
||||
|
||||
/// Whether to compute adjusted metrics (SOPR, etc.).
|
||||
pub(crate) fn compute_adjusted(&self) -> bool {
|
||||
self.filter.compute_adjusted(self.context)
|
||||
}
|
||||
|
||||
/// Whether to compute relative metrics (invested capital %, NUPL ratios, etc.).
|
||||
pub(crate) fn compute_relative(&self) -> bool {
|
||||
self.filter.compute_relative()
|
||||
}
|
||||
|
||||
/// Get full metric name with filter prefix.
|
||||
pub(crate) fn name(&self, suffix: &str) -> String {
|
||||
if self.full_name.is_empty() {
|
||||
@@ -52,12 +32,4 @@ impl<'a> ImportConfig<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether this cohort needs peak_regret metric.
|
||||
/// True for UTXO cohorts with age-based filters (all, term, time).
|
||||
/// age_range cohorts compute directly, others aggregate from age_range.
|
||||
pub(crate) fn compute_peak_regret(&self) -> bool {
|
||||
matches!(self.context, CohortContext::Utxo)
|
||||
&& matches!(self.filter, Filter::All | Filter::Term(_) | Filter::Time(_))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,248 +0,0 @@
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Dollars, Height, StoredF32, Version};
|
||||
use rayon::prelude::*;
|
||||
use vecdb::{AnyStoredVec, AnyVec, Exit, Rw, StorageMode, WritableVec};
|
||||
|
||||
use crate::{
|
||||
ComputeIndexes,
|
||||
distribution::state::CohortState,
|
||||
internal::{
|
||||
ComputedFromHeightLast, PERCENTILES_LEN, Price, PriceFromHeight, PercentilesVecs,
|
||||
compute_spot_percentile_rank,
|
||||
},
|
||||
};
|
||||
|
||||
use super::ImportConfig;
|
||||
|
||||
/// Cost basis metrics.
|
||||
#[derive(Traversable)]
|
||||
pub struct CostBasisMetrics<M: StorageMode = Rw> {
|
||||
/// Minimum cost basis for any UTXO at this height
|
||||
pub min: Price<ComputedFromHeightLast<Dollars, M>>,
|
||||
|
||||
/// Maximum cost basis for any UTXO at this height
|
||||
pub max: Price<ComputedFromHeightLast<Dollars, M>>,
|
||||
|
||||
/// Cost basis percentiles (sat-weighted)
|
||||
pub percentiles: Option<PercentilesVecs<M>>,
|
||||
|
||||
/// Invested capital percentiles (USD-weighted)
|
||||
pub invested_capital: Option<PercentilesVecs<M>>,
|
||||
|
||||
/// What percentile of cost basis is below spot (sat-weighted)
|
||||
pub spot_cost_basis_percentile: Option<ComputedFromHeightLast<StoredF32, M>>,
|
||||
|
||||
/// What percentile of invested capital is below spot (USD-weighted)
|
||||
pub spot_invested_capital_percentile: Option<ComputedFromHeightLast<StoredF32, M>>,
|
||||
}
|
||||
|
||||
impl CostBasisMetrics {
|
||||
/// Import cost basis metrics from database.
|
||||
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
|
||||
let extended = cfg.extended();
|
||||
|
||||
Ok(Self {
|
||||
min: PriceFromHeight::forced_import(
|
||||
cfg.db,
|
||||
&cfg.name("min_cost_basis"),
|
||||
cfg.version,
|
||||
cfg.indexes,
|
||||
)?,
|
||||
max: PriceFromHeight::forced_import(
|
||||
cfg.db,
|
||||
&cfg.name("max_cost_basis"),
|
||||
cfg.version,
|
||||
cfg.indexes,
|
||||
)?,
|
||||
percentiles: extended
|
||||
.then(|| {
|
||||
PercentilesVecs::forced_import(
|
||||
cfg.db,
|
||||
&cfg.name("cost_basis"),
|
||||
cfg.version,
|
||||
cfg.indexes,
|
||||
true,
|
||||
)
|
||||
})
|
||||
.transpose()?,
|
||||
invested_capital: extended
|
||||
.then(|| {
|
||||
PercentilesVecs::forced_import(
|
||||
cfg.db,
|
||||
&cfg.name("invested_capital"),
|
||||
cfg.version,
|
||||
cfg.indexes,
|
||||
true,
|
||||
)
|
||||
})
|
||||
.transpose()?,
|
||||
spot_cost_basis_percentile: extended
|
||||
.then(|| {
|
||||
ComputedFromHeightLast::forced_import(
|
||||
cfg.db,
|
||||
&cfg.name("spot_cost_basis_percentile"),
|
||||
cfg.version,
|
||||
cfg.indexes,
|
||||
)
|
||||
})
|
||||
.transpose()?,
|
||||
spot_invested_capital_percentile: extended
|
||||
.then(|| {
|
||||
ComputedFromHeightLast::forced_import(
|
||||
cfg.db,
|
||||
&cfg.name("spot_invested_capital_percentile"),
|
||||
cfg.version,
|
||||
cfg.indexes,
|
||||
)
|
||||
})
|
||||
.transpose()?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get minimum length across height-indexed vectors written in block loop.
|
||||
pub(crate) fn min_stateful_height_len(&self) -> usize {
|
||||
let mut min = self.min.height.len().min(self.max.height.len());
|
||||
if let Some(v) = &self.spot_cost_basis_percentile {
|
||||
min = min.min(v.height.len());
|
||||
}
|
||||
if let Some(v) = &self.spot_invested_capital_percentile {
|
||||
min = min.min(v.height.len());
|
||||
}
|
||||
if let Some(p) = &self.percentiles {
|
||||
min = min.min(p.min_stateful_height_len());
|
||||
}
|
||||
if let Some(p) = &self.invested_capital {
|
||||
min = min.min(p.min_stateful_height_len());
|
||||
}
|
||||
min
|
||||
}
|
||||
|
||||
/// Push min/max cost basis from state.
|
||||
pub(crate) fn truncate_push_minmax(&mut self, height: Height, state: &CohortState) -> Result<()> {
|
||||
self.min.height.truncate_push(
|
||||
height,
|
||||
state
|
||||
.cost_basis_data_first_key_value()
|
||||
.map(|(cents, _)| cents.into())
|
||||
.unwrap_or(Dollars::NAN),
|
||||
)?;
|
||||
self.max.height.truncate_push(
|
||||
height,
|
||||
state
|
||||
.cost_basis_data_last_key_value()
|
||||
.map(|(cents, _)| cents.into())
|
||||
.unwrap_or(Dollars::NAN),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Push cost basis percentiles and spot ranks at every height.
|
||||
pub(crate) fn truncate_push_percentiles(
|
||||
&mut self,
|
||||
height: Height,
|
||||
state: &mut CohortState,
|
||||
spot: Dollars,
|
||||
) -> Result<()> {
|
||||
let computed = state.compute_percentiles();
|
||||
|
||||
// Sat-weighted percentiles and spot rank
|
||||
let sat_prices = computed
|
||||
.as_ref()
|
||||
.map(|p| p.sat_weighted.map(|c| c.to_dollars()))
|
||||
.unwrap_or([Dollars::NAN; PERCENTILES_LEN]);
|
||||
|
||||
if let Some(percentiles) = self.percentiles.as_mut() {
|
||||
percentiles.truncate_push(height, &sat_prices)?;
|
||||
}
|
||||
if let Some(spot_pct) = self.spot_cost_basis_percentile.as_mut() {
|
||||
let rank = compute_spot_percentile_rank(&sat_prices, spot);
|
||||
spot_pct.height.truncate_push(height, rank)?;
|
||||
}
|
||||
|
||||
// USD-weighted percentiles and spot rank
|
||||
let usd_prices = computed
|
||||
.as_ref()
|
||||
.map(|p| p.usd_weighted.map(|c| c.to_dollars()))
|
||||
.unwrap_or([Dollars::NAN; PERCENTILES_LEN]);
|
||||
|
||||
if let Some(invested_capital) = self.invested_capital.as_mut() {
|
||||
invested_capital.truncate_push(height, &usd_prices)?;
|
||||
}
|
||||
if let Some(spot_pct) = self.spot_invested_capital_percentile.as_mut() {
|
||||
let rank = compute_spot_percentile_rank(&usd_prices, spot);
|
||||
spot_pct.height.truncate_push(height, rank)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns a parallel iterator over all vecs for parallel writing.
|
||||
pub(crate) fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
|
||||
let mut vecs: Vec<&mut dyn AnyStoredVec> = vec![&mut self.min.height, &mut self.max.height];
|
||||
if let Some(percentiles) = self.percentiles.as_mut() {
|
||||
vecs.extend(
|
||||
percentiles
|
||||
.vecs
|
||||
.iter_mut()
|
||||
.flatten()
|
||||
.map(|v| &mut v.height as &mut dyn AnyStoredVec),
|
||||
);
|
||||
}
|
||||
if let Some(invested_capital) = self.invested_capital.as_mut() {
|
||||
vecs.extend(
|
||||
invested_capital
|
||||
.vecs
|
||||
.iter_mut()
|
||||
.flatten()
|
||||
.map(|v| &mut v.height as &mut dyn AnyStoredVec),
|
||||
);
|
||||
}
|
||||
if let Some(v) = self.spot_cost_basis_percentile.as_mut() {
|
||||
vecs.push(&mut v.height);
|
||||
}
|
||||
if let Some(v) = self.spot_invested_capital_percentile.as_mut() {
|
||||
vecs.push(&mut v.height);
|
||||
}
|
||||
vecs.into_par_iter()
|
||||
}
|
||||
|
||||
/// Validate computed versions or reset if mismatched.
|
||||
pub(crate) fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> {
|
||||
if let Some(percentiles) = self.percentiles.as_mut() {
|
||||
percentiles.validate_computed_version_or_reset(base_version)?;
|
||||
}
|
||||
if let Some(invested_capital) = self.invested_capital.as_mut() {
|
||||
invested_capital.validate_computed_version_or_reset(base_version)?;
|
||||
}
|
||||
if let Some(v) = self.spot_cost_basis_percentile.as_mut() {
|
||||
v.height
|
||||
.validate_computed_version_or_reset(base_version)?;
|
||||
}
|
||||
if let Some(v) = self.spot_invested_capital_percentile.as_mut() {
|
||||
v.height
|
||||
.validate_computed_version_or_reset(base_version)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Compute aggregate values from separate cohorts.
|
||||
pub(crate) fn compute_from_stateful(
|
||||
&mut self,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
others: &[&Self],
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.min.height.compute_min_of_others(
|
||||
starting_indexes.height,
|
||||
&others.iter().map(|v| &v.min.height).collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
self.max.height.compute_max_of_others(
|
||||
starting_indexes.height,
|
||||
&others.iter().map(|v| &v.max.height).collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Dollars, Height};
|
||||
use vecdb::{AnyStoredVec, AnyVec, Exit, Rw, StorageMode, WritableVec};
|
||||
|
||||
use crate::{
|
||||
ComputeIndexes,
|
||||
distribution::state::CohortState,
|
||||
internal::{ComputedFromHeightLast, Price, PriceFromHeight},
|
||||
};
|
||||
|
||||
use crate::distribution::metrics::ImportConfig;
|
||||
|
||||
/// Base cost basis metrics (always computed).
|
||||
#[derive(Traversable)]
|
||||
pub struct CostBasisBase<M: StorageMode = Rw> {
|
||||
/// Minimum cost basis for any UTXO at this height
|
||||
pub min: Price<ComputedFromHeightLast<Dollars, M>>,
|
||||
|
||||
/// Maximum cost basis for any UTXO at this height
|
||||
pub max: Price<ComputedFromHeightLast<Dollars, M>>,
|
||||
}
|
||||
|
||||
impl CostBasisBase {
|
||||
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
|
||||
Ok(Self {
|
||||
min: PriceFromHeight::forced_import(
|
||||
cfg.db,
|
||||
&cfg.name("min_cost_basis"),
|
||||
cfg.version,
|
||||
cfg.indexes,
|
||||
)?,
|
||||
max: PriceFromHeight::forced_import(
|
||||
cfg.db,
|
||||
&cfg.name("max_cost_basis"),
|
||||
cfg.version,
|
||||
cfg.indexes,
|
||||
)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn min_stateful_height_len(&self) -> usize {
|
||||
self.min.height.len().min(self.max.height.len())
|
||||
}
|
||||
|
||||
pub(crate) fn truncate_push_minmax(
|
||||
&mut self,
|
||||
height: Height,
|
||||
state: &CohortState,
|
||||
) -> Result<()> {
|
||||
self.min.height.truncate_push(
|
||||
height,
|
||||
state
|
||||
.cost_basis_data_first_key_value()
|
||||
.map(|(cents, _)| cents.into())
|
||||
.unwrap_or(Dollars::NAN),
|
||||
)?;
|
||||
self.max.height.truncate_push(
|
||||
height,
|
||||
state
|
||||
.cost_basis_data_last_key_value()
|
||||
.map(|(cents, _)| cents.into())
|
||||
.unwrap_or(Dollars::NAN),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
|
||||
vec![
|
||||
&mut self.min.height as &mut dyn AnyStoredVec,
|
||||
&mut self.max.height,
|
||||
]
|
||||
}
|
||||
|
||||
pub(crate) fn compute_from_stateful(
|
||||
&mut self,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
others: &[&Self],
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.min.height.compute_min_of_others(
|
||||
starting_indexes.height,
|
||||
&others.iter().map(|v| &v.min.height).collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
self.max.height.compute_max_of_others(
|
||||
starting_indexes.height,
|
||||
&others.iter().map(|v| &v.max.height).collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Dollars, Height, StoredF32, Version};
|
||||
use vecdb::{AnyStoredVec, Rw, StorageMode, WritableVec};
|
||||
|
||||
use crate::{
|
||||
distribution::state::CohortState,
|
||||
internal::{
|
||||
ComputedFromHeightLast, PERCENTILES_LEN, PercentilesVecs, compute_spot_percentile_rank,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::distribution::metrics::ImportConfig;
|
||||
|
||||
/// Extended cost basis metrics (only for extended cohorts).
|
||||
#[derive(Traversable)]
|
||||
pub struct CostBasisExtended<M: StorageMode = Rw> {
|
||||
/// Cost basis percentiles (sat-weighted)
|
||||
pub percentiles: PercentilesVecs<M>,
|
||||
|
||||
/// Invested capital percentiles (USD-weighted)
|
||||
pub invested_capital: PercentilesVecs<M>,
|
||||
|
||||
/// What percentile of cost basis is below spot (sat-weighted)
|
||||
pub spot_cost_basis_percentile: ComputedFromHeightLast<StoredF32, M>,
|
||||
|
||||
/// What percentile of invested capital is below spot (USD-weighted)
|
||||
pub spot_invested_capital_percentile: ComputedFromHeightLast<StoredF32, M>,
|
||||
}
|
||||
|
||||
impl CostBasisExtended {
|
||||
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
|
||||
Ok(Self {
|
||||
percentiles: PercentilesVecs::forced_import(
|
||||
cfg.db,
|
||||
&cfg.name("cost_basis"),
|
||||
cfg.version,
|
||||
cfg.indexes,
|
||||
true,
|
||||
)?,
|
||||
invested_capital: PercentilesVecs::forced_import(
|
||||
cfg.db,
|
||||
&cfg.name("invested_capital"),
|
||||
cfg.version,
|
||||
cfg.indexes,
|
||||
true,
|
||||
)?,
|
||||
spot_cost_basis_percentile: ComputedFromHeightLast::forced_import(
|
||||
cfg.db,
|
||||
&cfg.name("spot_cost_basis_percentile"),
|
||||
cfg.version,
|
||||
cfg.indexes,
|
||||
)?,
|
||||
spot_invested_capital_percentile: ComputedFromHeightLast::forced_import(
|
||||
cfg.db,
|
||||
&cfg.name("spot_invested_capital_percentile"),
|
||||
cfg.version,
|
||||
cfg.indexes,
|
||||
)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn truncate_push_percentiles(
|
||||
&mut self,
|
||||
height: Height,
|
||||
state: &mut CohortState,
|
||||
spot: Dollars,
|
||||
) -> Result<()> {
|
||||
let computed = state.compute_percentiles();
|
||||
|
||||
let sat_prices = computed
|
||||
.as_ref()
|
||||
.map(|p| p.sat_weighted.map(|c| c.to_dollars()))
|
||||
.unwrap_or([Dollars::NAN; PERCENTILES_LEN]);
|
||||
|
||||
self.percentiles.truncate_push(height, &sat_prices)?;
|
||||
let rank = compute_spot_percentile_rank(&sat_prices, spot);
|
||||
self.spot_cost_basis_percentile
|
||||
.height
|
||||
.truncate_push(height, rank)?;
|
||||
|
||||
let usd_prices = computed
|
||||
.as_ref()
|
||||
.map(|p| p.usd_weighted.map(|c| c.to_dollars()))
|
||||
.unwrap_or([Dollars::NAN; PERCENTILES_LEN]);
|
||||
|
||||
self.invested_capital.truncate_push(height, &usd_prices)?;
|
||||
let rank = compute_spot_percentile_rank(&usd_prices, spot);
|
||||
self.spot_invested_capital_percentile
|
||||
.height
|
||||
.truncate_push(height, rank)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
|
||||
let mut vecs: Vec<&mut dyn AnyStoredVec> = Vec::new();
|
||||
vecs.extend(
|
||||
self.percentiles
|
||||
.vecs
|
||||
.iter_mut()
|
||||
.flatten()
|
||||
.map(|v| &mut v.height as &mut dyn AnyStoredVec),
|
||||
);
|
||||
vecs.extend(
|
||||
self.invested_capital
|
||||
.vecs
|
||||
.iter_mut()
|
||||
.flatten()
|
||||
.map(|v| &mut v.height as &mut dyn AnyStoredVec),
|
||||
);
|
||||
vecs.push(&mut self.spot_cost_basis_percentile.height);
|
||||
vecs.push(&mut self.spot_invested_capital_percentile.height);
|
||||
vecs
|
||||
}
|
||||
|
||||
pub(crate) fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> {
|
||||
self.percentiles
|
||||
.validate_computed_version_or_reset(base_version)?;
|
||||
self.invested_capital
|
||||
.validate_computed_version_or_reset(base_version)?;
|
||||
self.spot_cost_basis_percentile
|
||||
.height
|
||||
.validate_computed_version_or_reset(base_version)?;
|
||||
self.spot_invested_capital_percentile
|
||||
.height
|
||||
.validate_computed_version_or_reset(base_version)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
mod base;
|
||||
mod extended;
|
||||
|
||||
mod with_extended;
|
||||
|
||||
pub use base::*;
|
||||
pub use extended::*;
|
||||
|
||||
pub use with_extended::*;
|
||||
@@ -0,0 +1,35 @@
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::Version;
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use vecdb::{Rw, StorageMode};
|
||||
|
||||
use crate::distribution::metrics::ImportConfig;
|
||||
|
||||
use super::{CostBasisBase, CostBasisExtended};
|
||||
|
||||
/// Cost basis metrics with guaranteed extended (no Option).
|
||||
#[derive(Deref, DerefMut, Traversable)]
|
||||
#[traversable(merge)]
|
||||
pub struct CostBasisWithExtended<M: StorageMode = Rw> {
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
#[traversable(flatten)]
|
||||
pub base: CostBasisBase<M>,
|
||||
#[traversable(flatten)]
|
||||
pub extended: CostBasisExtended<M>,
|
||||
}
|
||||
|
||||
impl CostBasisWithExtended {
|
||||
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
|
||||
Ok(Self {
|
||||
base: CostBasisBase::forced_import(cfg)?,
|
||||
extended: CostBasisExtended::forced_import(cfg)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> {
|
||||
self.extended.validate_computed_versions(base_version)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
mod activity;
|
||||
mod cohort;
|
||||
mod config;
|
||||
mod cost_basis;
|
||||
mod outputs;
|
||||
@@ -8,6 +9,7 @@ mod supply;
|
||||
mod unrealized;
|
||||
|
||||
pub use activity::*;
|
||||
pub use cohort::*;
|
||||
pub use config::*;
|
||||
pub use cost_basis::*;
|
||||
pub use outputs::*;
|
||||
@@ -18,220 +20,123 @@ pub use unrealized::*;
|
||||
|
||||
use brk_cohort::Filter;
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Cents, Dollars, Height, Version};
|
||||
use rayon::prelude::*;
|
||||
use vecdb::{AnyStoredVec, Exit, ReadableVec, Rw, StorageMode};
|
||||
use brk_types::{Cents, Height, Version};
|
||||
use vecdb::{AnyStoredVec, Exit};
|
||||
|
||||
use crate::{ComputeIndexes, blocks, distribution::state::CohortState, prices};
|
||||
|
||||
/// All metrics for a cohort, organized by category.
|
||||
#[derive(Traversable)]
|
||||
pub struct CohortMetrics<M: StorageMode = Rw> {
|
||||
#[traversable(skip)]
|
||||
pub filter: Filter,
|
||||
/// Trait defining the interface for cohort metrics containers.
|
||||
///
|
||||
/// Provides typed accessor methods for base sub-metric components, default
|
||||
/// implementations for shared operations that only use base fields, and
|
||||
/// required methods for operations that vary by extension level.
|
||||
pub trait CohortMetricsBase: Send + Sync {
|
||||
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 activity(&self) -> &ActivityMetrics;
|
||||
fn activity_mut(&mut self) -> &mut ActivityMetrics;
|
||||
fn realized_base(&self) -> &RealizedBase;
|
||||
fn realized_base_mut(&mut self) -> &mut RealizedBase;
|
||||
fn unrealized_base(&self) -> &UnrealizedBase;
|
||||
fn unrealized_base_mut(&mut self) -> &mut UnrealizedBase;
|
||||
fn cost_basis_base(&self) -> &CostBasisBase;
|
||||
fn cost_basis_base_mut(&mut self) -> &mut CostBasisBase;
|
||||
|
||||
/// Supply metrics (always computed)
|
||||
pub supply: Box<SupplyMetrics<M>>,
|
||||
// === Required methods (vary by extension level) ===
|
||||
|
||||
/// Output metrics - UTXO count (always computed)
|
||||
pub outputs: Box<OutputsMetrics<M>>,
|
||||
/// Validate computed versions against base version.
|
||||
/// Extended types also validate cost_basis extended versions.
|
||||
fn validate_computed_versions(&mut self, base_version: Version) -> Result<()>;
|
||||
|
||||
/// Transaction activity (always computed)
|
||||
pub activity: Box<ActivityMetrics<M>>,
|
||||
/// Compute and push unrealized states.
|
||||
/// Extended types also push cost_basis percentiles.
|
||||
fn compute_then_truncate_push_unrealized_states(
|
||||
&mut self,
|
||||
height: Height,
|
||||
height_price: Cents,
|
||||
state: &mut CohortState,
|
||||
) -> Result<()>;
|
||||
|
||||
/// Realized cap and profit/loss
|
||||
pub realized: Box<RealizedMetrics<M>>,
|
||||
/// Collect all stored vecs for parallel writing.
|
||||
fn collect_all_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec>;
|
||||
|
||||
/// Unrealized profit/loss
|
||||
pub unrealized: Box<UnrealizedMetrics<M>>,
|
||||
|
||||
/// Cost basis metrics
|
||||
pub cost_basis: Box<CostBasisMetrics<M>>,
|
||||
|
||||
/// Relative metrics (not all cohorts compute this)
|
||||
pub relative: Option<Box<RelativeMetrics>>,
|
||||
}
|
||||
|
||||
impl CohortMetrics {
|
||||
/// Import all metrics from database.
|
||||
///
|
||||
/// `all_supply` is the supply metrics from the "all" cohort, used as global
|
||||
/// sources for `*_rel_to_market_cap` and `*_rel_to_circulating_supply` ratios.
|
||||
/// Pass `None` for the "all" cohort itself.
|
||||
pub(crate) fn forced_import(cfg: &ImportConfig, all_supply: Option<&SupplyMetrics>) -> Result<Self> {
|
||||
let supply = SupplyMetrics::forced_import(cfg)?;
|
||||
let outputs = OutputsMetrics::forced_import(cfg)?;
|
||||
|
||||
let unrealized = UnrealizedMetrics::forced_import(cfg)?;
|
||||
let realized = RealizedMetrics::forced_import(cfg)?;
|
||||
|
||||
let relative = cfg
|
||||
.compute_relative()
|
||||
.then(|| {
|
||||
RelativeMetrics::forced_import(
|
||||
cfg,
|
||||
&unrealized,
|
||||
&supply,
|
||||
all_supply,
|
||||
Some(&realized),
|
||||
)
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
Ok(Self {
|
||||
filter: cfg.filter.clone(),
|
||||
supply: Box::new(supply),
|
||||
outputs: Box::new(outputs),
|
||||
activity: Box::new(ActivityMetrics::forced_import(cfg)?),
|
||||
realized: Box::new(realized),
|
||||
cost_basis: Box::new(CostBasisMetrics::forced_import(cfg)?),
|
||||
relative: relative.map(Box::new),
|
||||
unrealized: Box::new(unrealized),
|
||||
})
|
||||
}
|
||||
// === Default methods (shared across all cohort metric types, use base fields only) ===
|
||||
|
||||
/// Get minimum length across height-indexed vectors written in block loop.
|
||||
pub(crate) fn min_stateful_height_len(&self) -> usize {
|
||||
self.supply
|
||||
fn min_stateful_height_len(&self) -> usize {
|
||||
self.supply()
|
||||
.min_len()
|
||||
.min(self.outputs.min_len())
|
||||
.min(self.activity.min_len())
|
||||
.min(self.realized.min_stateful_height_len())
|
||||
.min(self.unrealized.min_stateful_height_len())
|
||||
.min(self.cost_basis.min_stateful_height_len())
|
||||
.min(self.outputs().min_len())
|
||||
.min(self.activity().min_len())
|
||||
.min(self.realized_base().min_stateful_height_len())
|
||||
.min(self.unrealized_base().min_stateful_height_len())
|
||||
.min(self.cost_basis_base().min_stateful_height_len())
|
||||
}
|
||||
|
||||
/// Push state values to height-indexed vectors.
|
||||
pub(crate) fn truncate_push(&mut self, height: Height, state: &CohortState) -> Result<()> {
|
||||
self.supply.truncate_push(height, state.supply.value)?;
|
||||
self.outputs
|
||||
fn truncate_push(&mut self, height: Height, state: &CohortState) -> Result<()> {
|
||||
self.supply_mut()
|
||||
.truncate_push(height, state.supply.value)?;
|
||||
self.outputs_mut()
|
||||
.truncate_push(height, state.supply.utxo_count)?;
|
||||
self.activity.truncate_push(
|
||||
self.activity_mut().truncate_push(
|
||||
height,
|
||||
state.sent,
|
||||
state.satblocks_destroyed,
|
||||
state.satdays_destroyed,
|
||||
)?;
|
||||
|
||||
self.realized.truncate_push(height, &state.realized)?;
|
||||
|
||||
self.realized_base_mut()
|
||||
.truncate_push(height, &state.realized)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns a parallel iterator over all vecs for parallel writing.
|
||||
pub(crate) fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
|
||||
let mut vecs: Vec<&mut dyn AnyStoredVec> = Vec::new();
|
||||
|
||||
vecs.extend(self.supply.par_iter_mut().collect::<Vec<_>>());
|
||||
vecs.extend(self.outputs.par_iter_mut().collect::<Vec<_>>());
|
||||
vecs.extend(self.activity.par_iter_mut().collect::<Vec<_>>());
|
||||
vecs.extend(self.realized.par_iter_mut().collect::<Vec<_>>());
|
||||
vecs.extend(self.unrealized.par_iter_mut().collect::<Vec<_>>());
|
||||
vecs.extend(self.cost_basis.par_iter_mut().collect::<Vec<_>>());
|
||||
|
||||
vecs.into_par_iter()
|
||||
}
|
||||
|
||||
/// Validate computed versions against base version.
|
||||
pub(crate) fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> {
|
||||
self.supply.validate_computed_versions(base_version)?;
|
||||
self.activity.validate_computed_versions(base_version)?;
|
||||
self.realized.validate_computed_versions(base_version)?;
|
||||
self.cost_basis.validate_computed_versions(base_version)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Compute and push unrealized states and percentiles.
|
||||
pub(crate) fn compute_then_truncate_push_unrealized_states(
|
||||
&mut self,
|
||||
height: Height,
|
||||
height_price: Cents,
|
||||
state: &mut CohortState,
|
||||
) -> Result<()> {
|
||||
// Apply pending updates before reading
|
||||
state.apply_pending();
|
||||
|
||||
self.cost_basis.truncate_push_minmax(height, state)?;
|
||||
|
||||
let (height_unrealized_state, _) = state.compute_unrealized_states(height_price, None);
|
||||
|
||||
self.unrealized
|
||||
.truncate_push(height, &height_unrealized_state)?;
|
||||
|
||||
let spot = height_price.to_dollars();
|
||||
self.cost_basis
|
||||
.truncate_push_percentiles(height, state, spot)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Compute aggregate cohort values from separate cohorts.
|
||||
pub(crate) fn compute_from_stateful(
|
||||
/// Compute net_sentiment.height as capital-weighted average of component cohorts (same type).
|
||||
fn compute_net_sentiment_from_others(
|
||||
&mut self,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
others: &[&Self],
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.supply.compute_from_stateful(
|
||||
starting_indexes,
|
||||
&others.iter().map(|v| &*v.supply).collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
self.outputs.compute_from_stateful(
|
||||
starting_indexes,
|
||||
&others.iter().map(|v| &*v.outputs).collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
self.activity.compute_from_stateful(
|
||||
starting_indexes,
|
||||
&others.iter().map(|v| &*v.activity).collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
) -> Result<()>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let weights: Vec<_> = others
|
||||
.iter()
|
||||
.map(|o| &o.realized_base().realized_cap.height)
|
||||
.collect();
|
||||
let values: Vec<_> = others
|
||||
.iter()
|
||||
.map(|o| &o.unrealized_base().net_sentiment.height)
|
||||
.collect();
|
||||
|
||||
self.realized.compute_from_stateful(
|
||||
starting_indexes,
|
||||
&others.iter().map(|v| &*v.realized).collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
self.unrealized.compute_from_stateful(
|
||||
starting_indexes,
|
||||
&others.iter().map(|v| &*v.unrealized).collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
self.cost_basis.compute_from_stateful(
|
||||
starting_indexes,
|
||||
&others.iter().map(|v| &*v.cost_basis).collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
self.unrealized_base_mut()
|
||||
.net_sentiment
|
||||
.height
|
||||
.compute_weighted_average_of_others(starting_indexes.height, &weights, &values, exit)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Compute net_sentiment.height as capital-weighted average of component cohorts.
|
||||
///
|
||||
/// For aggregate cohorts, the simple greed-pain formula produces values outside
|
||||
/// the range of components due to asymmetric weighting. This computes net_sentiment
|
||||
/// as a proper weighted average using realized_cap as weight.
|
||||
///
|
||||
/// Only computes height; day1 derivation is done separately via compute_net_sentiment_rest.
|
||||
pub(crate) fn compute_net_sentiment_from_others(
|
||||
/// Compute net_sentiment.height as capital-weighted average from heterogeneous sources.
|
||||
fn compute_net_sentiment_from_others_dyn(
|
||||
&mut self,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
others: &[&Self],
|
||||
others: &[&dyn CohortMetricsBase],
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
let weights: Vec<_> = others
|
||||
.iter()
|
||||
.map(|o| &o.realized.realized_cap.height)
|
||||
.map(|o| &o.realized_base().realized_cap.height)
|
||||
.collect();
|
||||
let values: Vec<_> = others
|
||||
.iter()
|
||||
.map(|o| &o.unrealized.net_sentiment.height)
|
||||
.map(|o| &o.unrealized_base().net_sentiment.height)
|
||||
.collect();
|
||||
|
||||
self.unrealized
|
||||
self.unrealized_base_mut()
|
||||
.net_sentiment
|
||||
.height
|
||||
.compute_weighted_average_of_others(starting_indexes.height, &weights, &values, exit)?;
|
||||
@@ -240,55 +145,81 @@ impl CohortMetrics {
|
||||
}
|
||||
|
||||
/// First phase of computed metrics (indexes from height).
|
||||
pub(crate) fn compute_rest_part1(
|
||||
fn compute_rest_part1(
|
||||
&mut self,
|
||||
blocks: &blocks::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
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.supply_mut()
|
||||
.compute_rest_part1(blocks, starting_indexes, exit)?;
|
||||
self.outputs_mut()
|
||||
.compute_rest(blocks, starting_indexes, exit)?;
|
||||
self.activity_mut()
|
||||
.compute_rest_part1(blocks, starting_indexes, exit)?;
|
||||
|
||||
self.realized.compute_rest_part1(starting_indexes, exit)?;
|
||||
self.realized_base_mut()
|
||||
.compute_rest_part1(starting_indexes, exit)?;
|
||||
|
||||
self.unrealized
|
||||
self.unrealized_base_mut()
|
||||
.compute_rest(prices, starting_indexes, exit)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Second phase of computed metrics (ratios, relative values).
|
||||
pub(crate) fn compute_rest_part2(
|
||||
&mut self,
|
||||
blocks: &blocks::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
height_to_market_cap: Option<&impl ReadableVec<Height, Dollars>>,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.realized.compute_rest_part2(
|
||||
blocks,
|
||||
prices,
|
||||
starting_indexes,
|
||||
&self.supply.total.btc.height,
|
||||
height_to_market_cap,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Compute net_sentiment.height for separate cohorts (greed - pain).
|
||||
/// Called only for separate cohorts; aggregates compute via weighted average in compute_from_stateful.
|
||||
pub(crate) fn compute_net_sentiment_height(
|
||||
fn compute_net_sentiment_height(
|
||||
&mut self,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.unrealized
|
||||
self.unrealized_base_mut()
|
||||
.compute_net_sentiment_height(starting_indexes, exit)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Compute aggregate base metrics from heterogeneous source cohorts.
|
||||
/// Uses only base fields (supply, outputs, activity, realized_base, unrealized_base, cost_basis_base).
|
||||
fn compute_base_from_others(
|
||||
&mut self,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
others: &[&dyn CohortMetricsBase],
|
||||
exit: &Exit,
|
||||
) -> Result<()>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.supply_mut().compute_from_stateful(
|
||||
starting_indexes,
|
||||
&others.iter().map(|v| v.supply()).collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
self.outputs_mut().compute_from_stateful(
|
||||
starting_indexes,
|
||||
&others.iter().map(|v| v.outputs()).collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
self.activity_mut().compute_from_stateful(
|
||||
starting_indexes,
|
||||
&others.iter().map(|v| v.activity()).collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
self.realized_base_mut().compute_from_stateful(
|
||||
starting_indexes,
|
||||
&others.iter().map(|v| v.realized_base()).collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
self.unrealized_base_mut().compute_from_stateful(
|
||||
starting_indexes,
|
||||
&others.iter().map(|v| v.unrealized_base()).collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
self.cost_basis_base_mut().compute_from_stateful(
|
||||
starting_indexes,
|
||||
&others.iter().map(|v| v.cost_basis_base()).collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Dollars, StoredF64, Version};
|
||||
use vecdb::{Exit, Ident, ReadableCloneableVec, Rw, StorageMode};
|
||||
|
||||
use crate::{
|
||||
ComputeIndexes, blocks,
|
||||
internal::{
|
||||
ComputedFromHeightLast, DollarsMinus, LazyBinaryFromHeightLast,
|
||||
LazyFromHeightLast, Ratio64,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::distribution::metrics::ImportConfig;
|
||||
|
||||
use super::RealizedBase;
|
||||
|
||||
/// Adjusted realized metrics (only for adjusted cohorts: all, sth, max_age).
|
||||
#[derive(Traversable)]
|
||||
pub struct RealizedAdjusted<M: StorageMode = Rw> {
|
||||
// === Adjusted Value (lazy: cohort - up_to_1h) ===
|
||||
pub adjusted_value_created: LazyBinaryFromHeightLast<Dollars, Dollars, Dollars>,
|
||||
pub adjusted_value_destroyed: LazyBinaryFromHeightLast<Dollars, Dollars, Dollars>,
|
||||
|
||||
// === Adjusted Value Created/Destroyed Rolling Sums ===
|
||||
pub adjusted_value_created_24h: ComputedFromHeightLast<Dollars, M>,
|
||||
pub adjusted_value_created_7d: ComputedFromHeightLast<Dollars, M>,
|
||||
pub adjusted_value_created_30d: ComputedFromHeightLast<Dollars, M>,
|
||||
pub adjusted_value_created_1y: ComputedFromHeightLast<Dollars, M>,
|
||||
pub adjusted_value_destroyed_24h: ComputedFromHeightLast<Dollars, M>,
|
||||
pub adjusted_value_destroyed_7d: ComputedFromHeightLast<Dollars, M>,
|
||||
pub adjusted_value_destroyed_30d: ComputedFromHeightLast<Dollars, M>,
|
||||
pub adjusted_value_destroyed_1y: ComputedFromHeightLast<Dollars, M>,
|
||||
|
||||
// === Adjusted SOPR (rolling window ratios) ===
|
||||
pub adjusted_sopr: LazyFromHeightLast<StoredF64>,
|
||||
pub adjusted_sopr_24h: LazyBinaryFromHeightLast<StoredF64, Dollars, Dollars>,
|
||||
pub adjusted_sopr_7d: LazyBinaryFromHeightLast<StoredF64, Dollars, Dollars>,
|
||||
pub adjusted_sopr_30d: LazyBinaryFromHeightLast<StoredF64, Dollars, Dollars>,
|
||||
pub adjusted_sopr_1y: LazyBinaryFromHeightLast<StoredF64, Dollars, Dollars>,
|
||||
pub adjusted_sopr_24h_7d_ema: ComputedFromHeightLast<StoredF64, M>,
|
||||
pub adjusted_sopr_7d_ema: LazyFromHeightLast<StoredF64>,
|
||||
pub adjusted_sopr_24h_30d_ema: ComputedFromHeightLast<StoredF64, M>,
|
||||
pub adjusted_sopr_30d_ema: LazyFromHeightLast<StoredF64>,
|
||||
}
|
||||
|
||||
impl RealizedAdjusted {
|
||||
pub(crate) fn forced_import(
|
||||
cfg: &ImportConfig,
|
||||
base: &RealizedBase,
|
||||
up_to_1h: &RealizedBase,
|
||||
) -> Result<Self> {
|
||||
let v1 = Version::ONE;
|
||||
|
||||
macro_rules! import_rolling {
|
||||
($name:expr) => {
|
||||
ComputedFromHeightLast::forced_import(cfg.db, &cfg.name($name), cfg.version + v1, cfg.indexes)?
|
||||
};
|
||||
}
|
||||
|
||||
let adjusted_value_created = LazyBinaryFromHeightLast::from_both_binary_block::<
|
||||
DollarsMinus, Dollars, Dollars, Dollars, Dollars,
|
||||
>(
|
||||
&cfg.name("adjusted_value_created"),
|
||||
cfg.version,
|
||||
&base.value_created,
|
||||
&up_to_1h.value_created,
|
||||
);
|
||||
let adjusted_value_destroyed = LazyBinaryFromHeightLast::from_both_binary_block::<
|
||||
DollarsMinus, Dollars, Dollars, Dollars, Dollars,
|
||||
>(
|
||||
&cfg.name("adjusted_value_destroyed"),
|
||||
cfg.version,
|
||||
&base.value_destroyed,
|
||||
&up_to_1h.value_destroyed,
|
||||
);
|
||||
|
||||
let adjusted_value_created_24h = import_rolling!("adjusted_value_created_24h");
|
||||
let adjusted_value_created_7d = import_rolling!("adjusted_value_created_7d");
|
||||
let adjusted_value_created_30d = import_rolling!("adjusted_value_created_30d");
|
||||
let adjusted_value_created_1y = import_rolling!("adjusted_value_created_1y");
|
||||
let adjusted_value_destroyed_24h = import_rolling!("adjusted_value_destroyed_24h");
|
||||
let adjusted_value_destroyed_7d = import_rolling!("adjusted_value_destroyed_7d");
|
||||
let adjusted_value_destroyed_30d = import_rolling!("adjusted_value_destroyed_30d");
|
||||
let adjusted_value_destroyed_1y = import_rolling!("adjusted_value_destroyed_1y");
|
||||
|
||||
let adjusted_sopr_24h = LazyBinaryFromHeightLast::from_computed_last::<Ratio64>(
|
||||
&cfg.name("adjusted_sopr_24h"), cfg.version + v1, &adjusted_value_created_24h, &adjusted_value_destroyed_24h,
|
||||
);
|
||||
let adjusted_sopr_7d = LazyBinaryFromHeightLast::from_computed_last::<Ratio64>(
|
||||
&cfg.name("adjusted_sopr_7d"), cfg.version + v1, &adjusted_value_created_7d, &adjusted_value_destroyed_7d,
|
||||
);
|
||||
let adjusted_sopr_30d = LazyBinaryFromHeightLast::from_computed_last::<Ratio64>(
|
||||
&cfg.name("adjusted_sopr_30d"), cfg.version + v1, &adjusted_value_created_30d, &adjusted_value_destroyed_30d,
|
||||
);
|
||||
let adjusted_sopr_1y = LazyBinaryFromHeightLast::from_computed_last::<Ratio64>(
|
||||
&cfg.name("adjusted_sopr_1y"), cfg.version + v1, &adjusted_value_created_1y, &adjusted_value_destroyed_1y,
|
||||
);
|
||||
let adjusted_sopr = LazyFromHeightLast::from_binary::<Ident, Dollars, Dollars>(
|
||||
&cfg.name("adjusted_sopr"), cfg.version + v1, &adjusted_sopr_24h,
|
||||
);
|
||||
|
||||
let adjusted_sopr_24h_7d_ema = import_rolling!("adjusted_sopr_24h_7d_ema");
|
||||
let adjusted_sopr_7d_ema = LazyFromHeightLast::from_computed::<Ident>(
|
||||
&cfg.name("adjusted_sopr_7d_ema"), cfg.version + v1,
|
||||
adjusted_sopr_24h_7d_ema.height.read_only_boxed_clone(), &adjusted_sopr_24h_7d_ema,
|
||||
);
|
||||
let adjusted_sopr_24h_30d_ema = import_rolling!("adjusted_sopr_24h_30d_ema");
|
||||
let adjusted_sopr_30d_ema = LazyFromHeightLast::from_computed::<Ident>(
|
||||
&cfg.name("adjusted_sopr_30d_ema"), cfg.version + v1,
|
||||
adjusted_sopr_24h_30d_ema.height.read_only_boxed_clone(), &adjusted_sopr_24h_30d_ema,
|
||||
);
|
||||
|
||||
Ok(RealizedAdjusted {
|
||||
adjusted_value_created,
|
||||
adjusted_value_destroyed,
|
||||
adjusted_value_created_24h,
|
||||
adjusted_value_created_7d,
|
||||
adjusted_value_created_30d,
|
||||
adjusted_value_created_1y,
|
||||
adjusted_value_destroyed_24h,
|
||||
adjusted_value_destroyed_7d,
|
||||
adjusted_value_destroyed_30d,
|
||||
adjusted_value_destroyed_1y,
|
||||
adjusted_sopr,
|
||||
adjusted_sopr_24h,
|
||||
adjusted_sopr_7d,
|
||||
adjusted_sopr_30d,
|
||||
adjusted_sopr_1y,
|
||||
adjusted_sopr_24h_7d_ema,
|
||||
adjusted_sopr_7d_ema,
|
||||
adjusted_sopr_24h_30d_ema,
|
||||
adjusted_sopr_30d_ema,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn compute_rest_part2_adj(
|
||||
&mut self,
|
||||
blocks: &blocks::Vecs,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
// Adjusted value created/destroyed rolling sums
|
||||
self.adjusted_value_created_24h.height.compute_rolling_sum(starting_indexes.height, &blocks.count.height_24h_ago, &self.adjusted_value_created.height, exit)?;
|
||||
self.adjusted_value_created_7d.height.compute_rolling_sum(starting_indexes.height, &blocks.count.height_1w_ago, &self.adjusted_value_created.height, exit)?;
|
||||
self.adjusted_value_created_30d.height.compute_rolling_sum(starting_indexes.height, &blocks.count.height_1m_ago, &self.adjusted_value_created.height, exit)?;
|
||||
self.adjusted_value_created_1y.height.compute_rolling_sum(starting_indexes.height, &blocks.count.height_1y_ago, &self.adjusted_value_created.height, exit)?;
|
||||
|
||||
self.adjusted_value_destroyed_24h.height.compute_rolling_sum(starting_indexes.height, &blocks.count.height_24h_ago, &self.adjusted_value_destroyed.height, exit)?;
|
||||
self.adjusted_value_destroyed_7d.height.compute_rolling_sum(starting_indexes.height, &blocks.count.height_1w_ago, &self.adjusted_value_destroyed.height, exit)?;
|
||||
self.adjusted_value_destroyed_30d.height.compute_rolling_sum(starting_indexes.height, &blocks.count.height_1m_ago, &self.adjusted_value_destroyed.height, exit)?;
|
||||
self.adjusted_value_destroyed_1y.height.compute_rolling_sum(starting_indexes.height, &blocks.count.height_1y_ago, &self.adjusted_value_destroyed.height, exit)?;
|
||||
|
||||
// Adjusted SOPR EMAs
|
||||
self.adjusted_sopr_24h_7d_ema.height.compute_rolling_average(
|
||||
starting_indexes.height,
|
||||
&blocks.count.height_1w_ago,
|
||||
&self.adjusted_sopr.height,
|
||||
exit,
|
||||
)?;
|
||||
self.adjusted_sopr_24h_30d_ema.height.compute_rolling_average(
|
||||
starting_indexes.height,
|
||||
&blocks.count.height_1m_ago,
|
||||
&self.adjusted_sopr.height,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ use brk_types::{
|
||||
Bitcoin, Cents, CentsSats, CentsSquaredSats, Dollars, Height, StoredF32, StoredF64,
|
||||
Version,
|
||||
};
|
||||
use rayon::prelude::*;
|
||||
use vecdb::{
|
||||
AnyStoredVec, AnyVec, BytesVec, Exit, WritableVec, Ident, ImportableVec,
|
||||
ReadableCloneableVec, ReadableVec, Negate, Rw, StorageMode,
|
||||
@@ -15,7 +14,7 @@ use crate::{
|
||||
distribution::state::RealizedState,
|
||||
internal::{
|
||||
CentsUnsignedToDollars, ComputedFromHeightCum, ComputedFromHeightLast,
|
||||
ComputedFromHeightRatio, DollarsMinus, DollarsPlus,
|
||||
ComputedFromHeightRatio, DollarsPlus,
|
||||
DollarsSquaredDivide, LazyBinaryFromHeightLast,
|
||||
LazyBinaryPriceFromHeight, LazyComputedValueFromHeightCum, LazyFromHeightLast,
|
||||
LazyPriceFromCents, PercentageDollarsF32, Price, PriceFromHeight,
|
||||
@@ -24,36 +23,32 @@ use crate::{
|
||||
prices,
|
||||
};
|
||||
|
||||
use super::ImportConfig;
|
||||
use crate::distribution::metrics::ImportConfig;
|
||||
|
||||
/// Realized cap and related metrics.
|
||||
/// Base realized metrics (always computed).
|
||||
#[derive(Traversable)]
|
||||
pub struct RealizedMetrics<M: StorageMode = Rw> {
|
||||
pub struct RealizedBase<M: StorageMode = Rw> {
|
||||
// === Realized Cap ===
|
||||
pub realized_cap_cents: ComputedFromHeightLast<Cents, M>,
|
||||
pub realized_cap: LazyFromHeightLast<Dollars, Cents>,
|
||||
pub realized_price: Price<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub realized_price_extra: ComputedFromHeightRatio<M>,
|
||||
pub realized_cap_rel_to_own_market_cap: Option<ComputedFromHeightLast<StoredF32, M>>,
|
||||
pub realized_cap_30d_delta: ComputedFromHeightLast<Dollars, M>,
|
||||
|
||||
// === Investor Price (dollar-weighted average acquisition price) ===
|
||||
// === Investor Price ===
|
||||
pub investor_price_cents: ComputedFromHeightLast<Cents, M>,
|
||||
pub investor_price: LazyPriceFromCents,
|
||||
pub investor_price_extra: ComputedFromHeightRatio<M>,
|
||||
|
||||
// === Floor/Ceiling Price Bands (lazy: realized²/investor, investor²/realized) ===
|
||||
// === Floor/Ceiling Price Bands ===
|
||||
pub lower_price_band: LazyBinaryPriceFromHeight,
|
||||
pub upper_price_band: LazyBinaryPriceFromHeight,
|
||||
|
||||
// === Raw values for aggregation (needed to compute investor_price for aggregated cohorts) ===
|
||||
/// Raw Σ(price × sats) for realized cap aggregation
|
||||
// === Raw values for aggregation ===
|
||||
pub cap_raw: M::Stored<BytesVec<Height, CentsSats>>,
|
||||
/// Raw Σ(price² × sats) for investor_price aggregation
|
||||
pub investor_cap_raw: M::Stored<BytesVec<Height, CentsSquaredSats>>,
|
||||
|
||||
// === MVRV (Market Value to Realized Value) ===
|
||||
// Proxy for realized_price_extra.ratio (close / realized_price = market_cap / realized_cap)
|
||||
// === MVRV ===
|
||||
pub mvrv: LazyFromHeightLast<StoredF32>,
|
||||
|
||||
// === Realized Profit/Loss ===
|
||||
@@ -76,29 +71,13 @@ pub struct RealizedMetrics<M: StorageMode = Rw> {
|
||||
// === Total Realized PnL ===
|
||||
pub total_realized_pnl: LazyFromHeightLast<Dollars>,
|
||||
|
||||
// === Realized Profit/Loss Rolling Sums ===
|
||||
pub realized_profit_24h: Option<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub realized_profit_7d: Option<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub realized_profit_30d: Option<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub realized_profit_1y: Option<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub realized_loss_24h: Option<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub realized_loss_7d: Option<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub realized_loss_30d: Option<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub realized_loss_1y: Option<ComputedFromHeightLast<Dollars, M>>,
|
||||
|
||||
// === Realized Profit to Loss Ratio (lazy from rolling sums) ===
|
||||
pub realized_profit_to_loss_ratio_24h: Option<LazyBinaryFromHeightLast<StoredF64, Dollars, Dollars>>,
|
||||
pub realized_profit_to_loss_ratio_7d: Option<LazyBinaryFromHeightLast<StoredF64, Dollars, Dollars>>,
|
||||
pub realized_profit_to_loss_ratio_30d: Option<LazyBinaryFromHeightLast<StoredF64, Dollars, Dollars>>,
|
||||
pub realized_profit_to_loss_ratio_1y: Option<LazyBinaryFromHeightLast<StoredF64, Dollars, Dollars>>,
|
||||
|
||||
// === Value Created/Destroyed Splits (stored) ===
|
||||
pub profit_value_created: ComputedFromHeightLast<Dollars, M>,
|
||||
pub profit_value_destroyed: ComputedFromHeightLast<Dollars, M>,
|
||||
pub loss_value_created: ComputedFromHeightLast<Dollars, M>,
|
||||
pub loss_value_destroyed: ComputedFromHeightLast<Dollars, M>,
|
||||
|
||||
// === Value Created/Destroyed Totals (lazy: profit + loss) ===
|
||||
// === Value Created/Destroyed Totals (lazy) ===
|
||||
pub value_created: LazyBinaryFromHeightLast<Dollars, Dollars, Dollars>,
|
||||
pub value_destroyed: LazyBinaryFromHeightLast<Dollars, Dollars, Dollars>,
|
||||
|
||||
@@ -106,10 +85,6 @@ pub struct RealizedMetrics<M: StorageMode = Rw> {
|
||||
pub capitulation_flow: LazyFromHeightLast<Dollars>,
|
||||
pub profit_flow: LazyFromHeightLast<Dollars>,
|
||||
|
||||
// === Adjusted Value (lazy: cohort - up_to_1h) ===
|
||||
pub adjusted_value_created: Option<LazyBinaryFromHeightLast<Dollars, Dollars, Dollars>>,
|
||||
pub adjusted_value_destroyed: Option<LazyBinaryFromHeightLast<Dollars, Dollars, Dollars>>,
|
||||
|
||||
// === Value Created/Destroyed Rolling Sums ===
|
||||
pub value_created_24h: ComputedFromHeightLast<Dollars, M>,
|
||||
pub value_created_7d: ComputedFromHeightLast<Dollars, M>,
|
||||
@@ -131,27 +106,6 @@ pub struct RealizedMetrics<M: StorageMode = Rw> {
|
||||
pub sopr_24h_30d_ema: ComputedFromHeightLast<StoredF64, M>,
|
||||
pub sopr_30d_ema: LazyFromHeightLast<StoredF64>,
|
||||
|
||||
// === Adjusted Value Created/Destroyed Rolling Sums ===
|
||||
pub adjusted_value_created_24h: Option<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub adjusted_value_created_7d: Option<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub adjusted_value_created_30d: Option<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub adjusted_value_created_1y: Option<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub adjusted_value_destroyed_24h: Option<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub adjusted_value_destroyed_7d: Option<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub adjusted_value_destroyed_30d: Option<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub adjusted_value_destroyed_1y: Option<ComputedFromHeightLast<Dollars, M>>,
|
||||
|
||||
// === Adjusted SOPR (rolling window ratios) ===
|
||||
pub adjusted_sopr: Option<LazyFromHeightLast<StoredF64>>,
|
||||
pub adjusted_sopr_24h: Option<LazyBinaryFromHeightLast<StoredF64, Dollars, Dollars>>,
|
||||
pub adjusted_sopr_7d: Option<LazyBinaryFromHeightLast<StoredF64, Dollars, Dollars>>,
|
||||
pub adjusted_sopr_30d: Option<LazyBinaryFromHeightLast<StoredF64, Dollars, Dollars>>,
|
||||
pub adjusted_sopr_1y: Option<LazyBinaryFromHeightLast<StoredF64, Dollars, Dollars>>,
|
||||
pub adjusted_sopr_24h_7d_ema: Option<ComputedFromHeightLast<StoredF64, M>>,
|
||||
pub adjusted_sopr_7d_ema: Option<LazyFromHeightLast<StoredF64>>,
|
||||
pub adjusted_sopr_24h_30d_ema: Option<ComputedFromHeightLast<StoredF64, M>>,
|
||||
pub adjusted_sopr_30d_ema: Option<LazyFromHeightLast<StoredF64>>,
|
||||
|
||||
// === Sell Side Risk Rolling Sum Intermediates ===
|
||||
pub realized_value_24h: ComputedFromHeightLast<Dollars, M>,
|
||||
pub realized_value_7d: ComputedFromHeightLast<Dollars, M>,
|
||||
@@ -175,32 +129,23 @@ pub struct RealizedMetrics<M: StorageMode = Rw> {
|
||||
pub net_realized_pnl_cumulative_30d_delta_rel_to_market_cap: ComputedFromHeightLast<StoredF32, M>,
|
||||
|
||||
// === Peak Regret ===
|
||||
/// Realized peak regret: Σ((peak - sell_price) × sats)
|
||||
/// where peak = max price during holding period.
|
||||
/// "How much more could have been made by selling at peak instead"
|
||||
pub peak_regret: ComputedFromHeightCum<Dollars, M>,
|
||||
/// Peak regret as % of realized cap
|
||||
pub peak_regret_rel_to_realized_cap: LazyBinaryFromHeightLast<StoredF32, Dollars, Dollars>,
|
||||
|
||||
// === Sent in Profit/Loss ===
|
||||
/// Sats sent in profit (sats/btc/usd)
|
||||
pub sent_in_profit: LazyComputedValueFromHeightCum<M>,
|
||||
/// 14-day EMA of sent in profit (sats, btc, usd)
|
||||
pub sent_in_profit_14d_ema: ValueEmaFromHeight<M>,
|
||||
/// Sats sent in loss (sats/btc/usd)
|
||||
pub sent_in_loss: LazyComputedValueFromHeightCum<M>,
|
||||
/// 14-day EMA of sent in loss (sats, btc, usd)
|
||||
pub sent_in_loss_14d_ema: ValueEmaFromHeight<M>,
|
||||
}
|
||||
|
||||
impl RealizedMetrics {
|
||||
/// Import realized metrics from database.
|
||||
impl RealizedBase {
|
||||
/// Import realized base metrics from database.
|
||||
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
|
||||
let v1 = Version::ONE;
|
||||
let v2 = Version::new(2);
|
||||
let v3 = Version::new(3);
|
||||
let extended = cfg.extended();
|
||||
let compute_adjusted = cfg.compute_adjusted();
|
||||
|
||||
// Import combined types using forced_import which handles height + derived
|
||||
let realized_cap_cents = ComputedFromHeightLast::forced_import(
|
||||
@@ -273,7 +218,6 @@ impl RealizedMetrics {
|
||||
cfg.indexes,
|
||||
)?;
|
||||
|
||||
// realized_value is the source for total_realized_pnl (they're identical)
|
||||
let realized_value = ComputedFromHeightLast::forced_import(
|
||||
cfg.db,
|
||||
&cfg.name("realized_value"),
|
||||
@@ -281,7 +225,6 @@ impl RealizedMetrics {
|
||||
cfg.indexes,
|
||||
)?;
|
||||
|
||||
// total_realized_pnl is a lazy alias to realized_value
|
||||
let total_realized_pnl = LazyFromHeightLast::from_computed::<Ident>(
|
||||
&cfg.name("total_realized_pnl"),
|
||||
cfg.version + v1,
|
||||
@@ -289,7 +232,6 @@ impl RealizedMetrics {
|
||||
&realized_value,
|
||||
);
|
||||
|
||||
// Construct lazy ratio vecs
|
||||
let realized_profit_rel_to_realized_cap =
|
||||
LazyBinaryFromHeightLast::from_block_last_and_lazy_block_last::<PercentageDollarsF32, _>(
|
||||
&cfg.name("realized_profit_rel_to_realized_cap"),
|
||||
@@ -321,7 +263,6 @@ impl RealizedMetrics {
|
||||
cfg.indexes,
|
||||
)?;
|
||||
|
||||
// Investor price (dollar-weighted average acquisition price)
|
||||
let investor_price_cents = ComputedFromHeightLast::forced_import(
|
||||
cfg.db,
|
||||
&cfg.name("investor_price_cents"),
|
||||
@@ -344,7 +285,6 @@ impl RealizedMetrics {
|
||||
extended,
|
||||
)?;
|
||||
|
||||
// Floor price = realized² / investor (lower band)
|
||||
let lower_price_band =
|
||||
LazyBinaryPriceFromHeight::from_price_and_lazy_price::<DollarsSquaredDivide>(
|
||||
&cfg.name("lower_price_band"),
|
||||
@@ -353,7 +293,6 @@ impl RealizedMetrics {
|
||||
&investor_price,
|
||||
);
|
||||
|
||||
// Ceiling price = investor² / realized (upper band)
|
||||
let upper_price_band =
|
||||
LazyBinaryPriceFromHeight::from_lazy_price_and_price::<DollarsSquaredDivide>(
|
||||
&cfg.name("upper_price_band"),
|
||||
@@ -362,33 +301,28 @@ impl RealizedMetrics {
|
||||
&realized_price,
|
||||
);
|
||||
|
||||
// Raw values for aggregation
|
||||
let cap_raw = BytesVec::forced_import(cfg.db, &cfg.name("cap_raw"), cfg.version)?;
|
||||
let investor_cap_raw =
|
||||
BytesVec::forced_import(cfg.db, &cfg.name("investor_cap_raw"), cfg.version)?;
|
||||
|
||||
// Import the 4 splits (stored)
|
||||
let profit_value_created = ComputedFromHeightLast::forced_import(
|
||||
cfg.db,
|
||||
&cfg.name("profit_value_created"),
|
||||
cfg.version,
|
||||
cfg.indexes,
|
||||
)?;
|
||||
|
||||
let profit_value_destroyed = ComputedFromHeightLast::forced_import(
|
||||
cfg.db,
|
||||
&cfg.name("profit_value_destroyed"),
|
||||
cfg.version,
|
||||
cfg.indexes,
|
||||
)?;
|
||||
|
||||
let loss_value_created = ComputedFromHeightLast::forced_import(
|
||||
cfg.db,
|
||||
&cfg.name("loss_value_created"),
|
||||
cfg.version,
|
||||
cfg.indexes,
|
||||
)?;
|
||||
|
||||
let loss_value_destroyed = ComputedFromHeightLast::forced_import(
|
||||
cfg.db,
|
||||
&cfg.name("loss_value_destroyed"),
|
||||
@@ -396,14 +330,12 @@ impl RealizedMetrics {
|
||||
cfg.indexes,
|
||||
)?;
|
||||
|
||||
// Create lazy totals (profit + loss)
|
||||
let value_created = LazyBinaryFromHeightLast::from_computed_last::<DollarsPlus>(
|
||||
&cfg.name("value_created"),
|
||||
cfg.version,
|
||||
&profit_value_created,
|
||||
&loss_value_created,
|
||||
);
|
||||
|
||||
let value_destroyed = LazyBinaryFromHeightLast::from_computed_last::<DollarsPlus>(
|
||||
&cfg.name("value_destroyed"),
|
||||
cfg.version,
|
||||
@@ -411,14 +343,12 @@ impl RealizedMetrics {
|
||||
&loss_value_destroyed,
|
||||
);
|
||||
|
||||
// Create lazy aliases
|
||||
let capitulation_flow = LazyFromHeightLast::from_computed::<Ident>(
|
||||
&cfg.name("capitulation_flow"),
|
||||
cfg.version,
|
||||
loss_value_destroyed.height.read_only_boxed_clone(),
|
||||
&loss_value_destroyed,
|
||||
);
|
||||
|
||||
let profit_flow = LazyFromHeightLast::from_computed::<Ident>(
|
||||
&cfg.name("profit_flow"),
|
||||
cfg.version,
|
||||
@@ -426,41 +356,6 @@ impl RealizedMetrics {
|
||||
&profit_value_destroyed,
|
||||
);
|
||||
|
||||
// Create lazy adjusted vecs if compute_adjusted and up_to_1h is available
|
||||
let adjusted_value_created =
|
||||
(compute_adjusted && cfg.up_to_1h_realized.is_some()).then(|| {
|
||||
let up_to_1h = cfg.up_to_1h_realized.unwrap();
|
||||
LazyBinaryFromHeightLast::from_both_binary_block::<
|
||||
DollarsMinus,
|
||||
Dollars,
|
||||
Dollars,
|
||||
Dollars,
|
||||
Dollars,
|
||||
>(
|
||||
&cfg.name("adjusted_value_created"),
|
||||
cfg.version,
|
||||
&value_created,
|
||||
&up_to_1h.value_created,
|
||||
)
|
||||
});
|
||||
let adjusted_value_destroyed =
|
||||
(compute_adjusted && cfg.up_to_1h_realized.is_some()).then(|| {
|
||||
let up_to_1h = cfg.up_to_1h_realized.unwrap();
|
||||
LazyBinaryFromHeightLast::from_both_binary_block::<
|
||||
DollarsMinus,
|
||||
Dollars,
|
||||
Dollars,
|
||||
Dollars,
|
||||
Dollars,
|
||||
>(
|
||||
&cfg.name("adjusted_value_destroyed"),
|
||||
cfg.version,
|
||||
&value_destroyed,
|
||||
&up_to_1h.value_destroyed,
|
||||
)
|
||||
});
|
||||
|
||||
// Create realized_price_extra first so we can reference its ratio for MVRV proxy
|
||||
let realized_price_extra = ComputedFromHeightRatio::forced_import(
|
||||
cfg.db,
|
||||
&cfg.name("realized_price"),
|
||||
@@ -470,8 +365,6 @@ impl RealizedMetrics {
|
||||
extended,
|
||||
)?;
|
||||
|
||||
// MVRV is a lazy proxy for realized_price_extra.ratio
|
||||
// ratio = close / realized_price = market_cap / realized_cap = MVRV
|
||||
let mvrv = LazyFromHeightLast::from_computed::<StoredF32Identity>(
|
||||
&cfg.name("mvrv"),
|
||||
cfg.version,
|
||||
@@ -479,17 +372,12 @@ impl RealizedMetrics {
|
||||
&realized_price_extra.ratio,
|
||||
);
|
||||
|
||||
// === Rolling sum intermediates (must be imported before lazy ratios reference them) ===
|
||||
// === Rolling sum intermediates ===
|
||||
macro_rules! import_rolling {
|
||||
($name:expr) => {
|
||||
ComputedFromHeightLast::forced_import(cfg.db, &cfg.name($name), cfg.version + v1, cfg.indexes)?
|
||||
};
|
||||
}
|
||||
macro_rules! import_rolling_opt {
|
||||
($cond:expr, $name:expr) => {
|
||||
$cond.then(|| ComputedFromHeightLast::forced_import(cfg.db, &cfg.name($name), cfg.version + v1, cfg.indexes)).transpose()?
|
||||
};
|
||||
}
|
||||
|
||||
let value_created_24h = import_rolling!("value_created_24h");
|
||||
let value_created_7d = import_rolling!("value_created_7d");
|
||||
@@ -500,30 +388,12 @@ impl RealizedMetrics {
|
||||
let value_destroyed_30d = import_rolling!("value_destroyed_30d");
|
||||
let value_destroyed_1y = import_rolling!("value_destroyed_1y");
|
||||
|
||||
let adjusted_value_created_24h = import_rolling_opt!(compute_adjusted, "adjusted_value_created_24h");
|
||||
let adjusted_value_created_7d = import_rolling_opt!(compute_adjusted, "adjusted_value_created_7d");
|
||||
let adjusted_value_created_30d = import_rolling_opt!(compute_adjusted, "adjusted_value_created_30d");
|
||||
let adjusted_value_created_1y = import_rolling_opt!(compute_adjusted, "adjusted_value_created_1y");
|
||||
let adjusted_value_destroyed_24h = import_rolling_opt!(compute_adjusted, "adjusted_value_destroyed_24h");
|
||||
let adjusted_value_destroyed_7d = import_rolling_opt!(compute_adjusted, "adjusted_value_destroyed_7d");
|
||||
let adjusted_value_destroyed_30d = import_rolling_opt!(compute_adjusted, "adjusted_value_destroyed_30d");
|
||||
let adjusted_value_destroyed_1y = import_rolling_opt!(compute_adjusted, "adjusted_value_destroyed_1y");
|
||||
|
||||
let realized_value_24h = import_rolling!("realized_value_24h");
|
||||
let realized_value_7d = import_rolling!("realized_value_7d");
|
||||
let realized_value_30d = import_rolling!("realized_value_30d");
|
||||
let realized_value_1y = import_rolling!("realized_value_1y");
|
||||
|
||||
let realized_profit_24h = import_rolling_opt!(extended, "realized_profit_24h");
|
||||
let realized_profit_7d = import_rolling_opt!(extended, "realized_profit_7d");
|
||||
let realized_profit_30d = import_rolling_opt!(extended, "realized_profit_30d");
|
||||
let realized_profit_1y = import_rolling_opt!(extended, "realized_profit_1y");
|
||||
let realized_loss_24h = import_rolling_opt!(extended, "realized_loss_24h");
|
||||
let realized_loss_7d = import_rolling_opt!(extended, "realized_loss_7d");
|
||||
let realized_loss_30d = import_rolling_opt!(extended, "realized_loss_30d");
|
||||
let realized_loss_1y = import_rolling_opt!(extended, "realized_loss_1y");
|
||||
|
||||
// === Rolling window lazy ratios (from rolling sum intermediates) ===
|
||||
// === Rolling window lazy ratios ===
|
||||
let sopr_24h = LazyBinaryFromHeightLast::from_computed_last::<Ratio64>(
|
||||
&cfg.name("sopr_24h"), cfg.version + v1, &value_created_24h, &value_destroyed_24h,
|
||||
);
|
||||
@@ -540,26 +410,6 @@ impl RealizedMetrics {
|
||||
&cfg.name("sopr"), cfg.version + v1, &sopr_24h,
|
||||
);
|
||||
|
||||
macro_rules! lazy_binary_from_opt_last {
|
||||
($transform:ty, $name:expr, $s1:expr, $s2:expr) => {
|
||||
($s1.is_some() && $s2.is_some()).then(|| {
|
||||
LazyBinaryFromHeightLast::from_computed_last::<$transform>(
|
||||
&cfg.name($name), cfg.version + v1,
|
||||
$s1.as_ref().unwrap(), $s2.as_ref().unwrap(),
|
||||
)
|
||||
})
|
||||
};
|
||||
}
|
||||
let adjusted_sopr_24h = lazy_binary_from_opt_last!(Ratio64, "adjusted_sopr_24h", adjusted_value_created_24h, adjusted_value_destroyed_24h);
|
||||
let adjusted_sopr_7d = lazy_binary_from_opt_last!(Ratio64, "adjusted_sopr_7d", adjusted_value_created_7d, adjusted_value_destroyed_7d);
|
||||
let adjusted_sopr_30d = lazy_binary_from_opt_last!(Ratio64, "adjusted_sopr_30d", adjusted_value_created_30d, adjusted_value_destroyed_30d);
|
||||
let adjusted_sopr_1y = lazy_binary_from_opt_last!(Ratio64, "adjusted_sopr_1y", adjusted_value_created_1y, adjusted_value_destroyed_1y);
|
||||
let adjusted_sopr = adjusted_sopr_24h.as_ref().map(|sopr_24h| {
|
||||
LazyFromHeightLast::from_binary::<Ident, Dollars, Dollars>(
|
||||
&cfg.name("adjusted_sopr"), cfg.version + v1, sopr_24h,
|
||||
)
|
||||
});
|
||||
|
||||
let sell_side_risk_ratio_24h = LazyBinaryFromHeightLast::from_block_last_and_lazy_block_last::<PercentageDollarsF32, _>(
|
||||
&cfg.name("sell_side_risk_ratio_24h"), cfg.version + v1, &realized_value_24h, &realized_cap,
|
||||
);
|
||||
@@ -576,11 +426,6 @@ impl RealizedMetrics {
|
||||
&cfg.name("sell_side_risk_ratio"), cfg.version + v1, &sell_side_risk_ratio_24h,
|
||||
);
|
||||
|
||||
let realized_profit_to_loss_ratio_24h = lazy_binary_from_opt_last!(Ratio64, "realized_profit_to_loss_ratio_24h", realized_profit_24h, realized_loss_24h);
|
||||
let realized_profit_to_loss_ratio_7d = lazy_binary_from_opt_last!(Ratio64, "realized_profit_to_loss_ratio_7d", realized_profit_7d, realized_loss_7d);
|
||||
let realized_profit_to_loss_ratio_30d = lazy_binary_from_opt_last!(Ratio64, "realized_profit_to_loss_ratio_30d", realized_profit_30d, realized_loss_30d);
|
||||
let realized_profit_to_loss_ratio_1y = lazy_binary_from_opt_last!(Ratio64, "realized_profit_to_loss_ratio_1y", realized_profit_1y, realized_loss_1y);
|
||||
|
||||
// === EMA imports + identity aliases ===
|
||||
let sopr_24h_7d_ema = import_rolling!("sopr_24h_7d_ema");
|
||||
let sopr_7d_ema = LazyFromHeightLast::from_computed::<Ident>(
|
||||
@@ -593,21 +438,6 @@ impl RealizedMetrics {
|
||||
sopr_24h_30d_ema.height.read_only_boxed_clone(), &sopr_24h_30d_ema,
|
||||
);
|
||||
|
||||
let adjusted_sopr_24h_7d_ema = import_rolling_opt!(compute_adjusted, "adjusted_sopr_24h_7d_ema");
|
||||
let adjusted_sopr_7d_ema = adjusted_sopr_24h_7d_ema.as_ref().map(|ema| {
|
||||
LazyFromHeightLast::from_computed::<Ident>(
|
||||
&cfg.name("adjusted_sopr_7d_ema"), cfg.version + v1,
|
||||
ema.height.read_only_boxed_clone(), ema,
|
||||
)
|
||||
});
|
||||
let adjusted_sopr_24h_30d_ema = import_rolling_opt!(compute_adjusted, "adjusted_sopr_24h_30d_ema");
|
||||
let adjusted_sopr_30d_ema = adjusted_sopr_24h_30d_ema.as_ref().map(|ema| {
|
||||
LazyFromHeightLast::from_computed::<Ident>(
|
||||
&cfg.name("adjusted_sopr_30d_ema"), cfg.version + v1,
|
||||
ema.height.read_only_boxed_clone(), ema,
|
||||
)
|
||||
});
|
||||
|
||||
let sell_side_risk_ratio_24h_7d_ema = import_rolling!("sell_side_risk_ratio_24h_7d_ema");
|
||||
let sell_side_risk_ratio_7d_ema = LazyFromHeightLast::from_computed::<Ident>(
|
||||
&cfg.name("sell_side_risk_ratio_7d_ema"), cfg.version + v1,
|
||||
@@ -628,44 +458,24 @@ impl RealizedMetrics {
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
// === Realized Cap ===
|
||||
realized_cap_cents,
|
||||
realized_cap,
|
||||
realized_price,
|
||||
realized_price_extra,
|
||||
realized_cap_rel_to_own_market_cap: extended
|
||||
.then(|| {
|
||||
ComputedFromHeightLast::forced_import(
|
||||
cfg.db,
|
||||
&cfg.name("realized_cap_rel_to_own_market_cap"),
|
||||
cfg.version,
|
||||
cfg.indexes,
|
||||
)
|
||||
})
|
||||
.transpose()?,
|
||||
realized_cap_30d_delta: ComputedFromHeightLast::forced_import(
|
||||
cfg.db,
|
||||
&cfg.name("realized_cap_30d_delta"),
|
||||
cfg.version,
|
||||
cfg.indexes,
|
||||
)?,
|
||||
|
||||
// === Investor Price ===
|
||||
investor_price_cents,
|
||||
investor_price,
|
||||
investor_price_extra,
|
||||
|
||||
// === Floor/Ceiling Price Bands ===
|
||||
lower_price_band,
|
||||
upper_price_band,
|
||||
|
||||
cap_raw,
|
||||
investor_cap_raw,
|
||||
|
||||
// === MVRV ===
|
||||
mvrv,
|
||||
|
||||
// === Realized Profit/Loss ===
|
||||
realized_profit,
|
||||
realized_profit_7d_ema,
|
||||
realized_loss,
|
||||
@@ -674,50 +484,18 @@ impl RealizedMetrics {
|
||||
net_realized_pnl,
|
||||
net_realized_pnl_7d_ema,
|
||||
realized_value,
|
||||
|
||||
// === Realized vs Realized Cap Ratios (lazy) ===
|
||||
realized_profit_rel_to_realized_cap,
|
||||
realized_loss_rel_to_realized_cap,
|
||||
net_realized_pnl_rel_to_realized_cap,
|
||||
|
||||
// === Total Realized PnL ===
|
||||
total_realized_pnl,
|
||||
|
||||
// === Realized Profit/Loss Rolling Sums ===
|
||||
realized_profit_24h,
|
||||
realized_profit_7d,
|
||||
realized_profit_30d,
|
||||
realized_profit_1y,
|
||||
realized_loss_24h,
|
||||
realized_loss_7d,
|
||||
realized_loss_30d,
|
||||
realized_loss_1y,
|
||||
|
||||
// === Realized Profit to Loss Ratio (lazy from rolling sums) ===
|
||||
realized_profit_to_loss_ratio_24h,
|
||||
realized_profit_to_loss_ratio_7d,
|
||||
realized_profit_to_loss_ratio_30d,
|
||||
realized_profit_to_loss_ratio_1y,
|
||||
|
||||
// === Value Created/Destroyed Splits (stored) ===
|
||||
profit_value_created,
|
||||
profit_value_destroyed,
|
||||
loss_value_created,
|
||||
loss_value_destroyed,
|
||||
|
||||
// === Value Created/Destroyed Totals (lazy: profit + loss) ===
|
||||
value_created,
|
||||
value_destroyed,
|
||||
|
||||
// === Capitulation/Profit Flow (lazy aliases) ===
|
||||
capitulation_flow,
|
||||
profit_flow,
|
||||
|
||||
// === Adjusted Value (lazy: cohort - up_to_1h) ===
|
||||
adjusted_value_created,
|
||||
adjusted_value_destroyed,
|
||||
|
||||
// === Value Created/Destroyed Rolling Sums ===
|
||||
value_created_24h,
|
||||
value_created_7d,
|
||||
value_created_30d,
|
||||
@@ -726,8 +504,6 @@ impl RealizedMetrics {
|
||||
value_destroyed_7d,
|
||||
value_destroyed_30d,
|
||||
value_destroyed_1y,
|
||||
|
||||
// === SOPR (rolling window ratios) ===
|
||||
sopr,
|
||||
sopr_24h,
|
||||
sopr_7d,
|
||||
@@ -737,35 +513,10 @@ impl RealizedMetrics {
|
||||
sopr_7d_ema,
|
||||
sopr_24h_30d_ema,
|
||||
sopr_30d_ema,
|
||||
|
||||
// === Adjusted Value Created/Destroyed Rolling Sums ===
|
||||
adjusted_value_created_24h,
|
||||
adjusted_value_created_7d,
|
||||
adjusted_value_created_30d,
|
||||
adjusted_value_created_1y,
|
||||
adjusted_value_destroyed_24h,
|
||||
adjusted_value_destroyed_7d,
|
||||
adjusted_value_destroyed_30d,
|
||||
adjusted_value_destroyed_1y,
|
||||
|
||||
// === Adjusted SOPR (rolling window ratios) ===
|
||||
adjusted_sopr,
|
||||
adjusted_sopr_24h,
|
||||
adjusted_sopr_7d,
|
||||
adjusted_sopr_30d,
|
||||
adjusted_sopr_1y,
|
||||
adjusted_sopr_24h_7d_ema,
|
||||
adjusted_sopr_7d_ema,
|
||||
adjusted_sopr_24h_30d_ema,
|
||||
adjusted_sopr_30d_ema,
|
||||
|
||||
// === Sell Side Risk Rolling Sum Intermediates ===
|
||||
realized_value_24h,
|
||||
realized_value_7d,
|
||||
realized_value_30d,
|
||||
realized_value_1y,
|
||||
|
||||
// === Sell Side Risk (rolling window ratios) ===
|
||||
sell_side_risk_ratio,
|
||||
sell_side_risk_ratio_24h,
|
||||
sell_side_risk_ratio_7d,
|
||||
@@ -775,8 +526,6 @@ impl RealizedMetrics {
|
||||
sell_side_risk_ratio_7d_ema,
|
||||
sell_side_risk_ratio_24h_30d_ema,
|
||||
sell_side_risk_ratio_30d_ema,
|
||||
|
||||
// === Net Realized PnL Deltas ===
|
||||
net_realized_pnl_cumulative_30d_delta: ComputedFromHeightLast::forced_import(
|
||||
cfg.db,
|
||||
&cfg.name("net_realized_pnl_cumulative_30d_delta"),
|
||||
@@ -797,12 +546,8 @@ impl RealizedMetrics {
|
||||
cfg.version + v3,
|
||||
cfg.indexes,
|
||||
)?,
|
||||
|
||||
// === ATH Regret ===
|
||||
peak_regret,
|
||||
peak_regret_rel_to_realized_cap,
|
||||
|
||||
// === Sent in Profit/Loss ===
|
||||
sent_in_profit: LazyComputedValueFromHeightCum::forced_import(
|
||||
cfg.db,
|
||||
&cfg.name("sent_in_profit"),
|
||||
@@ -852,7 +597,6 @@ impl RealizedMetrics {
|
||||
}
|
||||
|
||||
/// Push realized state values to height-indexed vectors.
|
||||
/// State values are CentsUnsigned (deterministic), converted to Dollars for storage.
|
||||
pub(crate) fn truncate_push(&mut self, height: Height, state: &RealizedState) -> Result<()> {
|
||||
self.realized_cap_cents
|
||||
.height
|
||||
@@ -866,11 +610,9 @@ impl RealizedMetrics {
|
||||
self.investor_price_cents
|
||||
.height
|
||||
.truncate_push(height, state.investor_price())?;
|
||||
// Push raw values for aggregation
|
||||
self.cap_raw.truncate_push(height, state.cap_raw())?;
|
||||
self.investor_cap_raw
|
||||
.truncate_push(height, state.investor_cap_raw())?;
|
||||
// Push the 4 splits (totals are derived lazily)
|
||||
self.profit_value_created
|
||||
.height
|
||||
.truncate_push(height, state.profit_value_created().to_dollars())?;
|
||||
@@ -883,12 +625,9 @@ impl RealizedMetrics {
|
||||
self.loss_value_destroyed
|
||||
.height
|
||||
.truncate_push(height, state.loss_value_destroyed().to_dollars())?;
|
||||
// ATH regret
|
||||
self.peak_regret
|
||||
.height
|
||||
.truncate_push(height, state.peak_regret().to_dollars())?;
|
||||
|
||||
// Volume at profit/loss
|
||||
self.sent_in_profit
|
||||
.sats
|
||||
.height
|
||||
@@ -901,34 +640,23 @@ impl RealizedMetrics {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns a parallel iterator over all vecs for parallel writing.
|
||||
pub(crate) fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
|
||||
/// Returns a Vec of mutable references to all stored vecs for parallel writing.
|
||||
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
|
||||
vec![
|
||||
&mut self.realized_cap_cents.height as &mut dyn AnyStoredVec,
|
||||
&mut self.realized_profit.height,
|
||||
&mut self.realized_loss.height,
|
||||
&mut self.investor_price_cents.height,
|
||||
// Raw values for aggregation
|
||||
&mut self.cap_raw as &mut dyn AnyStoredVec,
|
||||
&mut self.investor_cap_raw as &mut dyn AnyStoredVec,
|
||||
// The 4 splits (totals are derived lazily)
|
||||
&mut self.profit_value_created.height,
|
||||
&mut self.profit_value_destroyed.height,
|
||||
&mut self.loss_value_created.height,
|
||||
&mut self.loss_value_destroyed.height,
|
||||
// ATH regret
|
||||
&mut self.peak_regret.height,
|
||||
// Sent in profit/loss
|
||||
&mut self.sent_in_profit.sats.height,
|
||||
&mut self.sent_in_loss.sats.height,
|
||||
]
|
||||
.into_par_iter()
|
||||
}
|
||||
|
||||
/// 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.
|
||||
@@ -964,8 +692,6 @@ impl RealizedMetrics {
|
||||
)?;
|
||||
|
||||
// Aggregate raw values for investor_price computation
|
||||
// (BytesVec doesn't have compute_sum_of_others, so we manually iterate)
|
||||
// Validate version for investor_price_cents (same pattern as compute_sum_of_others)
|
||||
let investor_price_dep_version = others
|
||||
.iter()
|
||||
.map(|o| o.investor_price_cents.height.version())
|
||||
@@ -974,13 +700,11 @@ impl RealizedMetrics {
|
||||
.height
|
||||
.validate_computed_version_or_reset(investor_price_dep_version)?;
|
||||
|
||||
// Start from where the target vecs left off (handles fresh/reset vecs)
|
||||
let start = self
|
||||
.cap_raw
|
||||
.len()
|
||||
.min(self.investor_cap_raw.len())
|
||||
.min(self.investor_price_cents.height.len());
|
||||
// End at the minimum length across all source vecs
|
||||
let end = others.iter().map(|o| o.cap_raw.len()).min().unwrap_or(0);
|
||||
|
||||
for i in start..end {
|
||||
@@ -998,7 +722,6 @@ impl RealizedMetrics {
|
||||
self.investor_cap_raw
|
||||
.truncate_push(height, sum_investor_cap)?;
|
||||
|
||||
// Compute investor_price from aggregated raw values
|
||||
let investor_price = if sum_cap.inner() == 0 {
|
||||
Cents::ZERO
|
||||
} else {
|
||||
@@ -1009,13 +732,11 @@ impl RealizedMetrics {
|
||||
.truncate_push(height, investor_price)?;
|
||||
}
|
||||
|
||||
// Write to persist computed_version (same pattern as compute_sum_of_others)
|
||||
{
|
||||
let _lock = exit.lock();
|
||||
self.investor_price_cents.height.write()?;
|
||||
}
|
||||
|
||||
// Aggregate the 4 splits (totals are derived lazily)
|
||||
self.profit_value_created.height.compute_sum_of_others(
|
||||
starting_indexes.height,
|
||||
&others
|
||||
@@ -1048,7 +769,6 @@ impl RealizedMetrics {
|
||||
.collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
// ATH regret
|
||||
self.peak_regret.height.compute_sum_of_others(
|
||||
starting_indexes.height,
|
||||
&others
|
||||
@@ -1057,8 +777,6 @@ impl RealizedMetrics {
|
||||
.collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
|
||||
// Volume at profit/loss
|
||||
self.sent_in_profit.sats.height.compute_sum_of_others(
|
||||
starting_indexes.height,
|
||||
&others
|
||||
@@ -1085,16 +803,11 @@ impl RealizedMetrics {
|
||||
starting_indexes: &ComputeIndexes,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
// realized_cap_cents: ComputedFromHeightLast - day1 is lazy, nothing to compute
|
||||
// investor_price_cents: ComputedFromHeightLast - day1 is lazy, nothing to compute
|
||||
|
||||
// realized_profit/loss: ComputedFromHeightCum - compute cumulative from height
|
||||
self.realized_profit
|
||||
.compute_cumulative(starting_indexes.height, exit)?;
|
||||
self.realized_loss
|
||||
.compute_cumulative(starting_indexes.height, exit)?;
|
||||
|
||||
// net_realized_pnl = profit - loss
|
||||
self.net_realized_pnl
|
||||
.compute(starting_indexes.height, exit, |vec| {
|
||||
vec.compute_subtract(
|
||||
@@ -1106,10 +819,6 @@ impl RealizedMetrics {
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// realized_value = profit + loss
|
||||
// Note: total_realized_pnl is a lazy alias to realized_value since both
|
||||
// compute profit + loss with sum aggregation, making them identical.
|
||||
// ComputedFromHeightLast: day1 is lazy, just compute the height vec directly
|
||||
self.realized_value.height.compute_add(
|
||||
starting_indexes.height,
|
||||
&self.realized_profit.height,
|
||||
@@ -1117,15 +826,9 @@ impl RealizedMetrics {
|
||||
exit,
|
||||
)?;
|
||||
|
||||
// Compute derived aggregations for the 4 splits
|
||||
// (value_created, value_destroyed, capitulation_flow, profit_flow are derived lazily)
|
||||
// ComputedFromHeightLast: day1 is lazy, nothing to compute
|
||||
|
||||
// ATH regret: ComputedFromHeightCum - compute cumulative from height
|
||||
self.peak_regret
|
||||
.compute_cumulative(starting_indexes.height, exit)?;
|
||||
|
||||
// Volume at profit/loss: LazyComputedValueFromHeightCum - compute cumulative
|
||||
self.sent_in_profit
|
||||
.compute_cumulative(starting_indexes.height, exit)?;
|
||||
self.sent_in_loss
|
||||
@@ -1134,18 +837,17 @@ impl RealizedMetrics {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Second phase of computed metrics (realized price from realized cap / supply).
|
||||
/// Second phase of computed metrics (base-only parts: realized price, rolling sums, EMAs).
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn compute_rest_part2(
|
||||
pub(crate) fn compute_rest_part2_base(
|
||||
&mut self,
|
||||
blocks: &blocks::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
height_to_supply: &impl ReadableVec<Height, Bitcoin>,
|
||||
height_to_market_cap: Option<&impl ReadableVec<Height, Dollars>>,
|
||||
height_to_market_cap: &impl ReadableVec<Height, Dollars>,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
// realized_price = realized_cap / supply
|
||||
self.realized_price.height.compute_divide(
|
||||
starting_indexes.height,
|
||||
&self.realized_cap.height,
|
||||
@@ -1169,7 +871,6 @@ impl RealizedMetrics {
|
||||
Some(&self.investor_price.height),
|
||||
)?;
|
||||
|
||||
// realized_cap_30d_delta: height-level rolling change
|
||||
self.realized_cap_30d_delta.height.compute_rolling_change(
|
||||
starting_indexes.height,
|
||||
&blocks.count.height_1m_ago,
|
||||
@@ -1177,7 +878,7 @@ impl RealizedMetrics {
|
||||
exit,
|
||||
)?;
|
||||
|
||||
// === Rolling sum intermediates (must be computed before lazy ratios/EMAs that read them) ===
|
||||
// === Rolling sum intermediates ===
|
||||
macro_rules! rolling_sum {
|
||||
($target:expr, $window:expr, $source:expr) => {
|
||||
$target.height.compute_rolling_sum(
|
||||
@@ -1186,7 +887,6 @@ impl RealizedMetrics {
|
||||
};
|
||||
}
|
||||
|
||||
// Value created/destroyed rolling sums (from lazy binary totals)
|
||||
rolling_sum!(self.value_created_24h, &blocks.count.height_24h_ago, &self.value_created.height);
|
||||
rolling_sum!(self.value_created_7d, &blocks.count.height_1w_ago, &self.value_created.height);
|
||||
rolling_sum!(self.value_created_30d, &blocks.count.height_1m_ago, &self.value_created.height);
|
||||
@@ -1196,71 +896,13 @@ impl RealizedMetrics {
|
||||
rolling_sum!(self.value_destroyed_30d, &blocks.count.height_1m_ago, &self.value_destroyed.height);
|
||||
rolling_sum!(self.value_destroyed_1y, &blocks.count.height_1y_ago, &self.value_destroyed.height);
|
||||
|
||||
// Adjusted value created/destroyed rolling sums (from lazy adjusted totals)
|
||||
if let Some(source) = self.adjusted_value_created.as_ref() {
|
||||
macro_rules! rolling_sum_opt {
|
||||
($target:expr, $window:expr) => {
|
||||
if let Some(f) = $target.as_mut() {
|
||||
f.height.compute_rolling_sum(
|
||||
starting_indexes.height, $window, &source.height, exit,
|
||||
)?;
|
||||
}
|
||||
};
|
||||
}
|
||||
rolling_sum_opt!(self.adjusted_value_created_24h, &blocks.count.height_24h_ago);
|
||||
rolling_sum_opt!(self.adjusted_value_created_7d, &blocks.count.height_1w_ago);
|
||||
rolling_sum_opt!(self.adjusted_value_created_30d, &blocks.count.height_1m_ago);
|
||||
rolling_sum_opt!(self.adjusted_value_created_1y, &blocks.count.height_1y_ago);
|
||||
}
|
||||
if let Some(source) = self.adjusted_value_destroyed.as_ref() {
|
||||
macro_rules! rolling_sum_opt {
|
||||
($target:expr, $window:expr) => {
|
||||
if let Some(f) = $target.as_mut() {
|
||||
f.height.compute_rolling_sum(
|
||||
starting_indexes.height, $window, &source.height, exit,
|
||||
)?;
|
||||
}
|
||||
};
|
||||
}
|
||||
rolling_sum_opt!(self.adjusted_value_destroyed_24h, &blocks.count.height_24h_ago);
|
||||
rolling_sum_opt!(self.adjusted_value_destroyed_7d, &blocks.count.height_1w_ago);
|
||||
rolling_sum_opt!(self.adjusted_value_destroyed_30d, &blocks.count.height_1m_ago);
|
||||
rolling_sum_opt!(self.adjusted_value_destroyed_1y, &blocks.count.height_1y_ago);
|
||||
}
|
||||
|
||||
// Realized value rolling sums (for sell_side_risk_ratio)
|
||||
// Realized value rolling sums
|
||||
rolling_sum!(self.realized_value_24h, &blocks.count.height_24h_ago, &self.realized_value.height);
|
||||
rolling_sum!(self.realized_value_7d, &blocks.count.height_1w_ago, &self.realized_value.height);
|
||||
rolling_sum!(self.realized_value_30d, &blocks.count.height_1m_ago, &self.realized_value.height);
|
||||
rolling_sum!(self.realized_value_1y, &blocks.count.height_1y_ago, &self.realized_value.height);
|
||||
|
||||
// Realized profit/loss rolling sums (for realized_profit_to_loss_ratio)
|
||||
if let Some(f) = self.realized_profit_24h.as_mut() {
|
||||
f.height.compute_rolling_sum(starting_indexes.height, &blocks.count.height_24h_ago, &self.realized_profit.height, exit)?;
|
||||
}
|
||||
if let Some(f) = self.realized_profit_7d.as_mut() {
|
||||
f.height.compute_rolling_sum(starting_indexes.height, &blocks.count.height_1w_ago, &self.realized_profit.height, exit)?;
|
||||
}
|
||||
if let Some(f) = self.realized_profit_30d.as_mut() {
|
||||
f.height.compute_rolling_sum(starting_indexes.height, &blocks.count.height_1m_ago, &self.realized_profit.height, exit)?;
|
||||
}
|
||||
if let Some(f) = self.realized_profit_1y.as_mut() {
|
||||
f.height.compute_rolling_sum(starting_indexes.height, &blocks.count.height_1y_ago, &self.realized_profit.height, exit)?;
|
||||
}
|
||||
if let Some(f) = self.realized_loss_24h.as_mut() {
|
||||
f.height.compute_rolling_sum(starting_indexes.height, &blocks.count.height_24h_ago, &self.realized_loss.height, exit)?;
|
||||
}
|
||||
if let Some(f) = self.realized_loss_7d.as_mut() {
|
||||
f.height.compute_rolling_sum(starting_indexes.height, &blocks.count.height_1w_ago, &self.realized_loss.height, exit)?;
|
||||
}
|
||||
if let Some(f) = self.realized_loss_30d.as_mut() {
|
||||
f.height.compute_rolling_sum(starting_indexes.height, &blocks.count.height_1m_ago, &self.realized_loss.height, exit)?;
|
||||
}
|
||||
if let Some(f) = self.realized_loss_1y.as_mut() {
|
||||
f.height.compute_rolling_sum(starting_indexes.height, &blocks.count.height_1y_ago, &self.realized_loss.height, exit)?;
|
||||
}
|
||||
|
||||
// 7d rolling average of realized profit (height-level)
|
||||
// 7d rolling averages
|
||||
self.realized_profit_7d_ema
|
||||
.height
|
||||
.compute_rolling_average(
|
||||
@@ -1269,16 +911,12 @@ impl RealizedMetrics {
|
||||
&self.realized_profit.height,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
// 7d rolling average of realized loss (height-level)
|
||||
self.realized_loss_7d_ema.height.compute_rolling_average(
|
||||
starting_indexes.height,
|
||||
&blocks.count.height_1w_ago,
|
||||
&self.realized_loss.height,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
// 7d rolling average of net realized PnL (height-level)
|
||||
self.net_realized_pnl_7d_ema
|
||||
.height
|
||||
.compute_rolling_average(
|
||||
@@ -1288,7 +926,7 @@ impl RealizedMetrics {
|
||||
exit,
|
||||
)?;
|
||||
|
||||
// 14-day rolling average of sent in profit (sats and dollars)
|
||||
// 14-day rolling average of sent in profit/loss
|
||||
self.sent_in_profit_14d_ema.compute_rolling_average(
|
||||
starting_indexes.height,
|
||||
&blocks.count.height_2w_ago,
|
||||
@@ -1296,8 +934,6 @@ impl RealizedMetrics {
|
||||
&self.sent_in_profit.usd.height,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
// 14-day rolling average of sent in loss (sats and dollars)
|
||||
self.sent_in_loss_14d_ema.compute_rolling_average(
|
||||
starting_indexes.height,
|
||||
&blocks.count.height_2w_ago,
|
||||
@@ -1306,14 +942,13 @@ impl RealizedMetrics {
|
||||
exit,
|
||||
)?;
|
||||
|
||||
// 7d/30d rolling average of SOPR (from 24h rolling ratio)
|
||||
// SOPR EMAs
|
||||
self.sopr_24h_7d_ema.height.compute_rolling_average(
|
||||
starting_indexes.height,
|
||||
&blocks.count.height_1w_ago,
|
||||
&self.sopr.height,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
self.sopr_24h_30d_ema.height.compute_rolling_average(
|
||||
starting_indexes.height,
|
||||
&blocks.count.height_1m_ago,
|
||||
@@ -1321,28 +956,7 @@ impl RealizedMetrics {
|
||||
exit,
|
||||
)?;
|
||||
|
||||
// Optional: adjusted SOPR rolling averages (from 24h rolling ratio)
|
||||
if let Some(adjusted_sopr) = self.adjusted_sopr.as_ref() {
|
||||
if let Some(ema_7d) = self.adjusted_sopr_24h_7d_ema.as_mut() {
|
||||
ema_7d.height.compute_rolling_average(
|
||||
starting_indexes.height,
|
||||
&blocks.count.height_1w_ago,
|
||||
&adjusted_sopr.height,
|
||||
exit,
|
||||
)?;
|
||||
}
|
||||
|
||||
if let Some(ema_30d) = self.adjusted_sopr_24h_30d_ema.as_mut() {
|
||||
ema_30d.height.compute_rolling_average(
|
||||
starting_indexes.height,
|
||||
&blocks.count.height_1m_ago,
|
||||
&adjusted_sopr.height,
|
||||
exit,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
// 7d/30d rolling average of sell_side_risk_ratio (from 24h rolling ratio)
|
||||
// Sell side risk EMAs
|
||||
self.sell_side_risk_ratio_24h_7d_ema
|
||||
.height
|
||||
.compute_rolling_average(
|
||||
@@ -1351,7 +965,6 @@ impl RealizedMetrics {
|
||||
&self.sell_side_risk_ratio.height,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
self.sell_side_risk_ratio_24h_30d_ema
|
||||
.height
|
||||
.compute_rolling_average(
|
||||
@@ -1361,7 +974,7 @@ impl RealizedMetrics {
|
||||
exit,
|
||||
)?;
|
||||
|
||||
// Net realized PnL cumulative 30d delta (height-level rolling change)
|
||||
// Net realized PnL cumulative 30d delta
|
||||
self.net_realized_pnl_cumulative_30d_delta
|
||||
.height
|
||||
.compute_rolling_change(
|
||||
@@ -1371,7 +984,6 @@ impl RealizedMetrics {
|
||||
exit,
|
||||
)?;
|
||||
|
||||
// Relative to realized cap (height-level)
|
||||
self.net_realized_pnl_cumulative_30d_delta_rel_to_realized_cap
|
||||
.height
|
||||
.compute_percentage(
|
||||
@@ -1381,27 +993,14 @@ impl RealizedMetrics {
|
||||
exit,
|
||||
)?;
|
||||
|
||||
// Relative to market cap (height-level)
|
||||
if let Some(height_to_market_cap) = height_to_market_cap {
|
||||
self.net_realized_pnl_cumulative_30d_delta_rel_to_market_cap
|
||||
.height
|
||||
.compute_percentage(
|
||||
starting_indexes.height,
|
||||
&self.net_realized_pnl_cumulative_30d_delta.height,
|
||||
height_to_market_cap,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
// Optional: realized_cap_rel_to_own_market_cap
|
||||
if let Some(rel_vec) = self.realized_cap_rel_to_own_market_cap.as_mut() {
|
||||
rel_vec.height.compute_percentage(
|
||||
starting_indexes.height,
|
||||
&self.realized_cap.height,
|
||||
height_to_market_cap,
|
||||
exit,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
self.net_realized_pnl_cumulative_30d_delta_rel_to_market_cap
|
||||
.height
|
||||
.compute_percentage(
|
||||
starting_indexes.height,
|
||||
&self.net_realized_pnl_cumulative_30d_delta.height,
|
||||
height_to_market_cap,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Dollars, Height, StoredF32, StoredF64, Version};
|
||||
use vecdb::{Exit, ReadableVec, Rw, StorageMode};
|
||||
|
||||
use crate::{
|
||||
ComputeIndexes, blocks,
|
||||
internal::{
|
||||
ComputedFromHeightLast, LazyBinaryFromHeightLast, Ratio64,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::distribution::metrics::ImportConfig;
|
||||
|
||||
use super::RealizedBase;
|
||||
|
||||
/// Extended realized metrics (only for extended cohorts: all, sth, lth, age_range).
|
||||
#[derive(Traversable)]
|
||||
pub struct RealizedExtended<M: StorageMode = Rw> {
|
||||
pub realized_cap_rel_to_own_market_cap: ComputedFromHeightLast<StoredF32, M>,
|
||||
|
||||
// === Realized Profit/Loss Rolling Sums ===
|
||||
pub realized_profit_24h: ComputedFromHeightLast<Dollars, M>,
|
||||
pub realized_profit_7d: ComputedFromHeightLast<Dollars, M>,
|
||||
pub realized_profit_30d: ComputedFromHeightLast<Dollars, M>,
|
||||
pub realized_profit_1y: ComputedFromHeightLast<Dollars, M>,
|
||||
pub realized_loss_24h: ComputedFromHeightLast<Dollars, M>,
|
||||
pub realized_loss_7d: ComputedFromHeightLast<Dollars, M>,
|
||||
pub realized_loss_30d: ComputedFromHeightLast<Dollars, M>,
|
||||
pub realized_loss_1y: ComputedFromHeightLast<Dollars, M>,
|
||||
|
||||
// === Realized Profit to Loss Ratio (lazy from rolling sums) ===
|
||||
pub realized_profit_to_loss_ratio_24h: LazyBinaryFromHeightLast<StoredF64, Dollars, Dollars>,
|
||||
pub realized_profit_to_loss_ratio_7d: LazyBinaryFromHeightLast<StoredF64, Dollars, Dollars>,
|
||||
pub realized_profit_to_loss_ratio_30d: LazyBinaryFromHeightLast<StoredF64, Dollars, Dollars>,
|
||||
pub realized_profit_to_loss_ratio_1y: LazyBinaryFromHeightLast<StoredF64, Dollars, Dollars>,
|
||||
}
|
||||
|
||||
impl RealizedExtended {
|
||||
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
|
||||
let v1 = Version::ONE;
|
||||
|
||||
macro_rules! import_rolling {
|
||||
($name:expr) => {
|
||||
ComputedFromHeightLast::forced_import(cfg.db, &cfg.name($name), cfg.version + v1, cfg.indexes)?
|
||||
};
|
||||
}
|
||||
|
||||
let realized_profit_24h = import_rolling!("realized_profit_24h");
|
||||
let realized_profit_7d = import_rolling!("realized_profit_7d");
|
||||
let realized_profit_30d = import_rolling!("realized_profit_30d");
|
||||
let realized_profit_1y = import_rolling!("realized_profit_1y");
|
||||
let realized_loss_24h = import_rolling!("realized_loss_24h");
|
||||
let realized_loss_7d = import_rolling!("realized_loss_7d");
|
||||
let realized_loss_30d = import_rolling!("realized_loss_30d");
|
||||
let realized_loss_1y = import_rolling!("realized_loss_1y");
|
||||
|
||||
let realized_profit_to_loss_ratio_24h = LazyBinaryFromHeightLast::from_computed_last::<Ratio64>(
|
||||
&cfg.name("realized_profit_to_loss_ratio_24h"), cfg.version + v1, &realized_profit_24h, &realized_loss_24h,
|
||||
);
|
||||
let realized_profit_to_loss_ratio_7d = LazyBinaryFromHeightLast::from_computed_last::<Ratio64>(
|
||||
&cfg.name("realized_profit_to_loss_ratio_7d"), cfg.version + v1, &realized_profit_7d, &realized_loss_7d,
|
||||
);
|
||||
let realized_profit_to_loss_ratio_30d = LazyBinaryFromHeightLast::from_computed_last::<Ratio64>(
|
||||
&cfg.name("realized_profit_to_loss_ratio_30d"), cfg.version + v1, &realized_profit_30d, &realized_loss_30d,
|
||||
);
|
||||
let realized_profit_to_loss_ratio_1y = LazyBinaryFromHeightLast::from_computed_last::<Ratio64>(
|
||||
&cfg.name("realized_profit_to_loss_ratio_1y"), cfg.version + v1, &realized_profit_1y, &realized_loss_1y,
|
||||
);
|
||||
|
||||
Ok(RealizedExtended {
|
||||
realized_cap_rel_to_own_market_cap: ComputedFromHeightLast::forced_import(
|
||||
cfg.db,
|
||||
&cfg.name("realized_cap_rel_to_own_market_cap"),
|
||||
cfg.version,
|
||||
cfg.indexes,
|
||||
)?,
|
||||
realized_profit_24h,
|
||||
realized_profit_7d,
|
||||
realized_profit_30d,
|
||||
realized_profit_1y,
|
||||
realized_loss_24h,
|
||||
realized_loss_7d,
|
||||
realized_loss_30d,
|
||||
realized_loss_1y,
|
||||
realized_profit_to_loss_ratio_24h,
|
||||
realized_profit_to_loss_ratio_7d,
|
||||
realized_profit_to_loss_ratio_30d,
|
||||
realized_profit_to_loss_ratio_1y,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn compute_rest_part2_ext(
|
||||
&mut self,
|
||||
base: &RealizedBase,
|
||||
blocks: &blocks::Vecs,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
height_to_market_cap: &impl ReadableVec<Height, Dollars>,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
// Realized profit/loss rolling sums
|
||||
self.realized_profit_24h.height.compute_rolling_sum(starting_indexes.height, &blocks.count.height_24h_ago, &base.realized_profit.height, exit)?;
|
||||
self.realized_profit_7d.height.compute_rolling_sum(starting_indexes.height, &blocks.count.height_1w_ago, &base.realized_profit.height, exit)?;
|
||||
self.realized_profit_30d.height.compute_rolling_sum(starting_indexes.height, &blocks.count.height_1m_ago, &base.realized_profit.height, exit)?;
|
||||
self.realized_profit_1y.height.compute_rolling_sum(starting_indexes.height, &blocks.count.height_1y_ago, &base.realized_profit.height, exit)?;
|
||||
self.realized_loss_24h.height.compute_rolling_sum(starting_indexes.height, &blocks.count.height_24h_ago, &base.realized_loss.height, exit)?;
|
||||
self.realized_loss_7d.height.compute_rolling_sum(starting_indexes.height, &blocks.count.height_1w_ago, &base.realized_loss.height, exit)?;
|
||||
self.realized_loss_30d.height.compute_rolling_sum(starting_indexes.height, &blocks.count.height_1m_ago, &base.realized_loss.height, exit)?;
|
||||
self.realized_loss_1y.height.compute_rolling_sum(starting_indexes.height, &blocks.count.height_1y_ago, &base.realized_loss.height, exit)?;
|
||||
|
||||
// Realized cap relative to own market cap
|
||||
self.realized_cap_rel_to_own_market_cap.height.compute_percentage(
|
||||
starting_indexes.height,
|
||||
&base.realized_cap.height,
|
||||
height_to_market_cap,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
15
crates/brk_computer/src/distribution/metrics/realized/mod.rs
Normal file
15
crates/brk_computer/src/distribution/metrics/realized/mod.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
mod adjusted;
|
||||
mod base;
|
||||
mod extended;
|
||||
|
||||
mod with_adjusted;
|
||||
mod with_extended;
|
||||
mod with_extended_adjusted;
|
||||
|
||||
pub use adjusted::*;
|
||||
pub use base::*;
|
||||
pub use extended::*;
|
||||
|
||||
pub use with_adjusted::*;
|
||||
pub use with_extended::*;
|
||||
pub use with_extended_adjusted::*;
|
||||
@@ -0,0 +1,59 @@
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Bitcoin, Dollars, Height};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use vecdb::{Exit, ReadableVec, Rw, StorageMode};
|
||||
|
||||
use crate::{ComputeIndexes, blocks, prices};
|
||||
|
||||
use crate::distribution::metrics::ImportConfig;
|
||||
|
||||
use super::{RealizedAdjusted, RealizedBase};
|
||||
|
||||
/// Realized metrics with guaranteed adjusted (no Option).
|
||||
#[derive(Deref, DerefMut, Traversable)]
|
||||
#[traversable(merge)]
|
||||
pub struct RealizedWithAdjusted<M: StorageMode = Rw> {
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
#[traversable(flatten)]
|
||||
pub base: RealizedBase<M>,
|
||||
#[traversable(flatten)]
|
||||
pub adjusted: RealizedAdjusted<M>,
|
||||
}
|
||||
|
||||
impl RealizedWithAdjusted {
|
||||
pub(crate) fn forced_import(cfg: &ImportConfig, up_to_1h: &RealizedBase) -> Result<Self> {
|
||||
let base = RealizedBase::forced_import(cfg)?;
|
||||
let adjusted = RealizedAdjusted::forced_import(cfg, &base, up_to_1h)?;
|
||||
Ok(Self { base, adjusted })
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn compute_rest_part2(
|
||||
&mut self,
|
||||
blocks: &blocks::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
height_to_supply: &impl ReadableVec<Height, Bitcoin>,
|
||||
height_to_market_cap: &impl ReadableVec<Height, Dollars>,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.base.compute_rest_part2_base(
|
||||
blocks,
|
||||
prices,
|
||||
starting_indexes,
|
||||
height_to_supply,
|
||||
height_to_market_cap,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
self.adjusted.compute_rest_part2_adj(
|
||||
blocks,
|
||||
starting_indexes,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Bitcoin, Dollars, Height};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use vecdb::{Exit, ReadableVec, Rw, StorageMode};
|
||||
|
||||
use crate::{ComputeIndexes, blocks, prices};
|
||||
|
||||
use crate::distribution::metrics::ImportConfig;
|
||||
|
||||
use super::{RealizedBase, RealizedExtended};
|
||||
|
||||
/// Realized metrics with guaranteed extended (no Option).
|
||||
#[derive(Deref, DerefMut, Traversable)]
|
||||
#[traversable(merge)]
|
||||
pub struct RealizedWithExtended<M: StorageMode = Rw> {
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
#[traversable(flatten)]
|
||||
pub base: RealizedBase<M>,
|
||||
#[traversable(flatten)]
|
||||
pub extended: RealizedExtended<M>,
|
||||
}
|
||||
|
||||
impl RealizedWithExtended {
|
||||
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
|
||||
let base = RealizedBase::forced_import(cfg)?;
|
||||
let extended = RealizedExtended::forced_import(cfg)?;
|
||||
Ok(Self { base, extended })
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn compute_rest_part2(
|
||||
&mut self,
|
||||
blocks: &blocks::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
height_to_supply: &impl ReadableVec<Height, Bitcoin>,
|
||||
height_to_market_cap: &impl ReadableVec<Height, Dollars>,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.base.compute_rest_part2_base(
|
||||
blocks,
|
||||
prices,
|
||||
starting_indexes,
|
||||
height_to_supply,
|
||||
height_to_market_cap,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
self.extended.compute_rest_part2_ext(
|
||||
&self.base,
|
||||
blocks,
|
||||
starting_indexes,
|
||||
height_to_market_cap,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Bitcoin, Dollars, Height};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use vecdb::{Exit, ReadableVec, Rw, StorageMode};
|
||||
|
||||
use crate::{ComputeIndexes, blocks, prices};
|
||||
|
||||
use crate::distribution::metrics::ImportConfig;
|
||||
|
||||
use super::{RealizedAdjusted, RealizedBase, RealizedExtended};
|
||||
|
||||
/// Realized metrics with guaranteed extended AND adjusted (no Options).
|
||||
#[derive(Deref, DerefMut, Traversable)]
|
||||
#[traversable(merge)]
|
||||
pub struct RealizedWithExtendedAdjusted<M: StorageMode = Rw> {
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
#[traversable(flatten)]
|
||||
pub base: RealizedBase<M>,
|
||||
#[traversable(flatten)]
|
||||
pub extended: RealizedExtended<M>,
|
||||
#[traversable(flatten)]
|
||||
pub adjusted: RealizedAdjusted<M>,
|
||||
}
|
||||
|
||||
impl RealizedWithExtendedAdjusted {
|
||||
pub(crate) fn forced_import(cfg: &ImportConfig, up_to_1h: &RealizedBase) -> Result<Self> {
|
||||
let base = RealizedBase::forced_import(cfg)?;
|
||||
let extended = RealizedExtended::forced_import(cfg)?;
|
||||
let adjusted = RealizedAdjusted::forced_import(cfg, &base, up_to_1h)?;
|
||||
Ok(Self {
|
||||
base,
|
||||
extended,
|
||||
adjusted,
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn compute_rest_part2(
|
||||
&mut self,
|
||||
blocks: &blocks::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
height_to_supply: &impl ReadableVec<Height, Bitcoin>,
|
||||
height_to_market_cap: &impl ReadableVec<Height, Dollars>,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.base.compute_rest_part2_base(
|
||||
blocks,
|
||||
prices,
|
||||
starting_indexes,
|
||||
height_to_supply,
|
||||
height_to_market_cap,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
self.extended.compute_rest_part2_ext(
|
||||
&self.base,
|
||||
blocks,
|
||||
starting_indexes,
|
||||
height_to_market_cap,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
self.adjusted.compute_rest_part2_adj(
|
||||
blocks,
|
||||
starting_indexes,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,328 +0,0 @@
|
||||
use brk_cohort::Filter;
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Dollars, Sats, StoredF32, StoredF64, Version};
|
||||
|
||||
use crate::internal::{
|
||||
LazyBinaryFromHeightLast, NegPercentageDollarsF32, PercentageDollarsF32, PercentageSatsF64,
|
||||
};
|
||||
|
||||
use super::{ImportConfig, RealizedMetrics, SupplyMetrics, UnrealizedMetrics};
|
||||
|
||||
/// Relative metrics comparing cohort values to global values.
|
||||
/// All `rel_to_` vecs are lazy - computed on-demand from their sources.
|
||||
#[derive(Clone, Traversable)]
|
||||
pub struct RelativeMetrics {
|
||||
// === Supply Relative to Circulating Supply (lazy from global supply) ===
|
||||
pub supply_rel_to_circulating_supply:
|
||||
Option<LazyBinaryFromHeightLast<StoredF64, Sats, Sats>>,
|
||||
|
||||
// === Supply in Profit/Loss Relative to Own Supply (lazy) ===
|
||||
pub supply_in_profit_rel_to_own_supply: LazyBinaryFromHeightLast<StoredF64, Sats, Sats>,
|
||||
pub supply_in_loss_rel_to_own_supply: LazyBinaryFromHeightLast<StoredF64, Sats, Sats>,
|
||||
|
||||
// === Supply in Profit/Loss Relative to Circulating Supply (lazy from global supply) ===
|
||||
pub supply_in_profit_rel_to_circulating_supply:
|
||||
Option<LazyBinaryFromHeightLast<StoredF64, Sats, Sats>>,
|
||||
pub supply_in_loss_rel_to_circulating_supply:
|
||||
Option<LazyBinaryFromHeightLast<StoredF64, Sats, Sats>>,
|
||||
|
||||
// === Unrealized vs Market Cap (lazy from global market cap) ===
|
||||
pub unrealized_profit_rel_to_market_cap:
|
||||
Option<LazyBinaryFromHeightLast<StoredF32, Dollars, Dollars>>,
|
||||
pub unrealized_loss_rel_to_market_cap:
|
||||
Option<LazyBinaryFromHeightLast<StoredF32, Dollars, Dollars>>,
|
||||
pub neg_unrealized_loss_rel_to_market_cap:
|
||||
Option<LazyBinaryFromHeightLast<StoredF32, Dollars, Dollars>>,
|
||||
pub net_unrealized_pnl_rel_to_market_cap:
|
||||
Option<LazyBinaryFromHeightLast<StoredF32, Dollars, Dollars>>,
|
||||
|
||||
// === NUPL (Net Unrealized Profit/Loss) ===
|
||||
pub nupl: Option<LazyBinaryFromHeightLast<StoredF32, Dollars, Dollars>>,
|
||||
|
||||
// === Unrealized vs Own Market Cap (lazy) ===
|
||||
pub unrealized_profit_rel_to_own_market_cap:
|
||||
Option<LazyBinaryFromHeightLast<StoredF32, Dollars, Dollars>>,
|
||||
pub unrealized_loss_rel_to_own_market_cap:
|
||||
Option<LazyBinaryFromHeightLast<StoredF32, Dollars, Dollars>>,
|
||||
pub neg_unrealized_loss_rel_to_own_market_cap:
|
||||
Option<LazyBinaryFromHeightLast<StoredF32, Dollars, Dollars>>,
|
||||
pub net_unrealized_pnl_rel_to_own_market_cap:
|
||||
Option<LazyBinaryFromHeightLast<StoredF32, Dollars, Dollars>>,
|
||||
|
||||
// === Unrealized vs Own Total Unrealized PnL (lazy) ===
|
||||
pub unrealized_profit_rel_to_own_total_unrealized_pnl:
|
||||
Option<LazyBinaryFromHeightLast<StoredF32, Dollars, Dollars>>,
|
||||
pub unrealized_loss_rel_to_own_total_unrealized_pnl:
|
||||
Option<LazyBinaryFromHeightLast<StoredF32, Dollars, Dollars>>,
|
||||
pub neg_unrealized_loss_rel_to_own_total_unrealized_pnl:
|
||||
Option<LazyBinaryFromHeightLast<StoredF32, Dollars, Dollars>>,
|
||||
pub net_unrealized_pnl_rel_to_own_total_unrealized_pnl:
|
||||
Option<LazyBinaryFromHeightLast<StoredF32, Dollars, Dollars>>,
|
||||
|
||||
// === Invested Capital in Profit/Loss as % of Realized Cap ===
|
||||
pub invested_capital_in_profit_pct:
|
||||
Option<LazyBinaryFromHeightLast<StoredF32, Dollars, Dollars>>,
|
||||
pub invested_capital_in_loss_pct:
|
||||
Option<LazyBinaryFromHeightLast<StoredF32, Dollars, Dollars>>,
|
||||
|
||||
// === Unrealized Peak Regret Relative to Market Cap (lazy) ===
|
||||
pub unrealized_peak_regret_rel_to_market_cap:
|
||||
Option<LazyBinaryFromHeightLast<StoredF32, Dollars, Dollars>>,
|
||||
}
|
||||
|
||||
impl RelativeMetrics {
|
||||
/// Import relative metrics from database.
|
||||
///
|
||||
/// All `rel_to_` metrics are lazy - computed on-demand from their sources.
|
||||
/// `all_supply` provides global sources for `*_rel_to_market_cap` and `*_rel_to_circulating_supply`.
|
||||
/// `realized` provides realized_cap for invested capital percentage metrics.
|
||||
pub(crate) fn forced_import(
|
||||
cfg: &ImportConfig,
|
||||
unrealized: &UnrealizedMetrics,
|
||||
supply: &SupplyMetrics,
|
||||
all_supply: Option<&SupplyMetrics>,
|
||||
realized: Option<&RealizedMetrics>,
|
||||
) -> Result<Self> {
|
||||
let v1 = Version::ONE;
|
||||
let v2 = Version::new(2);
|
||||
let extended = cfg.extended();
|
||||
let compute_rel_to_all = cfg.compute_rel_to_all();
|
||||
|
||||
// Global sources from "all" cohort
|
||||
let global_supply_sats = all_supply.map(|s| &s.total.sats);
|
||||
let global_market_cap = all_supply.map(|s| &s.total.usd);
|
||||
|
||||
// Own market cap source
|
||||
let own_market_cap = &supply.total.usd;
|
||||
|
||||
// For "all" cohort, own_market_cap IS the global market cap
|
||||
let market_cap = global_market_cap.or_else(|| {
|
||||
matches!(cfg.filter, Filter::All).then_some(own_market_cap)
|
||||
});
|
||||
|
||||
Ok(Self {
|
||||
// === Supply Relative to Circulating Supply ===
|
||||
supply_rel_to_circulating_supply: (compute_rel_to_all
|
||||
&& global_supply_sats.is_some())
|
||||
.then(|| {
|
||||
LazyBinaryFromHeightLast::from_computed_last::<PercentageSatsF64>(
|
||||
&cfg.name("supply_rel_to_circulating_supply"),
|
||||
cfg.version + v1,
|
||||
&supply.total.sats,
|
||||
global_supply_sats.unwrap(),
|
||||
)
|
||||
}),
|
||||
|
||||
// === Supply in Profit/Loss Relative to Own Supply ===
|
||||
supply_in_profit_rel_to_own_supply:
|
||||
LazyBinaryFromHeightLast::from_computed_last::<PercentageSatsF64>(
|
||||
&cfg.name("supply_in_profit_rel_to_own_supply"),
|
||||
cfg.version + v1,
|
||||
&unrealized.supply_in_profit.sats,
|
||||
&supply.total.sats,
|
||||
),
|
||||
supply_in_loss_rel_to_own_supply:
|
||||
LazyBinaryFromHeightLast::from_computed_last::<PercentageSatsF64>(
|
||||
&cfg.name("supply_in_loss_rel_to_own_supply"),
|
||||
cfg.version + v1,
|
||||
&unrealized.supply_in_loss.sats,
|
||||
&supply.total.sats,
|
||||
),
|
||||
|
||||
// === Supply in Profit/Loss Relative to Circulating Supply ===
|
||||
supply_in_profit_rel_to_circulating_supply: (compute_rel_to_all
|
||||
&& global_supply_sats.is_some())
|
||||
.then(|| {
|
||||
LazyBinaryFromHeightLast::from_computed_last::<PercentageSatsF64>(
|
||||
&cfg.name("supply_in_profit_rel_to_circulating_supply"),
|
||||
cfg.version + v1,
|
||||
&unrealized.supply_in_profit.sats,
|
||||
global_supply_sats.unwrap(),
|
||||
)
|
||||
}),
|
||||
supply_in_loss_rel_to_circulating_supply: (compute_rel_to_all
|
||||
&& global_supply_sats.is_some())
|
||||
.then(|| {
|
||||
LazyBinaryFromHeightLast::from_computed_last::<PercentageSatsF64>(
|
||||
&cfg.name("supply_in_loss_rel_to_circulating_supply"),
|
||||
cfg.version + v1,
|
||||
&unrealized.supply_in_loss.sats,
|
||||
global_supply_sats.unwrap(),
|
||||
)
|
||||
}),
|
||||
|
||||
// === Unrealized vs Market Cap ===
|
||||
unrealized_profit_rel_to_market_cap: market_cap.map(|mc| {
|
||||
LazyBinaryFromHeightLast::from_block_last_and_lazy_binary_computed_block_last::<
|
||||
PercentageDollarsF32, _, _,
|
||||
>(
|
||||
&cfg.name("unrealized_profit_rel_to_market_cap"),
|
||||
cfg.version + v2,
|
||||
&unrealized.unrealized_profit,
|
||||
mc,
|
||||
)
|
||||
}),
|
||||
unrealized_loss_rel_to_market_cap: market_cap.map(|mc| {
|
||||
LazyBinaryFromHeightLast::from_block_last_and_lazy_binary_computed_block_last::<
|
||||
PercentageDollarsF32, _, _,
|
||||
>(
|
||||
&cfg.name("unrealized_loss_rel_to_market_cap"),
|
||||
cfg.version + v2,
|
||||
&unrealized.unrealized_loss,
|
||||
mc,
|
||||
)
|
||||
}),
|
||||
neg_unrealized_loss_rel_to_market_cap: market_cap.map(|mc| {
|
||||
LazyBinaryFromHeightLast::from_block_last_and_lazy_binary_computed_block_last::<
|
||||
NegPercentageDollarsF32, _, _,
|
||||
>(
|
||||
&cfg.name("neg_unrealized_loss_rel_to_market_cap"),
|
||||
cfg.version + v2,
|
||||
&unrealized.unrealized_loss,
|
||||
mc,
|
||||
)
|
||||
}),
|
||||
net_unrealized_pnl_rel_to_market_cap: market_cap.map(|mc| {
|
||||
LazyBinaryFromHeightLast::from_binary_block_and_lazy_binary_block_last::<
|
||||
PercentageDollarsF32, _, _, _, _,
|
||||
>(
|
||||
&cfg.name("net_unrealized_pnl_rel_to_market_cap"),
|
||||
cfg.version + v2,
|
||||
&unrealized.net_unrealized_pnl,
|
||||
mc,
|
||||
)
|
||||
}),
|
||||
|
||||
// NUPL is a proxy for net_unrealized_pnl_rel_to_market_cap
|
||||
nupl: market_cap.map(|mc| {
|
||||
LazyBinaryFromHeightLast::from_binary_block_and_lazy_binary_block_last::<
|
||||
PercentageDollarsF32, _, _, _, _,
|
||||
>(
|
||||
&cfg.name("nupl"),
|
||||
cfg.version + v2,
|
||||
&unrealized.net_unrealized_pnl,
|
||||
mc,
|
||||
)
|
||||
}),
|
||||
|
||||
// === Unrealized vs Own Market Cap (lazy, optional) ===
|
||||
unrealized_profit_rel_to_own_market_cap: (extended && compute_rel_to_all)
|
||||
.then(|| {
|
||||
LazyBinaryFromHeightLast::from_block_last_and_lazy_binary_computed_block_last::<
|
||||
PercentageDollarsF32, _, _,
|
||||
>(
|
||||
&cfg.name("unrealized_profit_rel_to_own_market_cap"),
|
||||
cfg.version + v2,
|
||||
&unrealized.unrealized_profit,
|
||||
own_market_cap,
|
||||
)
|
||||
}),
|
||||
unrealized_loss_rel_to_own_market_cap: (extended && compute_rel_to_all)
|
||||
.then(|| {
|
||||
LazyBinaryFromHeightLast::from_block_last_and_lazy_binary_computed_block_last::<
|
||||
PercentageDollarsF32, _, _,
|
||||
>(
|
||||
&cfg.name("unrealized_loss_rel_to_own_market_cap"),
|
||||
cfg.version + v2,
|
||||
&unrealized.unrealized_loss,
|
||||
own_market_cap,
|
||||
)
|
||||
}),
|
||||
neg_unrealized_loss_rel_to_own_market_cap: (extended && compute_rel_to_all)
|
||||
.then(|| {
|
||||
LazyBinaryFromHeightLast::from_block_last_and_lazy_binary_computed_block_last::<
|
||||
NegPercentageDollarsF32, _, _,
|
||||
>(
|
||||
&cfg.name("neg_unrealized_loss_rel_to_own_market_cap"),
|
||||
cfg.version + v2,
|
||||
&unrealized.unrealized_loss,
|
||||
own_market_cap,
|
||||
)
|
||||
}),
|
||||
net_unrealized_pnl_rel_to_own_market_cap: (extended && compute_rel_to_all)
|
||||
.then(|| {
|
||||
LazyBinaryFromHeightLast::from_binary_block_and_lazy_binary_block_last::<
|
||||
PercentageDollarsF32, _, _, _, _,
|
||||
>(
|
||||
&cfg.name("net_unrealized_pnl_rel_to_own_market_cap"),
|
||||
cfg.version + v2,
|
||||
&unrealized.net_unrealized_pnl,
|
||||
own_market_cap,
|
||||
)
|
||||
}),
|
||||
|
||||
// === Unrealized vs Own Total Unrealized PnL (lazy, optional) ===
|
||||
unrealized_profit_rel_to_own_total_unrealized_pnl: extended.then(|| {
|
||||
LazyBinaryFromHeightLast::from_block_last_and_binary_block::<PercentageDollarsF32, _, _>(
|
||||
&cfg.name("unrealized_profit_rel_to_own_total_unrealized_pnl"),
|
||||
cfg.version + v1,
|
||||
&unrealized.unrealized_profit,
|
||||
&unrealized.total_unrealized_pnl,
|
||||
)
|
||||
}),
|
||||
unrealized_loss_rel_to_own_total_unrealized_pnl: extended.then(|| {
|
||||
LazyBinaryFromHeightLast::from_block_last_and_binary_block::<PercentageDollarsF32, _, _>(
|
||||
&cfg.name("unrealized_loss_rel_to_own_total_unrealized_pnl"),
|
||||
cfg.version + v1,
|
||||
&unrealized.unrealized_loss,
|
||||
&unrealized.total_unrealized_pnl,
|
||||
)
|
||||
}),
|
||||
neg_unrealized_loss_rel_to_own_total_unrealized_pnl: extended.then(|| {
|
||||
LazyBinaryFromHeightLast::from_block_last_and_binary_block::<NegPercentageDollarsF32, _, _>(
|
||||
&cfg.name("neg_unrealized_loss_rel_to_own_total_unrealized_pnl"),
|
||||
cfg.version + v1,
|
||||
&unrealized.unrealized_loss,
|
||||
&unrealized.total_unrealized_pnl,
|
||||
)
|
||||
}),
|
||||
net_unrealized_pnl_rel_to_own_total_unrealized_pnl: extended.then(|| {
|
||||
LazyBinaryFromHeightLast::from_both_binary_block::<PercentageDollarsF32, _, _, _, _>(
|
||||
&cfg.name("net_unrealized_pnl_rel_to_own_total_unrealized_pnl"),
|
||||
cfg.version + v2,
|
||||
&unrealized.net_unrealized_pnl,
|
||||
&unrealized.total_unrealized_pnl,
|
||||
)
|
||||
}),
|
||||
|
||||
// === Invested Capital in Profit/Loss as % of Realized Cap ===
|
||||
invested_capital_in_profit_pct: realized.map(|r| {
|
||||
LazyBinaryFromHeightLast::from_block_last_and_lazy_block_last::<
|
||||
PercentageDollarsF32, _,
|
||||
>(
|
||||
&cfg.name("invested_capital_in_profit_pct"),
|
||||
cfg.version,
|
||||
&unrealized.invested_capital_in_profit,
|
||||
&r.realized_cap,
|
||||
)
|
||||
}),
|
||||
invested_capital_in_loss_pct: realized.map(|r| {
|
||||
LazyBinaryFromHeightLast::from_block_last_and_lazy_block_last::<
|
||||
PercentageDollarsF32, _,
|
||||
>(
|
||||
&cfg.name("invested_capital_in_loss_pct"),
|
||||
cfg.version,
|
||||
&unrealized.invested_capital_in_loss,
|
||||
&r.realized_cap,
|
||||
)
|
||||
}),
|
||||
|
||||
// === Peak Regret Relative to Market Cap ===
|
||||
unrealized_peak_regret_rel_to_market_cap: unrealized
|
||||
.peak_regret
|
||||
.as_ref()
|
||||
.zip(market_cap)
|
||||
.map(|(pr, mc)| {
|
||||
LazyBinaryFromHeightLast::from_block_last_and_lazy_binary_computed_block_last::<
|
||||
PercentageDollarsF32, _, _,
|
||||
>(
|
||||
&cfg.name("unrealized_peak_regret_rel_to_market_cap"),
|
||||
cfg.version,
|
||||
pr,
|
||||
mc,
|
||||
)
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
131
crates/brk_computer/src/distribution/metrics/relative/base.rs
Normal file
131
crates/brk_computer/src/distribution/metrics/relative/base.rs
Normal file
@@ -0,0 +1,131 @@
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Cents, Dollars, Sats, StoredF32, StoredF64, Version};
|
||||
|
||||
use crate::internal::{
|
||||
LazyBinaryComputedFromHeightLast, LazyBinaryFromHeightLast, LazyFromHeightLast,
|
||||
NegPercentageDollarsF32, PercentageDollarsF32, PercentageSatsF64,
|
||||
};
|
||||
|
||||
use crate::distribution::metrics::{ImportConfig, SupplyMetrics, UnrealizedBase};
|
||||
|
||||
/// Base relative metrics (always computed when relative is enabled).
|
||||
/// All fields are non-Optional - market_cap and realized_cap are always
|
||||
/// available when relative metrics are enabled.
|
||||
#[derive(Clone, Traversable)]
|
||||
pub struct RelativeBase {
|
||||
// === Supply in Profit/Loss Relative to Own Supply ===
|
||||
pub supply_in_profit_rel_to_own_supply: LazyBinaryFromHeightLast<StoredF64, Sats, Sats>,
|
||||
pub supply_in_loss_rel_to_own_supply: LazyBinaryFromHeightLast<StoredF64, Sats, Sats>,
|
||||
|
||||
// === Unrealized vs Market Cap ===
|
||||
pub unrealized_profit_rel_to_market_cap: LazyBinaryFromHeightLast<StoredF32, Dollars, Dollars>,
|
||||
pub unrealized_loss_rel_to_market_cap: LazyBinaryFromHeightLast<StoredF32, Dollars, Dollars>,
|
||||
pub neg_unrealized_loss_rel_to_market_cap:
|
||||
LazyBinaryFromHeightLast<StoredF32, Dollars, Dollars>,
|
||||
pub net_unrealized_pnl_rel_to_market_cap:
|
||||
LazyBinaryFromHeightLast<StoredF32, Dollars, Dollars>,
|
||||
pub nupl: LazyBinaryFromHeightLast<StoredF32, Dollars, Dollars>,
|
||||
|
||||
// === Invested Capital in Profit/Loss as % of Realized Cap ===
|
||||
pub invested_capital_in_profit_pct: LazyBinaryFromHeightLast<StoredF32, Dollars, Dollars>,
|
||||
pub invested_capital_in_loss_pct: LazyBinaryFromHeightLast<StoredF32, Dollars, Dollars>,
|
||||
}
|
||||
|
||||
impl RelativeBase {
|
||||
/// Import base relative metrics.
|
||||
///
|
||||
/// `market_cap` is either `all_supply.total.usd` (for non-"all" cohorts)
|
||||
/// or `supply.total.usd` (for the "all" cohort itself).
|
||||
pub(crate) fn forced_import(
|
||||
cfg: &ImportConfig,
|
||||
unrealized: &UnrealizedBase,
|
||||
supply: &SupplyMetrics,
|
||||
market_cap: &LazyBinaryComputedFromHeightLast<Dollars, Sats, Dollars>,
|
||||
realized_cap: &LazyFromHeightLast<Dollars, Cents>,
|
||||
) -> Self {
|
||||
let v1 = Version::ONE;
|
||||
let v2 = Version::new(2);
|
||||
|
||||
Self {
|
||||
supply_in_profit_rel_to_own_supply:
|
||||
LazyBinaryFromHeightLast::from_computed_last::<PercentageSatsF64>(
|
||||
&cfg.name("supply_in_profit_rel_to_own_supply"),
|
||||
cfg.version + v1,
|
||||
&unrealized.supply_in_profit.sats,
|
||||
&supply.total.sats,
|
||||
),
|
||||
supply_in_loss_rel_to_own_supply:
|
||||
LazyBinaryFromHeightLast::from_computed_last::<PercentageSatsF64>(
|
||||
&cfg.name("supply_in_loss_rel_to_own_supply"),
|
||||
cfg.version + v1,
|
||||
&unrealized.supply_in_loss.sats,
|
||||
&supply.total.sats,
|
||||
),
|
||||
|
||||
unrealized_profit_rel_to_market_cap:
|
||||
LazyBinaryFromHeightLast::from_block_last_and_lazy_binary_computed_block_last::<
|
||||
PercentageDollarsF32, _, _,
|
||||
>(
|
||||
&cfg.name("unrealized_profit_rel_to_market_cap"),
|
||||
cfg.version + v2,
|
||||
&unrealized.unrealized_profit,
|
||||
market_cap,
|
||||
),
|
||||
unrealized_loss_rel_to_market_cap:
|
||||
LazyBinaryFromHeightLast::from_block_last_and_lazy_binary_computed_block_last::<
|
||||
PercentageDollarsF32, _, _,
|
||||
>(
|
||||
&cfg.name("unrealized_loss_rel_to_market_cap"),
|
||||
cfg.version + v2,
|
||||
&unrealized.unrealized_loss,
|
||||
market_cap,
|
||||
),
|
||||
neg_unrealized_loss_rel_to_market_cap:
|
||||
LazyBinaryFromHeightLast::from_block_last_and_lazy_binary_computed_block_last::<
|
||||
NegPercentageDollarsF32, _, _,
|
||||
>(
|
||||
&cfg.name("neg_unrealized_loss_rel_to_market_cap"),
|
||||
cfg.version + v2,
|
||||
&unrealized.unrealized_loss,
|
||||
market_cap,
|
||||
),
|
||||
net_unrealized_pnl_rel_to_market_cap:
|
||||
LazyBinaryFromHeightLast::from_binary_block_and_lazy_binary_block_last::<
|
||||
PercentageDollarsF32, _, _, _, _,
|
||||
>(
|
||||
&cfg.name("net_unrealized_pnl_rel_to_market_cap"),
|
||||
cfg.version + v2,
|
||||
&unrealized.net_unrealized_pnl,
|
||||
market_cap,
|
||||
),
|
||||
nupl:
|
||||
LazyBinaryFromHeightLast::from_binary_block_and_lazy_binary_block_last::<
|
||||
PercentageDollarsF32, _, _, _, _,
|
||||
>(
|
||||
&cfg.name("nupl"),
|
||||
cfg.version + v2,
|
||||
&unrealized.net_unrealized_pnl,
|
||||
market_cap,
|
||||
),
|
||||
|
||||
invested_capital_in_profit_pct:
|
||||
LazyBinaryFromHeightLast::from_block_last_and_lazy_block_last::<
|
||||
PercentageDollarsF32, _,
|
||||
>(
|
||||
&cfg.name("invested_capital_in_profit_pct"),
|
||||
cfg.version,
|
||||
&unrealized.invested_capital_in_profit,
|
||||
realized_cap,
|
||||
),
|
||||
invested_capital_in_loss_pct:
|
||||
LazyBinaryFromHeightLast::from_block_last_and_lazy_block_last::<
|
||||
PercentageDollarsF32, _,
|
||||
>(
|
||||
&cfg.name("invested_capital_in_loss_pct"),
|
||||
cfg.version,
|
||||
&unrealized.invested_capital_in_loss,
|
||||
realized_cap,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Dollars, Sats, StoredF32, Version};
|
||||
|
||||
use crate::internal::{
|
||||
LazyBinaryComputedFromHeightLast, LazyBinaryFromHeightLast,
|
||||
NegPercentageDollarsF32, PercentageDollarsF32,
|
||||
};
|
||||
|
||||
use crate::distribution::metrics::{ImportConfig, UnrealizedBase};
|
||||
|
||||
/// Extended relative metrics for own market cap (extended && rel_to_all).
|
||||
#[derive(Clone, Traversable)]
|
||||
pub struct RelativeExtendedOwnMarketCap {
|
||||
pub unrealized_profit_rel_to_own_market_cap:
|
||||
LazyBinaryFromHeightLast<StoredF32, Dollars, Dollars>,
|
||||
pub unrealized_loss_rel_to_own_market_cap:
|
||||
LazyBinaryFromHeightLast<StoredF32, Dollars, Dollars>,
|
||||
pub neg_unrealized_loss_rel_to_own_market_cap:
|
||||
LazyBinaryFromHeightLast<StoredF32, Dollars, Dollars>,
|
||||
pub net_unrealized_pnl_rel_to_own_market_cap:
|
||||
LazyBinaryFromHeightLast<StoredF32, Dollars, Dollars>,
|
||||
}
|
||||
|
||||
impl RelativeExtendedOwnMarketCap {
|
||||
pub(crate) fn forced_import(
|
||||
cfg: &ImportConfig,
|
||||
unrealized: &UnrealizedBase,
|
||||
own_market_cap: &LazyBinaryComputedFromHeightLast<Dollars, Sats, Dollars>,
|
||||
) -> Self {
|
||||
let v2 = Version::new(2);
|
||||
|
||||
Self {
|
||||
unrealized_profit_rel_to_own_market_cap:
|
||||
LazyBinaryFromHeightLast::from_block_last_and_lazy_binary_computed_block_last::<
|
||||
PercentageDollarsF32, _, _,
|
||||
>(
|
||||
&cfg.name("unrealized_profit_rel_to_own_market_cap"),
|
||||
cfg.version + v2,
|
||||
&unrealized.unrealized_profit,
|
||||
own_market_cap,
|
||||
),
|
||||
unrealized_loss_rel_to_own_market_cap:
|
||||
LazyBinaryFromHeightLast::from_block_last_and_lazy_binary_computed_block_last::<
|
||||
PercentageDollarsF32, _, _,
|
||||
>(
|
||||
&cfg.name("unrealized_loss_rel_to_own_market_cap"),
|
||||
cfg.version + v2,
|
||||
&unrealized.unrealized_loss,
|
||||
own_market_cap,
|
||||
),
|
||||
neg_unrealized_loss_rel_to_own_market_cap:
|
||||
LazyBinaryFromHeightLast::from_block_last_and_lazy_binary_computed_block_last::<
|
||||
NegPercentageDollarsF32, _, _,
|
||||
>(
|
||||
&cfg.name("neg_unrealized_loss_rel_to_own_market_cap"),
|
||||
cfg.version + v2,
|
||||
&unrealized.unrealized_loss,
|
||||
own_market_cap,
|
||||
),
|
||||
net_unrealized_pnl_rel_to_own_market_cap:
|
||||
LazyBinaryFromHeightLast::from_binary_block_and_lazy_binary_block_last::<
|
||||
PercentageDollarsF32, _, _, _, _,
|
||||
>(
|
||||
&cfg.name("net_unrealized_pnl_rel_to_own_market_cap"),
|
||||
cfg.version + v2,
|
||||
&unrealized.net_unrealized_pnl,
|
||||
own_market_cap,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Dollars, StoredF32, Version};
|
||||
|
||||
use crate::internal::{
|
||||
LazyBinaryFromHeightLast, NegPercentageDollarsF32, PercentageDollarsF32,
|
||||
};
|
||||
|
||||
use crate::distribution::metrics::{ImportConfig, UnrealizedBase};
|
||||
|
||||
/// Extended relative metrics for own total unrealized PnL (extended only).
|
||||
#[derive(Clone, Traversable)]
|
||||
pub struct RelativeExtendedOwnPnl {
|
||||
pub unrealized_profit_rel_to_own_total_unrealized_pnl:
|
||||
LazyBinaryFromHeightLast<StoredF32, Dollars, Dollars>,
|
||||
pub unrealized_loss_rel_to_own_total_unrealized_pnl:
|
||||
LazyBinaryFromHeightLast<StoredF32, Dollars, Dollars>,
|
||||
pub neg_unrealized_loss_rel_to_own_total_unrealized_pnl:
|
||||
LazyBinaryFromHeightLast<StoredF32, Dollars, Dollars>,
|
||||
pub net_unrealized_pnl_rel_to_own_total_unrealized_pnl:
|
||||
LazyBinaryFromHeightLast<StoredF32, Dollars, Dollars>,
|
||||
}
|
||||
|
||||
impl RelativeExtendedOwnPnl {
|
||||
pub(crate) fn forced_import(
|
||||
cfg: &ImportConfig,
|
||||
unrealized: &UnrealizedBase,
|
||||
) -> Self {
|
||||
let v1 = Version::ONE;
|
||||
let v2 = Version::new(2);
|
||||
|
||||
Self {
|
||||
unrealized_profit_rel_to_own_total_unrealized_pnl:
|
||||
LazyBinaryFromHeightLast::from_block_last_and_binary_block::<PercentageDollarsF32, _, _>(
|
||||
&cfg.name("unrealized_profit_rel_to_own_total_unrealized_pnl"),
|
||||
cfg.version + v1,
|
||||
&unrealized.unrealized_profit,
|
||||
&unrealized.total_unrealized_pnl,
|
||||
),
|
||||
unrealized_loss_rel_to_own_total_unrealized_pnl:
|
||||
LazyBinaryFromHeightLast::from_block_last_and_binary_block::<PercentageDollarsF32, _, _>(
|
||||
&cfg.name("unrealized_loss_rel_to_own_total_unrealized_pnl"),
|
||||
cfg.version + v1,
|
||||
&unrealized.unrealized_loss,
|
||||
&unrealized.total_unrealized_pnl,
|
||||
),
|
||||
neg_unrealized_loss_rel_to_own_total_unrealized_pnl:
|
||||
LazyBinaryFromHeightLast::from_block_last_and_binary_block::<NegPercentageDollarsF32, _, _>(
|
||||
&cfg.name("neg_unrealized_loss_rel_to_own_total_unrealized_pnl"),
|
||||
cfg.version + v1,
|
||||
&unrealized.unrealized_loss,
|
||||
&unrealized.total_unrealized_pnl,
|
||||
),
|
||||
net_unrealized_pnl_rel_to_own_total_unrealized_pnl:
|
||||
LazyBinaryFromHeightLast::from_both_binary_block::<PercentageDollarsF32, _, _, _, _>(
|
||||
&cfg.name("net_unrealized_pnl_rel_to_own_total_unrealized_pnl"),
|
||||
cfg.version + v2,
|
||||
&unrealized.net_unrealized_pnl,
|
||||
&unrealized.total_unrealized_pnl,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
use brk_types::Dollars;
|
||||
use brk_traversable::Traversable;
|
||||
use derive_more::{Deref, DerefMut};
|
||||
|
||||
use crate::internal::ComputedFromHeightLast;
|
||||
|
||||
use crate::distribution::metrics::{ImportConfig, RealizedBase, SupplyMetrics, UnrealizedBase};
|
||||
|
||||
use super::{RelativeBase, RelativeExtendedOwnPnl, RelativePeakRegret};
|
||||
|
||||
/// Relative metrics for the "all" cohort (base + own_pnl + peak_regret, NO rel_to_all).
|
||||
#[derive(Clone, Deref, DerefMut, Traversable)]
|
||||
#[traversable(merge)]
|
||||
pub struct RelativeForAll {
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
#[traversable(flatten)]
|
||||
pub base: RelativeBase,
|
||||
#[traversable(flatten)]
|
||||
pub extended_own_pnl: RelativeExtendedOwnPnl,
|
||||
#[traversable(flatten)]
|
||||
pub peak_regret: RelativePeakRegret,
|
||||
}
|
||||
|
||||
impl RelativeForAll {
|
||||
pub(crate) fn forced_import(
|
||||
cfg: &ImportConfig,
|
||||
unrealized: &UnrealizedBase,
|
||||
supply: &SupplyMetrics,
|
||||
realized_base: &RealizedBase,
|
||||
peak_regret: &ComputedFromHeightLast<Dollars>,
|
||||
) -> Self {
|
||||
// For the "all" cohort, market_cap = own market cap
|
||||
let market_cap = &supply.total.usd;
|
||||
Self {
|
||||
base: RelativeBase::forced_import(
|
||||
cfg, unrealized, supply, market_cap, &realized_base.realized_cap,
|
||||
),
|
||||
extended_own_pnl: RelativeExtendedOwnPnl::forced_import(cfg, unrealized),
|
||||
peak_regret: RelativePeakRegret::forced_import(cfg, peak_regret, market_cap),
|
||||
}
|
||||
}
|
||||
}
|
||||
21
crates/brk_computer/src/distribution/metrics/relative/mod.rs
Normal file
21
crates/brk_computer/src/distribution/metrics/relative/mod.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
mod base;
|
||||
mod extended_own_market_cap;
|
||||
mod extended_own_pnl;
|
||||
mod for_all;
|
||||
|
||||
mod peak_regret;
|
||||
mod to_all;
|
||||
mod with_extended;
|
||||
mod with_peak_regret;
|
||||
mod with_rel_to_all;
|
||||
|
||||
pub use base::*;
|
||||
pub use extended_own_market_cap::*;
|
||||
pub use extended_own_pnl::*;
|
||||
pub use for_all::*;
|
||||
|
||||
pub use peak_regret::*;
|
||||
pub use to_all::*;
|
||||
pub use with_extended::*;
|
||||
pub use with_peak_regret::*;
|
||||
pub use with_rel_to_all::*;
|
||||
@@ -0,0 +1,36 @@
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Dollars, Sats, StoredF32};
|
||||
|
||||
use crate::internal::{
|
||||
ComputedFromHeightLast, LazyBinaryComputedFromHeightLast, LazyBinaryFromHeightLast,
|
||||
PercentageDollarsF32,
|
||||
};
|
||||
|
||||
use crate::distribution::metrics::ImportConfig;
|
||||
|
||||
/// Peak regret relative metric.
|
||||
#[derive(Clone, Traversable)]
|
||||
pub struct RelativePeakRegret {
|
||||
pub unrealized_peak_regret_rel_to_market_cap:
|
||||
LazyBinaryFromHeightLast<StoredF32, Dollars, Dollars>,
|
||||
}
|
||||
|
||||
impl RelativePeakRegret {
|
||||
pub(crate) fn forced_import(
|
||||
cfg: &ImportConfig,
|
||||
peak_regret: &ComputedFromHeightLast<Dollars>,
|
||||
market_cap: &LazyBinaryComputedFromHeightLast<Dollars, Sats, Dollars>,
|
||||
) -> Self {
|
||||
Self {
|
||||
unrealized_peak_regret_rel_to_market_cap:
|
||||
LazyBinaryFromHeightLast::from_block_last_and_lazy_binary_computed_block_last::<
|
||||
PercentageDollarsF32, _, _,
|
||||
>(
|
||||
&cfg.name("unrealized_peak_regret_rel_to_market_cap"),
|
||||
cfg.version,
|
||||
peak_regret,
|
||||
market_cap,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Sats, StoredF64, Version};
|
||||
|
||||
use crate::internal::{LazyBinaryFromHeightLast, PercentageSatsF64};
|
||||
|
||||
use crate::distribution::metrics::{ImportConfig, SupplyMetrics, UnrealizedBase};
|
||||
|
||||
/// Relative-to-all metrics (not present for the "all" cohort itself).
|
||||
#[derive(Clone, Traversable)]
|
||||
pub struct RelativeToAll {
|
||||
pub supply_rel_to_circulating_supply:
|
||||
LazyBinaryFromHeightLast<StoredF64, Sats, Sats>,
|
||||
pub supply_in_profit_rel_to_circulating_supply:
|
||||
LazyBinaryFromHeightLast<StoredF64, Sats, Sats>,
|
||||
pub supply_in_loss_rel_to_circulating_supply:
|
||||
LazyBinaryFromHeightLast<StoredF64, Sats, Sats>,
|
||||
}
|
||||
|
||||
impl RelativeToAll {
|
||||
pub(crate) fn forced_import(
|
||||
cfg: &ImportConfig,
|
||||
unrealized: &UnrealizedBase,
|
||||
supply: &SupplyMetrics,
|
||||
all_supply: &SupplyMetrics,
|
||||
) -> Self {
|
||||
let v1 = Version::ONE;
|
||||
let gs = &all_supply.total.sats;
|
||||
|
||||
Self {
|
||||
supply_rel_to_circulating_supply:
|
||||
LazyBinaryFromHeightLast::from_computed_last::<PercentageSatsF64>(
|
||||
&cfg.name("supply_rel_to_circulating_supply"),
|
||||
cfg.version + v1,
|
||||
&supply.total.sats,
|
||||
gs,
|
||||
),
|
||||
supply_in_profit_rel_to_circulating_supply:
|
||||
LazyBinaryFromHeightLast::from_computed_last::<PercentageSatsF64>(
|
||||
&cfg.name("supply_in_profit_rel_to_circulating_supply"),
|
||||
cfg.version + v1,
|
||||
&unrealized.supply_in_profit.sats,
|
||||
gs,
|
||||
),
|
||||
supply_in_loss_rel_to_circulating_supply:
|
||||
LazyBinaryFromHeightLast::from_computed_last::<PercentageSatsF64>(
|
||||
&cfg.name("supply_in_loss_rel_to_circulating_supply"),
|
||||
cfg.version + v1,
|
||||
&unrealized.supply_in_loss.sats,
|
||||
gs,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
use brk_types::Dollars;
|
||||
use brk_traversable::Traversable;
|
||||
use derive_more::{Deref, DerefMut};
|
||||
|
||||
use crate::internal::ComputedFromHeightLast;
|
||||
|
||||
use crate::distribution::metrics::{ImportConfig, RealizedBase, SupplyMetrics, UnrealizedBase};
|
||||
|
||||
use super::{
|
||||
RelativeBase, RelativeExtendedOwnMarketCap, RelativeExtendedOwnPnl,
|
||||
RelativePeakRegret, RelativeToAll,
|
||||
};
|
||||
|
||||
/// Full extended relative metrics (base + rel_to_all + own_market_cap + own_pnl + peak_regret).
|
||||
/// Used by: sth, lth, age_range cohorts.
|
||||
#[derive(Clone, Deref, DerefMut, Traversable)]
|
||||
#[traversable(merge)]
|
||||
pub struct RelativeWithExtended {
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
#[traversable(flatten)]
|
||||
pub base: RelativeBase,
|
||||
#[traversable(flatten)]
|
||||
pub rel_to_all: RelativeToAll,
|
||||
#[traversable(flatten)]
|
||||
pub extended_own_market_cap: RelativeExtendedOwnMarketCap,
|
||||
#[traversable(flatten)]
|
||||
pub extended_own_pnl: RelativeExtendedOwnPnl,
|
||||
#[traversable(flatten)]
|
||||
pub peak_regret: RelativePeakRegret,
|
||||
}
|
||||
|
||||
impl RelativeWithExtended {
|
||||
pub(crate) fn forced_import(
|
||||
cfg: &ImportConfig,
|
||||
unrealized: &UnrealizedBase,
|
||||
supply: &SupplyMetrics,
|
||||
all_supply: &SupplyMetrics,
|
||||
realized_base: &RealizedBase,
|
||||
peak_regret: &ComputedFromHeightLast<Dollars>,
|
||||
) -> Self {
|
||||
let market_cap = &all_supply.total.usd;
|
||||
let own_market_cap = &supply.total.usd;
|
||||
Self {
|
||||
base: RelativeBase::forced_import(
|
||||
cfg, unrealized, supply, market_cap, &realized_base.realized_cap,
|
||||
),
|
||||
rel_to_all: RelativeToAll::forced_import(cfg, unrealized, supply, all_supply),
|
||||
extended_own_market_cap: RelativeExtendedOwnMarketCap::forced_import(
|
||||
cfg, unrealized, own_market_cap,
|
||||
),
|
||||
extended_own_pnl: RelativeExtendedOwnPnl::forced_import(cfg, unrealized),
|
||||
peak_regret: RelativePeakRegret::forced_import(cfg, peak_regret, market_cap),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
use brk_types::Dollars;
|
||||
use brk_traversable::Traversable;
|
||||
use derive_more::{Deref, DerefMut};
|
||||
|
||||
use crate::internal::ComputedFromHeightLast;
|
||||
|
||||
use crate::distribution::metrics::{ImportConfig, RealizedBase, SupplyMetrics, UnrealizedBase};
|
||||
|
||||
use super::{RelativeBase, RelativePeakRegret, RelativeToAll};
|
||||
|
||||
/// Relative metrics with rel_to_all + peak_regret (no extended).
|
||||
/// Used by: max_age, min_age cohorts.
|
||||
#[derive(Clone, Deref, DerefMut, Traversable)]
|
||||
#[traversable(merge)]
|
||||
pub struct RelativeWithPeakRegret {
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
#[traversable(flatten)]
|
||||
pub base: RelativeBase,
|
||||
#[traversable(flatten)]
|
||||
pub rel_to_all: RelativeToAll,
|
||||
#[traversable(flatten)]
|
||||
pub peak_regret: RelativePeakRegret,
|
||||
}
|
||||
|
||||
impl RelativeWithPeakRegret {
|
||||
pub(crate) fn forced_import(
|
||||
cfg: &ImportConfig,
|
||||
unrealized: &UnrealizedBase,
|
||||
supply: &SupplyMetrics,
|
||||
all_supply: &SupplyMetrics,
|
||||
realized_base: &RealizedBase,
|
||||
peak_regret: &ComputedFromHeightLast<Dollars>,
|
||||
) -> Self {
|
||||
let market_cap = &all_supply.total.usd;
|
||||
Self {
|
||||
base: RelativeBase::forced_import(
|
||||
cfg, unrealized, supply, market_cap, &realized_base.realized_cap,
|
||||
),
|
||||
rel_to_all: RelativeToAll::forced_import(cfg, unrealized, supply, all_supply),
|
||||
peak_regret: RelativePeakRegret::forced_import(cfg, peak_regret, market_cap),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
use brk_traversable::Traversable;
|
||||
use derive_more::{Deref, DerefMut};
|
||||
|
||||
use crate::distribution::metrics::{ImportConfig, RealizedBase, SupplyMetrics, UnrealizedBase};
|
||||
|
||||
use super::{RelativeBase, RelativeToAll};
|
||||
|
||||
/// Relative metrics with rel_to_all (no extended, no peak_regret).
|
||||
/// Used by: epoch, year, type, amount, address cohorts.
|
||||
#[derive(Clone, Deref, DerefMut, Traversable)]
|
||||
#[traversable(merge)]
|
||||
pub struct RelativeWithRelToAll {
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
#[traversable(flatten)]
|
||||
pub base: RelativeBase,
|
||||
#[traversable(flatten)]
|
||||
pub rel_to_all: RelativeToAll,
|
||||
}
|
||||
|
||||
impl RelativeWithRelToAll {
|
||||
pub(crate) fn forced_import(
|
||||
cfg: &ImportConfig,
|
||||
unrealized: &UnrealizedBase,
|
||||
supply: &SupplyMetrics,
|
||||
all_supply: &SupplyMetrics,
|
||||
realized_base: &RealizedBase,
|
||||
) -> Self {
|
||||
let market_cap = &all_supply.total.usd;
|
||||
Self {
|
||||
base: RelativeBase::forced_import(
|
||||
cfg, unrealized, supply, market_cap, &realized_base.realized_cap,
|
||||
),
|
||||
rel_to_all: RelativeToAll::forced_import(cfg, unrealized, supply, all_supply),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,26 @@
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Cents, CentsSats, CentsSquaredSats, Dollars, Height, Version};
|
||||
use rayon::prelude::*;
|
||||
use vecdb::{
|
||||
AnyStoredVec, AnyVec, BytesVec, Exit, WritableVec, ImportableVec, ReadableCloneableVec,
|
||||
ReadableVec, Negate, Rw, StorageMode,
|
||||
AnyStoredVec, AnyVec, BytesVec, Exit, ImportableVec, Negate, ReadableCloneableVec, ReadableVec,
|
||||
Rw, StorageMode, WritableVec,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
ComputeIndexes,
|
||||
distribution::state::UnrealizedState,
|
||||
internal::{
|
||||
ComputedFromHeightLast, DollarsMinus, DollarsPlus,
|
||||
LazyBinaryFromHeightLast, LazyFromHeightLast, ValueFromHeightLast,
|
||||
ComputedFromHeightLast, DollarsMinus, DollarsPlus, LazyBinaryFromHeightLast,
|
||||
LazyFromHeightLast, ValueFromHeightLast,
|
||||
},
|
||||
prices,
|
||||
};
|
||||
|
||||
use super::ImportConfig;
|
||||
use crate::distribution::metrics::ImportConfig;
|
||||
|
||||
/// Unrealized profit/loss metrics.
|
||||
/// Base unrealized profit/loss metrics (always computed).
|
||||
#[derive(Traversable)]
|
||||
pub struct UnrealizedMetrics<M: StorageMode = Rw> {
|
||||
pub struct UnrealizedBase<M: StorageMode = Rw> {
|
||||
// === Supply in Profit/Loss ===
|
||||
pub supply_in_profit: ValueFromHeightLast<M>,
|
||||
pub supply_in_loss: ValueFromHeightLast<M>,
|
||||
@@ -35,21 +34,14 @@ pub struct UnrealizedMetrics<M: StorageMode = Rw> {
|
||||
pub invested_capital_in_loss: ComputedFromHeightLast<Dollars, M>,
|
||||
|
||||
// === Raw values for precise aggregation (used to compute pain/greed indices) ===
|
||||
/// Σ(price × sats) for UTXOs in profit (raw u128, no indexes)
|
||||
pub invested_capital_in_profit_raw: M::Stored<BytesVec<Height, CentsSats>>,
|
||||
/// Σ(price × sats) for UTXOs in loss (raw u128, no indexes)
|
||||
pub invested_capital_in_loss_raw: M::Stored<BytesVec<Height, CentsSats>>,
|
||||
/// Σ(price² × sats) for UTXOs in profit (raw u128, no indexes)
|
||||
pub investor_cap_in_profit_raw: M::Stored<BytesVec<Height, CentsSquaredSats>>,
|
||||
/// Σ(price² × sats) for UTXOs in loss (raw u128, no indexes)
|
||||
pub investor_cap_in_loss_raw: M::Stored<BytesVec<Height, CentsSquaredSats>>,
|
||||
|
||||
// === Pain/Greed Indices (computed in compute_rest from raw values + spot price) ===
|
||||
/// investor_price_of_losers - spot (average distance underwater, weighted by $)
|
||||
// === Pain/Greed Indices ===
|
||||
pub pain_index: ComputedFromHeightLast<Dollars, M>,
|
||||
/// spot - investor_price_of_winners (average distance in profit, weighted by $)
|
||||
pub greed_index: ComputedFromHeightLast<Dollars, M>,
|
||||
/// greed_index - pain_index (positive = greedy market, negative = painful market)
|
||||
pub net_sentiment: ComputedFromHeightLast<Dollars, M>,
|
||||
|
||||
// === Negated ===
|
||||
@@ -58,18 +50,10 @@ pub struct UnrealizedMetrics<M: StorageMode = Rw> {
|
||||
// === Net and Total ===
|
||||
pub net_unrealized_pnl: LazyBinaryFromHeightLast<Dollars>,
|
||||
pub total_unrealized_pnl: LazyBinaryFromHeightLast<Dollars>,
|
||||
|
||||
// === Peak Regret (age_range cohorts only) ===
|
||||
/// Unrealized peak regret: sum of (peak_price - reference_price) × supply
|
||||
/// where reference_price = max(spot, cost_basis) and peak = max price during holding period.
|
||||
/// Only computed for age_range cohorts, then aggregated for overlapping cohorts.
|
||||
pub peak_regret: Option<ComputedFromHeightLast<Dollars, M>>,
|
||||
}
|
||||
|
||||
impl UnrealizedMetrics {
|
||||
/// Import unrealized metrics from database.
|
||||
impl UnrealizedBase {
|
||||
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
|
||||
// === Supply in Profit/Loss ===
|
||||
let supply_in_profit = ValueFromHeightLast::forced_import(
|
||||
cfg.db,
|
||||
&cfg.name("supply_in_profit"),
|
||||
@@ -85,7 +69,6 @@ impl UnrealizedMetrics {
|
||||
cfg.prices,
|
||||
)?;
|
||||
|
||||
// === Unrealized Profit/Loss ===
|
||||
let unrealized_profit = ComputedFromHeightLast::forced_import(
|
||||
cfg.db,
|
||||
&cfg.name("unrealized_profit"),
|
||||
@@ -99,7 +82,6 @@ impl UnrealizedMetrics {
|
||||
cfg.indexes,
|
||||
)?;
|
||||
|
||||
// === Invested Capital in Profit/Loss ===
|
||||
let invested_capital_in_profit = ComputedFromHeightLast::forced_import(
|
||||
cfg.db,
|
||||
&cfg.name("invested_capital_in_profit"),
|
||||
@@ -113,7 +95,6 @@ impl UnrealizedMetrics {
|
||||
cfg.indexes,
|
||||
)?;
|
||||
|
||||
// === Raw values for precise aggregation ===
|
||||
let invested_capital_in_profit_raw = BytesVec::forced_import(
|
||||
cfg.db,
|
||||
&cfg.name("invested_capital_in_profit_raw"),
|
||||
@@ -124,12 +105,17 @@ impl UnrealizedMetrics {
|
||||
&cfg.name("invested_capital_in_loss_raw"),
|
||||
cfg.version,
|
||||
)?;
|
||||
let investor_cap_in_profit_raw =
|
||||
BytesVec::forced_import(cfg.db, &cfg.name("investor_cap_in_profit_raw"), cfg.version)?;
|
||||
let investor_cap_in_loss_raw =
|
||||
BytesVec::forced_import(cfg.db, &cfg.name("investor_cap_in_loss_raw"), cfg.version)?;
|
||||
let investor_cap_in_profit_raw = BytesVec::forced_import(
|
||||
cfg.db,
|
||||
&cfg.name("investor_cap_in_profit_raw"),
|
||||
cfg.version,
|
||||
)?;
|
||||
let investor_cap_in_loss_raw = BytesVec::forced_import(
|
||||
cfg.db,
|
||||
&cfg.name("investor_cap_in_loss_raw"),
|
||||
cfg.version,
|
||||
)?;
|
||||
|
||||
// === Pain/Greed Indices ===
|
||||
let pain_index = ComputedFromHeightLast::forced_import(
|
||||
cfg.db,
|
||||
&cfg.name("pain_index"),
|
||||
@@ -145,11 +131,10 @@ impl UnrealizedMetrics {
|
||||
let net_sentiment = ComputedFromHeightLast::forced_import(
|
||||
cfg.db,
|
||||
&cfg.name("net_sentiment"),
|
||||
cfg.version + Version::ONE, // v1: weighted average for aggregate cohorts
|
||||
cfg.version + Version::ONE,
|
||||
cfg.indexes,
|
||||
)?;
|
||||
|
||||
// === Negated ===
|
||||
let neg_unrealized_loss = LazyFromHeightLast::from_computed::<Negate>(
|
||||
&cfg.name("neg_unrealized_loss"),
|
||||
cfg.version,
|
||||
@@ -157,7 +142,6 @@ impl UnrealizedMetrics {
|
||||
&unrealized_loss,
|
||||
);
|
||||
|
||||
// === Net and Total ===
|
||||
let net_unrealized_pnl = LazyBinaryFromHeightLast::from_computed_last::<DollarsMinus>(
|
||||
&cfg.name("net_unrealized_pnl"),
|
||||
cfg.version,
|
||||
@@ -171,19 +155,6 @@ impl UnrealizedMetrics {
|
||||
&unrealized_loss,
|
||||
);
|
||||
|
||||
// Peak regret: only for age-based UTXO cohorts
|
||||
let peak_regret = cfg
|
||||
.compute_peak_regret()
|
||||
.then(|| {
|
||||
ComputedFromHeightLast::forced_import(
|
||||
cfg.db,
|
||||
&cfg.name("unrealized_peak_regret"),
|
||||
cfg.version,
|
||||
cfg.indexes,
|
||||
)
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
Ok(Self {
|
||||
supply_in_profit,
|
||||
supply_in_loss,
|
||||
@@ -201,11 +172,9 @@ impl UnrealizedMetrics {
|
||||
neg_unrealized_loss,
|
||||
net_unrealized_pnl,
|
||||
total_unrealized_pnl,
|
||||
peak_regret,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get minimum length across height-indexed vectors written in block loop.
|
||||
pub(crate) fn min_stateful_height_len(&self) -> usize {
|
||||
self.supply_in_profit
|
||||
.sats
|
||||
@@ -222,8 +191,11 @@ impl UnrealizedMetrics {
|
||||
.min(self.investor_cap_in_loss_raw.len())
|
||||
}
|
||||
|
||||
/// Push unrealized state values to height-indexed vectors.
|
||||
pub(crate) fn truncate_push(&mut self, height: Height, height_state: &UnrealizedState) -> Result<()> {
|
||||
pub(crate) fn truncate_push(
|
||||
&mut self,
|
||||
height: Height,
|
||||
height_state: &UnrealizedState,
|
||||
) -> Result<()> {
|
||||
self.supply_in_profit
|
||||
.sats
|
||||
.height
|
||||
@@ -245,7 +217,6 @@ impl UnrealizedMetrics {
|
||||
.height
|
||||
.truncate_push(height, height_state.invested_capital_in_loss.to_dollars())?;
|
||||
|
||||
// Raw values for aggregation
|
||||
self.invested_capital_in_profit_raw.truncate_push(
|
||||
height,
|
||||
CentsSats::new(height_state.invested_capital_in_profit_raw),
|
||||
@@ -266,57 +237,59 @@ impl UnrealizedMetrics {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns a parallel iterator over all vecs for parallel writing.
|
||||
pub(crate) fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
|
||||
let mut vecs: Vec<&mut dyn AnyStoredVec> = vec![
|
||||
&mut self.supply_in_profit.sats.height,
|
||||
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_loss.sats.height,
|
||||
&mut self.unrealized_profit.height,
|
||||
&mut self.unrealized_loss.height,
|
||||
&mut self.invested_capital_in_profit.height,
|
||||
&mut self.invested_capital_in_loss.height,
|
||||
&mut self.invested_capital_in_profit_raw,
|
||||
&mut self.invested_capital_in_loss_raw,
|
||||
&mut self.investor_cap_in_profit_raw,
|
||||
&mut self.investor_cap_in_loss_raw,
|
||||
];
|
||||
if let Some(pr) = &mut self.peak_regret {
|
||||
vecs.push(&mut pr.height);
|
||||
}
|
||||
vecs.into_par_iter()
|
||||
&mut self.invested_capital_in_profit_raw as &mut dyn AnyStoredVec,
|
||||
&mut self.invested_capital_in_loss_raw as &mut dyn AnyStoredVec,
|
||||
&mut self.investor_cap_in_profit_raw as &mut dyn AnyStoredVec,
|
||||
&mut self.investor_cap_in_loss_raw as &mut dyn AnyStoredVec,
|
||||
]
|
||||
}
|
||||
|
||||
/// Compute aggregate values from separate cohorts.
|
||||
pub(crate) fn compute_from_stateful(
|
||||
&mut self,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
others: &[&Self],
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.supply_in_profit.sats.height.compute_sum_of_others(
|
||||
starting_indexes.height,
|
||||
&others
|
||||
.iter()
|
||||
.map(|v| &v.supply_in_profit.sats.height)
|
||||
.collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
self.supply_in_loss.sats.height.compute_sum_of_others(
|
||||
starting_indexes.height,
|
||||
&others
|
||||
.iter()
|
||||
.map(|v| &v.supply_in_loss.sats.height)
|
||||
.collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
self.unrealized_profit.height.compute_sum_of_others(
|
||||
starting_indexes.height,
|
||||
&others
|
||||
.iter()
|
||||
.map(|v| &v.unrealized_profit.height)
|
||||
.collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
self.supply_in_profit
|
||||
.sats
|
||||
.height
|
||||
.compute_sum_of_others(
|
||||
starting_indexes.height,
|
||||
&others
|
||||
.iter()
|
||||
.map(|v| &v.supply_in_profit.sats.height)
|
||||
.collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
self.supply_in_loss
|
||||
.sats
|
||||
.height
|
||||
.compute_sum_of_others(
|
||||
starting_indexes.height,
|
||||
&others
|
||||
.iter()
|
||||
.map(|v| &v.supply_in_loss.sats.height)
|
||||
.collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
self.unrealized_profit
|
||||
.height
|
||||
.compute_sum_of_others(
|
||||
starting_indexes.height,
|
||||
&others
|
||||
.iter()
|
||||
.map(|v| &v.unrealized_profit.height)
|
||||
.collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
self.unrealized_loss.height.compute_sum_of_others(
|
||||
starting_indexes.height,
|
||||
&others
|
||||
@@ -335,24 +308,24 @@ impl UnrealizedMetrics {
|
||||
.collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
self.invested_capital_in_loss.height.compute_sum_of_others(
|
||||
starting_indexes.height,
|
||||
&others
|
||||
.iter()
|
||||
.map(|v| &v.invested_capital_in_loss.height)
|
||||
.collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
self.invested_capital_in_loss
|
||||
.height
|
||||
.compute_sum_of_others(
|
||||
starting_indexes.height,
|
||||
&others
|
||||
.iter()
|
||||
.map(|v| &v.invested_capital_in_loss.height)
|
||||
.collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
|
||||
// Raw values for aggregation - manually sum since BytesVec doesn't have compute_sum_of_others
|
||||
// Start from where the target vecs left off (handles fresh/reset vecs)
|
||||
// Raw values for aggregation
|
||||
let start = self
|
||||
.invested_capital_in_profit_raw
|
||||
.len()
|
||||
.min(self.invested_capital_in_loss_raw.len())
|
||||
.min(self.investor_cap_in_profit_raw.len())
|
||||
.min(self.investor_cap_in_loss_raw.len());
|
||||
// End at the minimum length across all source vecs
|
||||
let end = others
|
||||
.iter()
|
||||
.map(|o| o.invested_capital_in_profit_raw.len())
|
||||
@@ -368,10 +341,22 @@ impl UnrealizedMetrics {
|
||||
let mut sum_investor_loss = CentsSquaredSats::ZERO;
|
||||
|
||||
for o in others.iter() {
|
||||
sum_invested_profit += o.invested_capital_in_profit_raw.collect_one_at(i).unwrap();
|
||||
sum_invested_loss += o.invested_capital_in_loss_raw.collect_one_at(i).unwrap();
|
||||
sum_investor_profit += o.investor_cap_in_profit_raw.collect_one_at(i).unwrap();
|
||||
sum_investor_loss += o.investor_cap_in_loss_raw.collect_one_at(i).unwrap();
|
||||
sum_invested_profit += o
|
||||
.invested_capital_in_profit_raw
|
||||
.collect_one_at(i)
|
||||
.unwrap();
|
||||
sum_invested_loss += o
|
||||
.invested_capital_in_loss_raw
|
||||
.collect_one_at(i)
|
||||
.unwrap();
|
||||
sum_investor_profit += o
|
||||
.investor_cap_in_profit_raw
|
||||
.collect_one_at(i)
|
||||
.unwrap();
|
||||
sum_investor_loss += o
|
||||
.investor_cap_in_loss_raw
|
||||
.collect_one_at(i)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
self.invested_capital_in_profit_raw
|
||||
@@ -384,21 +369,6 @@ impl UnrealizedMetrics {
|
||||
.truncate_push(height, sum_investor_loss)?;
|
||||
}
|
||||
|
||||
// Peak regret aggregation (only if this cohort has peak_regret)
|
||||
if let Some(pr) = &mut self.peak_regret {
|
||||
let other_prs: Vec<_> = others
|
||||
.iter()
|
||||
.filter_map(|v| v.peak_regret.as_ref())
|
||||
.collect();
|
||||
if !other_prs.is_empty() {
|
||||
pr.height.compute_sum_of_others(
|
||||
starting_indexes.height,
|
||||
&other_prs.iter().map(|v| &v.height).collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -409,8 +379,6 @@ impl UnrealizedMetrics {
|
||||
starting_indexes: &ComputeIndexes,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
// Height-based types now have lazy day1, no compute_rest needed.
|
||||
|
||||
// Pain index: investor_price_of_losers - spot
|
||||
self.pain_index.height.compute_transform3(
|
||||
starting_indexes.height,
|
||||
@@ -451,15 +419,10 @@ impl UnrealizedMetrics {
|
||||
exit,
|
||||
)?;
|
||||
|
||||
// Net sentiment height (greed - pain) computed separately for separate cohorts only
|
||||
// Aggregate cohorts compute it via weighted average in compute_from_stateful
|
||||
// Dateindex derivation for ALL cohorts happens in compute_net_sentiment_rest
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Compute net_sentiment.height for separate cohorts (greed - pain).
|
||||
/// Aggregate cohorts skip this - their height is computed via weighted average in compute_from_stateful.
|
||||
pub(crate) fn compute_net_sentiment_height(
|
||||
&mut self,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
@@ -0,0 +1,9 @@
|
||||
mod base;
|
||||
|
||||
mod peak_regret;
|
||||
mod with_peak_regret;
|
||||
|
||||
pub use base::*;
|
||||
|
||||
pub use peak_regret::*;
|
||||
pub use with_peak_regret::*;
|
||||
@@ -0,0 +1,49 @@
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::Dollars;
|
||||
use vecdb::{AnyStoredVec, Exit, Rw, StorageMode};
|
||||
|
||||
use crate::{ComputeIndexes, internal::ComputedFromHeightLast};
|
||||
|
||||
use crate::distribution::metrics::ImportConfig;
|
||||
|
||||
/// Unrealized peak regret extension (only for age-based UTXO cohorts).
|
||||
#[derive(Traversable)]
|
||||
pub struct UnrealizedPeakRegret<M: StorageMode = Rw> {
|
||||
/// Unrealized peak regret: sum of (peak_price - reference_price) x supply
|
||||
pub peak_regret: ComputedFromHeightLast<Dollars, M>,
|
||||
}
|
||||
|
||||
impl UnrealizedPeakRegret {
|
||||
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
|
||||
Ok(Self {
|
||||
peak_regret: ComputedFromHeightLast::forced_import(
|
||||
cfg.db,
|
||||
&cfg.name("unrealized_peak_regret"),
|
||||
cfg.version,
|
||||
cfg.indexes,
|
||||
)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
|
||||
vec![&mut self.peak_regret.height]
|
||||
}
|
||||
|
||||
pub(crate) fn compute_from_stateful(
|
||||
&mut self,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
others: &[&Self],
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.peak_regret.height.compute_sum_of_others(
|
||||
starting_indexes.height,
|
||||
&others
|
||||
.iter()
|
||||
.map(|v| &v.peak_regret.height)
|
||||
.collect::<Vec<_>>(),
|
||||
exit,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use vecdb::{Rw, StorageMode};
|
||||
|
||||
use crate::distribution::metrics::ImportConfig;
|
||||
|
||||
use super::{UnrealizedBase, UnrealizedPeakRegret};
|
||||
|
||||
/// Unrealized metrics with guaranteed peak regret (no Option).
|
||||
#[derive(Deref, DerefMut, Traversable)]
|
||||
#[traversable(merge)]
|
||||
pub struct UnrealizedWithPeakRegret<M: StorageMode = Rw> {
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
#[traversable(flatten)]
|
||||
pub base: UnrealizedBase<M>,
|
||||
#[traversable(flatten)]
|
||||
pub peak_regret_ext: UnrealizedPeakRegret<M>,
|
||||
}
|
||||
|
||||
impl UnrealizedWithPeakRegret {
|
||||
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
|
||||
Ok(Self {
|
||||
base: UnrealizedBase::forced_import(cfg)?,
|
||||
peak_regret_ext: UnrealizedPeakRegret::forced_import(cfg)?,
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
@@ -33,10 +33,6 @@ impl AddressCohortState {
|
||||
self.inner.realized = RealizedState::default();
|
||||
}
|
||||
|
||||
pub(crate) fn reset_cost_basis_data_if_needed(&mut self) -> Result<()> {
|
||||
self.inner.reset_cost_basis_data_if_needed()
|
||||
}
|
||||
|
||||
pub(crate) fn send(
|
||||
&mut self,
|
||||
addressdata: &mut FundedAddressData,
|
||||
|
||||
@@ -90,7 +90,7 @@ impl Vecs {
|
||||
indexes,
|
||||
prices,
|
||||
&states_path,
|
||||
Some(&utxo_cohorts.all.metrics.supply),
|
||||
&utxo_cohorts.all.metrics.supply,
|
||||
)?;
|
||||
|
||||
// Create address data BytesVecs first so we can also use them for identity mappings
|
||||
@@ -374,7 +374,7 @@ impl Vecs {
|
||||
blocks,
|
||||
prices,
|
||||
starting_indexes,
|
||||
Some(&height_to_market_cap),
|
||||
&height_to_market_cap,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use brk_error::Result;
|
||||
use brk_traversable::{Traversable, TreeNode};
|
||||
use brk_types::{Dollars, Height, StoredF32, Version};
|
||||
use vecdb::{
|
||||
AnyExportableVec, AnyVec, Database, ReadOnlyClone, Ro, Rw, StorageMode, WritableVec,
|
||||
AnyExportableVec, Database, ReadOnlyClone, Ro, Rw, StorageMode, WritableVec,
|
||||
};
|
||||
|
||||
use crate::indexes;
|
||||
@@ -90,16 +90,6 @@ impl PercentilesVecs {
|
||||
Ok(Self { vecs })
|
||||
}
|
||||
|
||||
/// Get minimum length across height-indexed vectors written in block loop.
|
||||
pub(crate) fn min_stateful_height_len(&self) -> usize {
|
||||
self.vecs
|
||||
.iter()
|
||||
.filter_map(|v| v.as_ref())
|
||||
.map(|v| v.height.len())
|
||||
.min()
|
||||
.unwrap_or(usize::MAX)
|
||||
}
|
||||
|
||||
/// Push percentile prices at this height.
|
||||
pub(crate) fn truncate_push(
|
||||
&mut self,
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{
|
||||
Day1, Day3, DifficultyEpoch, HalvingEpoch, Hour1, Hour12, Hour4, Minute1, Minute10, Minute30,
|
||||
Minute5, Month1, Month3, Month6, Version, Week1, Year1, Year10,
|
||||
Day1, Day3, DifficultyEpoch, HalvingEpoch, Hour1, Hour4, Hour12, Minute1, Minute5, Minute10,
|
||||
Minute30, Month1, Month3, Month6, Version, Week1, Year1, Year10,
|
||||
};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use schemars::JsonSchema;
|
||||
@@ -19,29 +19,30 @@ use crate::{
|
||||
},
|
||||
};
|
||||
|
||||
pub type LazyHeightDerivedLastInner<T, S1T> = Indexes<
|
||||
LazyTransformLast<Minute1, T, S1T>,
|
||||
LazyTransformLast<Minute5, T, S1T>,
|
||||
LazyTransformLast<Minute10, T, S1T>,
|
||||
LazyTransformLast<Minute30, T, S1T>,
|
||||
LazyTransformLast<Hour1, T, S1T>,
|
||||
LazyTransformLast<Hour4, T, S1T>,
|
||||
LazyTransformLast<Hour12, T, S1T>,
|
||||
LazyTransformLast<Day1, T, S1T>,
|
||||
LazyTransformLast<Day3, T, S1T>,
|
||||
LazyTransformLast<Week1, T, S1T>,
|
||||
LazyTransformLast<Month1, T, S1T>,
|
||||
LazyTransformLast<Month3, T, S1T>,
|
||||
LazyTransformLast<Month6, T, S1T>,
|
||||
LazyTransformLast<Year1, T, S1T>,
|
||||
LazyTransformLast<Year10, T, S1T>,
|
||||
LazyTransformLast<HalvingEpoch, T, S1T>,
|
||||
LazyTransformLast<DifficultyEpoch, T, S1T>,
|
||||
>;
|
||||
|
||||
#[derive(Clone, Deref, DerefMut, Traversable)]
|
||||
#[traversable(transparent)]
|
||||
pub struct LazyHeightDerivedLast<T, S1T = T>(pub LazyHeightDerivedLastInner<T, S1T>)
|
||||
pub struct LazyHeightDerivedLast<T, S1T = T>(
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub Indexes<
|
||||
LazyTransformLast<Minute1, T, S1T>,
|
||||
LazyTransformLast<Minute5, T, S1T>,
|
||||
LazyTransformLast<Minute10, T, S1T>,
|
||||
LazyTransformLast<Minute30, T, S1T>,
|
||||
LazyTransformLast<Hour1, T, S1T>,
|
||||
LazyTransformLast<Hour4, T, S1T>,
|
||||
LazyTransformLast<Hour12, T, S1T>,
|
||||
LazyTransformLast<Day1, T, S1T>,
|
||||
LazyTransformLast<Day3, T, S1T>,
|
||||
LazyTransformLast<Week1, T, S1T>,
|
||||
LazyTransformLast<Month1, T, S1T>,
|
||||
LazyTransformLast<Month3, T, S1T>,
|
||||
LazyTransformLast<Month6, T, S1T>,
|
||||
LazyTransformLast<Year1, T, S1T>,
|
||||
LazyTransformLast<Year10, T, S1T>,
|
||||
LazyTransformLast<HalvingEpoch, T, S1T>,
|
||||
LazyTransformLast<DifficultyEpoch, T, S1T>,
|
||||
>,
|
||||
)
|
||||
where
|
||||
T: ComputedVecValue + PartialOrd + JsonSchema,
|
||||
S1T: ComputedVecValue;
|
||||
|
||||
@@ -200,6 +200,5 @@ pub use max::*;
|
||||
pub use min::*;
|
||||
pub use percentile::*;
|
||||
pub use percentiles::*;
|
||||
pub use sparse_last::*;
|
||||
pub use sum::*;
|
||||
pub use sum_cum::*;
|
||||
|
||||
Reference in New Issue
Block a user