global: snapshot

This commit is contained in:
nym21
2026-03-04 17:10:15 +01:00
parent 891f0dad9e
commit 9e23de4ba1
313 changed files with 9087 additions and 4918 deletions

View File

@@ -22,7 +22,10 @@ use derive_more::{Deref, DerefMut};
use rayon::prelude::*;
use vecdb::{AnyStoredVec, AnyVec, Database, Exit, Rw, StorageMode, WritableVec};
use crate::{indexes, internal::{ComputedFromHeightDistribution, WindowStarts}};
use crate::{
indexes,
internal::{ComputedFromHeightDistribution, WindowStarts},
};
/// Per-block activity counts - reset each block.
///
@@ -137,7 +140,9 @@ impl ActivityCountVecs {
.min(self.both.height.len())
}
pub(crate) fn par_iter_height_mut(&mut self) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
pub(crate) fn par_iter_height_mut(
&mut self,
) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
[
&mut self.reactivated.height as &mut dyn AnyStoredVec,
&mut self.sending.height as &mut dyn AnyStoredVec,
@@ -181,9 +186,7 @@ impl ActivityCountVecs {
self.balance_decreased
.height
.truncate_push(height, (counts.sending - counts.both).into())?;
self.both
.height
.truncate_push(height, counts.both.into())?;
self.both.height.truncate_push(height, counts.both.into())?;
Ok(())
}
@@ -196,8 +199,10 @@ impl ActivityCountVecs {
self.reactivated.compute_rest(max_from, windows, exit)?;
self.sending.compute_rest(max_from, windows, exit)?;
self.receiving.compute_rest(max_from, windows, exit)?;
self.balance_increased.compute_rest(max_from, windows, exit)?;
self.balance_decreased.compute_rest(max_from, windows, exit)?;
self.balance_increased
.compute_rest(max_from, windows, exit)?;
self.balance_decreased
.compute_rest(max_from, windows, exit)?;
self.both.compute_rest(max_from, windows, exit)?;
Ok(())
}
@@ -223,16 +228,27 @@ impl AddressTypeToActivityCountVecs {
) -> Result<Self> {
Ok(Self::from(
ByAddressType::<ActivityCountVecs>::new_with_name(|type_name| {
ActivityCountVecs::forced_import(db, &format!("{type_name}_{name}"), version, indexes)
ActivityCountVecs::forced_import(
db,
&format!("{type_name}_{name}"),
version,
indexes,
)
})?,
))
}
pub(crate) fn min_stateful_height(&self) -> usize {
self.0.values().map(|v| v.min_stateful_height()).min().unwrap_or(0)
self.0
.values()
.map(|v| v.min_stateful_height())
.min()
.unwrap_or(0)
}
pub(crate) fn par_iter_height_mut(&mut self) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
pub(crate) fn par_iter_height_mut(
&mut self,
) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
let mut vecs: Vec<&mut dyn AnyStoredVec> = Vec::new();
for type_vecs in self.0.values_mut() {
vecs.push(&mut type_vecs.reactivated.height);
@@ -274,7 +290,6 @@ impl AddressTypeToActivityCountVecs {
}
Ok(())
}
}
/// Storage for activity metrics (global + per type).
@@ -301,10 +316,14 @@ impl AddressActivityVecs {
}
pub(crate) fn min_stateful_height(&self) -> usize {
self.all.min_stateful_height().min(self.by_addresstype.min_stateful_height())
self.all
.min_stateful_height()
.min(self.by_addresstype.min_stateful_height())
}
pub(crate) fn par_iter_height_mut(&mut self) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
pub(crate) fn par_iter_height_mut(
&mut self,
) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
self.all
.par_iter_height_mut()
.chain(self.by_addresstype.par_iter_height_mut())
@@ -337,5 +356,4 @@ impl AddressActivityVecs {
self.by_addresstype.truncate_push_height(height, counts)?;
Ok(())
}
}

View File

@@ -70,14 +70,62 @@ impl From<(&AddressTypeToAddrCountVecs, Height)> for AddressTypeToAddressCount {
fn from((groups, starting_height): (&AddressTypeToAddrCountVecs, Height)) -> Self {
if let Some(prev_height) = starting_height.decremented() {
Self(ByAddressType {
p2pk65: groups.p2pk65.count.height.collect_one(prev_height).unwrap().into(),
p2pk33: groups.p2pk33.count.height.collect_one(prev_height).unwrap().into(),
p2pkh: groups.p2pkh.count.height.collect_one(prev_height).unwrap().into(),
p2sh: groups.p2sh.count.height.collect_one(prev_height).unwrap().into(),
p2wpkh: groups.p2wpkh.count.height.collect_one(prev_height).unwrap().into(),
p2wsh: groups.p2wsh.count.height.collect_one(prev_height).unwrap().into(),
p2tr: groups.p2tr.count.height.collect_one(prev_height).unwrap().into(),
p2a: groups.p2a.count.height.collect_one(prev_height).unwrap().into(),
p2pk65: groups
.p2pk65
.count
.height
.collect_one(prev_height)
.unwrap()
.into(),
p2pk33: groups
.p2pk33
.count
.height
.collect_one(prev_height)
.unwrap()
.into(),
p2pkh: groups
.p2pkh
.count
.height
.collect_one(prev_height)
.unwrap()
.into(),
p2sh: groups
.p2sh
.count
.height
.collect_one(prev_height)
.unwrap()
.into(),
p2wpkh: groups
.p2wpkh
.count
.height
.collect_one(prev_height)
.unwrap()
.into(),
p2wsh: groups
.p2wsh
.count
.height
.collect_one(prev_height)
.unwrap()
.into(),
p2tr: groups
.p2tr
.count
.height
.collect_one(prev_height)
.unwrap()
.into(),
p2a: groups
.p2a
.count
.height
.collect_one(prev_height)
.unwrap()
.into(),
})
} else {
Default::default()
@@ -103,24 +151,23 @@ impl AddressTypeToAddrCountVecs {
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
Ok(Self::from(
ByAddressType::<AddrCountVecs>::new_with_name(|type_name| {
AddrCountVecs::forced_import(
db,
&format!("{type_name}_{name}"),
version,
indexes,
)
})?,
))
Ok(Self::from(ByAddressType::<AddrCountVecs>::new_with_name(
|type_name| {
AddrCountVecs::forced_import(db, &format!("{type_name}_{name}"), version, indexes)
},
)?))
}
pub(crate) fn min_stateful_height(&self) -> usize {
self.0.values().map(|v| v.count.height.len()).min().unwrap()
}
pub(crate) fn par_iter_height_mut(&mut self) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
self.0.par_values_mut().map(|v| &mut v.count.height as &mut dyn AnyStoredVec)
pub(crate) fn par_iter_height_mut(
&mut self,
) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
self.0
.par_values_mut()
.map(|v| &mut v.count.height as &mut dyn AnyStoredVec)
}
pub(crate) fn truncate_push_height(
@@ -180,10 +227,16 @@ impl AddrCountsVecs {
}
pub(crate) fn min_stateful_height(&self) -> usize {
self.all.count.height.len().min(self.by_addresstype.min_stateful_height())
self.all
.count
.height
.len()
.min(self.by_addresstype.min_stateful_height())
}
pub(crate) fn par_iter_height_mut(&mut self) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
pub(crate) fn par_iter_height_mut(
&mut self,
) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
rayon::iter::once(&mut self.all.count.height as &mut dyn AnyStoredVec)
.chain(self.by_addresstype.par_iter_height_mut())
}

View File

@@ -4,9 +4,7 @@ use brk_types::{
EmptyAddressData, EmptyAddressIndex, FundedAddressData, FundedAddressIndex, Height,
};
use rayon::prelude::*;
use vecdb::{
AnyStoredVec, BytesVec, Rw, Stamp, StorageMode, WritableVec,
};
use vecdb::{AnyStoredVec, BytesVec, Rw, Stamp, StorageMode, WritableVec};
/// Storage for both funded and empty address data.
#[derive(Traversable)]

View File

@@ -1,5 +1,3 @@
//! Growth rate: new_addr_count / addr_count (global + per-type)
use brk_cohort::ByAddressType;
use brk_error::Result;
use brk_traversable::Traversable;
@@ -27,12 +25,8 @@ impl GrowthRateVecs {
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
let all = PercentFromHeightDistribution::forced_import(
db,
"growth_rate",
version,
indexes,
)?;
let all =
PercentFromHeightDistribution::forced_import(db, "growth_rate", version, indexes)?;
let by_addresstype = ByAddressType::new_with_name(|name| {
PercentFromHeightDistribution::forced_import(
@@ -43,7 +37,10 @@ impl GrowthRateVecs {
)
})?;
Ok(Self { all, by_addresstype })
Ok(Self {
all,
by_addresstype,
})
}
pub(crate) fn compute(
@@ -64,24 +61,14 @@ impl GrowthRateVecs {
)
})?;
for ((_, growth), ((_, new), (_, addr))) in self
.by_addresstype
.iter_mut()
.zip(
new_addr_count
.by_addresstype
.iter()
.zip(addr_count.by_addresstype.iter()),
)
{
for ((_, growth), ((_, new), (_, addr))) in self.by_addresstype.iter_mut().zip(
new_addr_count
.by_addresstype
.iter()
.zip(addr_count.by_addresstype.iter()),
) {
growth.compute(max_from, windows, exit, |target| {
compute_ratio(
target,
max_from,
&new.height,
&addr.count.height,
exit,
)
compute_ratio(target, max_from, &new.height, &addr.count.height, exit)
})?;
}

View File

@@ -1,12 +1,13 @@
//! New address count: per-block delta of total_addr_count (global + per-type)
use brk_cohort::ByAddressType;
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Height, StoredU64, Version};
use vecdb::{Database, Exit, Rw, StorageMode};
use crate::{indexes, internal::{ComputedFromHeightFull, WindowStarts}};
use crate::{
indexes,
internal::{ComputedFromHeightFull, WindowStarts},
};
use super::TotalAddrCountVecs;
@@ -24,12 +25,7 @@ impl NewAddrCountVecs {
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
let all = ComputedFromHeightFull::forced_import(
db,
"new_addr_count",
version,
indexes,
)?;
let all = ComputedFromHeightFull::forced_import(db, "new_addr_count", version, indexes)?;
let by_addresstype: ByAddressType<ComputedFromHeightFull<StoredU64>> =
ByAddressType::new_with_name(|name| {

View File

@@ -1,5 +1,3 @@
//! Total address count: addr_count + empty_addr_count (global + per-type)
use brk_cohort::ByAddressType;
use brk_error::Result;
use brk_traversable::Traversable;
@@ -24,25 +22,22 @@ impl TotalAddrCountVecs {
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
let all = ComputedFromHeight::forced_import(
db,
"total_addr_count",
version,
indexes,
)?;
let all = ComputedFromHeight::forced_import(db, "total_addr_count", version, indexes)?;
let by_addresstype: ByAddressType<ComputedFromHeight<StoredU64>> = ByAddressType::new_with_name(
|name| {
let by_addresstype: ByAddressType<ComputedFromHeight<StoredU64>> =
ByAddressType::new_with_name(|name| {
ComputedFromHeight::forced_import(
db,
&format!("{name}_total_addr_count"),
version,
indexes,
)
},
)?;
})?;
Ok(Self { all, by_addresstype })
Ok(Self {
all,
by_addresstype,
})
}
/// Eagerly compute total = addr_count + empty_addr_count.
@@ -60,22 +55,15 @@ impl TotalAddrCountVecs {
exit,
)?;
for ((_, total), ((_, addr), (_, empty))) in self
.by_addresstype
.iter_mut()
.zip(
addr_count
.by_addresstype
.iter()
.zip(empty_addr_count.by_addresstype.iter()),
)
{
total.height.compute_add(
max_from,
&addr.count.height,
&empty.count.height,
exit,
)?;
for ((_, total), ((_, addr), (_, empty))) in self.by_addresstype.iter_mut().zip(
addr_count
.by_addresstype
.iter()
.zip(empty_addr_count.by_addresstype.iter()),
) {
total
.height
.compute_add(max_from, &addr.count.height, &empty.count.height, exit)?;
}
Ok(())

View File

@@ -60,7 +60,12 @@ impl<T> AddressTypeToTypeIndexMap<T> {
}
/// Insert a value for a specific address type and typeindex.
pub(crate) fn insert_for_type(&mut self, address_type: OutputType, typeindex: TypeIndex, value: T) {
pub(crate) fn insert_for_type(
&mut self,
address_type: OutputType,
typeindex: TypeIndex,
value: T,
) {
self.get_mut(address_type).unwrap().insert(typeindex, value);
}
@@ -76,7 +81,9 @@ impl<T> AddressTypeToTypeIndexMap<T> {
}
/// Iterate mutably over entries by address type.
pub(crate) fn iter_mut(&mut self) -> impl Iterator<Item = (OutputType, &mut FxHashMap<TypeIndex, T>)> {
pub(crate) fn iter_mut(
&mut self,
) -> impl Iterator<Item = (OutputType, &mut FxHashMap<TypeIndex, T>)> {
self.0.iter_mut()
}
}

View File

@@ -49,7 +49,10 @@ impl AddressCache {
/// Merge address data into funded cache.
#[inline]
pub(crate) fn merge_funded(&mut self, data: AddressTypeToTypeIndexMap<WithAddressDataSource<FundedAddressData>>) {
pub(crate) fn merge_funded(
&mut self,
data: AddressTypeToTypeIndexMap<WithAddressDataSource<FundedAddressData>>,
) {
self.funded.merge_mut(data);
}
@@ -63,7 +66,10 @@ impl AddressCache {
}
/// Update transaction counts for addresses.
pub(crate) fn update_tx_counts(&mut self, txindex_vecs: AddressTypeToTypeIndexMap<SmallVec<[TxIndex; 4]>>) {
pub(crate) fn update_tx_counts(
&mut self,
txindex_vecs: AddressTypeToTypeIndexMap<SmallVec<[TxIndex; 4]>>,
) {
update_tx_counts(&mut self.funded, &mut self.empty, txindex_vecs);
}
@@ -97,7 +103,9 @@ pub(crate) fn load_uncached_address_data(
// Check if this is a new address (typeindex >= first for this height)
let first = *first_addressindexes.get(address_type).unwrap();
if first <= typeindex {
return Ok(Some(WithAddressDataSource::New(FundedAddressData::default())));
return Ok(Some(WithAddressDataSource::New(
FundedAddressData::default(),
)));
}
// Skip if already in cache

View File

@@ -26,7 +26,10 @@ impl<'a> AddressLookup<'a> {
&mut self,
output_type: OutputType,
type_index: TypeIndex,
) -> (&mut WithAddressDataSource<FundedAddressData>, TrackingStatus) {
) -> (
&mut WithAddressDataSource<FundedAddressData>,
TrackingStatus,
) {
use std::collections::hash_map::Entry;
let map = self.funded.get_mut(output_type).unwrap();

View File

@@ -150,14 +150,7 @@ pub(crate) fn process_sent(
.state
.as_mut()
.unwrap()
.send(
addr_data,
value,
current_price,
prev_price,
peak_price,
age,
)?;
.send(addr_data, value, current_price, prev_price, peak_price, age)?;
}
}
}

View File

@@ -102,7 +102,9 @@ pub(crate) fn process_inputs(
);
let mut sent_data = HeightToAddressTypeToVec::with_capacity(estimated_unique_heights);
let mut address_data =
AddressTypeToTypeIndexMap::<WithAddressDataSource<FundedAddressData>>::with_capacity(estimated_per_type);
AddressTypeToTypeIndexMap::<WithAddressDataSource<FundedAddressData>>::with_capacity(
estimated_per_type,
);
let mut txindex_vecs =
AddressTypeToTypeIndexMap::<SmallVec<[TxIndex; 4]>>::with_capacity(estimated_per_type);

View File

@@ -52,7 +52,9 @@ pub(crate) fn process_outputs(
let mut transacted = Transacted::default();
let mut received_data = AddressTypeToVec::with_capacity(estimated_per_type);
let mut address_data =
AddressTypeToTypeIndexMap::<WithAddressDataSource<FundedAddressData>>::with_capacity(estimated_per_type);
AddressTypeToTypeIndexMap::<WithAddressDataSource<FundedAddressData>>::with_capacity(
estimated_per_type,
);
let mut txindex_vecs =
AddressTypeToTypeIndexMap::<SmallVec<[TxIndex; 4]>>::with_capacity(estimated_per_type);

View File

@@ -33,13 +33,11 @@ impl AddressCohorts {
let v = version + VERSION;
// Helper to create a cohort - only amount_range cohorts have state
let create = |filter: Filter,
name: &'static str,
has_state: bool|
-> Result<AddressCohortVecs> {
let sp = if has_state { Some(states_path) } else { None };
AddressCohortVecs::forced_import(db, filter, name, v, indexes, sp)
};
let create =
|filter: Filter, name: &'static str, has_state: bool| -> Result<AddressCohortVecs> {
let sp = if has_state { Some(states_path) } else { None };
AddressCohortVecs::forced_import(db, filter, name, v, indexes, sp)
};
let full = |f: Filter, name: &'static str| create(f, name, true);
let none = |f: Filter, name: &'static str| create(f, name, false);
@@ -156,7 +154,9 @@ impl AddressCohorts {
}
/// 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> {
pub(crate) fn par_iter_vecs_mut(
&mut self,
) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
// Collect all vecs from all cohorts
self.0
.iter_mut()

View File

@@ -5,21 +5,15 @@ use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Cents, Dollars, Height, Indexes, Sats, StoredF64, StoredU64, Version};
use rayon::prelude::*;
use vecdb::{AnyStoredVec, AnyVec, Database, Exit, WritableVec, ReadableVec, Rw, StorageMode};
use vecdb::{AnyStoredVec, AnyVec, Database, Exit, ReadableVec, Rw, StorageMode, WritableVec};
use crate::{
blocks,
distribution::state::AddressCohortState,
indexes,
internal::ComputedFromHeight,
prices,
blocks, distribution::state::AddressCohortState, indexes, internal::ComputedFromHeight, prices,
};
use crate::distribution::metrics::{BasicCohortMetrics, CohortMetricsBase, ImportConfig};
use super::super::traits::{CohortVecs, DynCohortVecs};
/// Address cohort with metrics and optional runtime state.
#[derive(Traversable)]
pub struct AddressCohortVecs<M: StorageMode = Rw> {
/// Starting height when state was imported
@@ -60,8 +54,7 @@ impl AddressCohortVecs {
Ok(Self {
starting_height: None,
state: states_path
.map(|path| Box::new(AddressCohortState::new(path, &full_name))),
state: states_path.map(|path| Box::new(AddressCohortState::new(path, &full_name))),
metrics: BasicCohortMetrics::forced_import(&cfg)?,
@@ -86,7 +79,9 @@ impl AddressCohortVecs {
}
/// 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> {
pub(crate) fn par_iter_vecs_mut(
&mut self,
) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
rayon::iter::once(&mut self.addr_count.height as &mut dyn AnyStoredVec)
.chain(self.metrics.par_iter_mut())
}

View File

@@ -1,8 +1,8 @@
use std::{cmp::Reverse, collections::BinaryHeap, fs, path::Path};
use brk_cohort::{
ByAgeRange, ByAmountRange, ByEpoch, ByGreatEqualAmount, ByLowerThanAmount,
ByMaxAge, ByMinAge, BySpendableType, ByYear, CohortContext, Filter, Filtered, TERM_NAMES, Term,
ByAgeRange, ByAmountRange, ByEpoch, ByGreatEqualAmount, ByLowerThanAmount, ByMaxAge, ByMinAge,
BySpendableType, ByYear, CohortContext, Filter, Filtered, TERM_NAMES, Term,
};
use brk_error::Result;
use brk_traversable::Traversable;
@@ -11,7 +11,9 @@ use brk_types::{
Sats, Version,
};
use rayon::prelude::*;
use vecdb::{AnyStoredVec, Database, Exit, ReadOnlyClone, ReadableVec, Rw, StorageMode, WritableVec};
use vecdb::{
AnyStoredVec, Database, Exit, ReadOnlyClone, ReadableVec, Rw, StorageMode, WritableVec,
};
use crate::{
blocks,
@@ -23,8 +25,7 @@ use crate::{
use crate::distribution::metrics::{
AdjustedCohortMetrics, AllCohortMetrics, BasicCohortMetrics, CohortMetricsBase,
ExtendedAdjustedCohortMetrics, ExtendedCohortMetrics, ImportConfig,
SupplyMetrics,
ExtendedAdjustedCohortMetrics, ExtendedCohortMetrics, ImportConfig, SupplyMetrics,
};
use super::vecs::UTXOCohortVecs;
@@ -146,12 +147,7 @@ impl UTXOCohorts<Rw> {
version: v,
indexes,
};
UTXOCohortVecs::new(
None,
ExtendedAdjustedCohortMetrics::forced_import(
&cfg,
)?,
)
UTXOCohortVecs::new(None, ExtendedAdjustedCohortMetrics::forced_import(&cfg)?)
};
// lth: ExtendedCohortMetrics
@@ -165,10 +161,7 @@ impl UTXOCohorts<Rw> {
version: v,
indexes,
};
UTXOCohortVecs::new(
None,
ExtendedCohortMetrics::forced_import(&cfg)?,
)
UTXOCohortVecs::new(None, ExtendedCohortMetrics::forced_import(&cfg)?)
};
// max_age: AdjustedCohortMetrics (adjusted + peak_regret)
@@ -243,9 +236,6 @@ impl UTXOCohorts<Rw> {
})
}
// === Iteration helpers ===
/// Parallel iterator over all separate (stateful) cohorts.
pub(crate) fn par_iter_separate_mut(
&mut self,
) -> impl ParallelIterator<Item = &mut dyn DynCohortVecs> {
@@ -296,9 +286,6 @@ impl UTXOCohorts<Rw> {
v.into_iter()
}
// === Computation methods ===
/// Compute overlapping cohorts from component age/amount range cohorts.
pub(crate) fn compute_overlapping_vecs(
&mut self,
starting_indexes: &Indexes,
@@ -573,8 +560,22 @@ impl UTXOCohorts<Rw> {
HM: ReadableVec<Height, Dollars> + Sync,
{
// Get up_to_1h value sources for adjusted computation (cloned to avoid borrow conflicts).
let up_to_1h_value_created = self.age_range.up_to_1h.metrics.realized.value_created.height.read_only_clone();
let up_to_1h_value_destroyed = self.age_range.up_to_1h.metrics.realized.value_destroyed.height.read_only_clone();
let up_to_1h_value_created = self
.age_range
.up_to_1h
.metrics
.realized
.value_created
.height
.read_only_clone();
let up_to_1h_value_destroyed = self
.age_range
.up_to_1h
.metrics
.realized
.value_destroyed
.height
.read_only_clone();
// "all" cohort computed first (no all_supply_sats needed).
self.all.metrics.compute_rest_part2(
@@ -1024,5 +1025,4 @@ impl UTXOCohorts<Rw> {
Ok(())
}
}

View File

@@ -24,19 +24,32 @@ impl UTXOCohorts<Rw> {
let supply_state = received.spendable_supply;
// New UTXOs go into up_to_1h, current epoch, and current year
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);
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_
.iter_typed_mut()
.for_each(|(output_type, vecs)| {
vecs.state
.as_mut()
.unwrap()
.receive_utxo(received.by_type.get(output_type), price)
});
self.type_.iter_typed_mut().for_each(|(output_type, vecs)| {
vecs.state
.as_mut()
.unwrap()
.receive_utxo(received.by_type.get(output_type), price)
});
// Update amount range cohorts
received

View File

@@ -36,9 +36,8 @@ impl UTXOCohorts<Rw> {
let mut min_receive_height: Option<Height> = None;
for (receive_height, sent) in height_to_sent {
min_receive_height = Some(
min_receive_height.map_or(receive_height, |cur| cur.min(receive_height)),
);
min_receive_height =
Some(min_receive_height.map_or(receive_height, |cur| cur.min(receive_height)));
// Update chain_state to reflect spent supply
chain_state[receive_height.to_usize()].supply -= &sent.spendable_supply;
@@ -52,19 +51,25 @@ impl UTXOCohorts<Rw> {
let peak_price = price_range_max.max_between(receive_height, send_height);
// Update age range cohort (direct index lookup)
self.age_range.get_mut(age).state.as_mut().unwrap().send_utxo(
&sent.spendable_supply,
current_price,
prev_price,
peak_price,
age,
);
self.age_range
.get_mut(age)
.state
.as_mut()
.unwrap()
.send_utxo(
&sent.spendable_supply,
current_price,
prev_price,
peak_price,
age,
);
// Update epoch cohort (direct lookup by height)
self.epoch
.mut_vec_from_height(receive_height)
.state
.as_mut().unwrap()
.as_mut()
.unwrap()
.send_utxo(
&sent.spendable_supply,
current_price,
@@ -77,7 +82,8 @@ impl UTXOCohorts<Rw> {
self.year
.mut_vec_from_timestamp(block_state.timestamp)
.state
.as_mut().unwrap()
.as_mut()
.unwrap()
.send_utxo(
&sent.spendable_supply,
current_price,
@@ -91,26 +97,24 @@ impl UTXOCohorts<Rw> {
.spendable
.iter_typed()
.for_each(|(output_type, supply_state)| {
self.type_.get_mut(output_type).state.as_mut().unwrap().send_utxo(
supply_state,
current_price,
prev_price,
peak_price,
age,
)
self.type_
.get_mut(output_type)
.state
.as_mut()
.unwrap()
.send_utxo(supply_state, current_price, prev_price, peak_price, age)
});
// Update amount range cohorts
sent.by_size_group
.iter_typed()
.for_each(|(group, supply_state)| {
self.amount_range.get_mut(group).state.as_mut().unwrap().send_utxo(
supply_state,
current_price,
prev_price,
peak_price,
age,
);
self.amount_range
.get_mut(group)
.state
.as_mut()
.unwrap()
.send_utxo(supply_state, current_price, prev_price, peak_price, age);
});
}

View File

@@ -16,7 +16,11 @@ impl UTXOCohorts<Rw> {
/// - k = 20 boundaries to check
/// - n = total blocks in chain_state
/// - Linear scan for end_idx is faster than binary search since typically 0-2 blocks cross each boundary
pub(crate) fn tick_tock_next_block(&mut self, chain_state: &[BlockState], timestamp: Timestamp) {
pub(crate) fn tick_tock_next_block(
&mut self,
chain_state: &[BlockState],
timestamp: Timestamp,
) {
if chain_state.is_empty() {
return;
}

View File

@@ -10,10 +10,6 @@ use crate::distribution::metrics::CohortMetricsBase;
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<Metrics> {
/// Starting height when state was imported
@@ -38,7 +34,6 @@ impl<Metrics> UTXOCohortVecs<Metrics> {
metrics,
}
}
}
impl<Metrics: CohortMetricsBase + Traversable> Filtered for UTXOCohortVecs<Metrics> {
@@ -117,8 +112,11 @@ impl<Metrics: CohortMetricsBase + Traversable> 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(())
}

View File

@@ -3,7 +3,9 @@ use std::thread;
use brk_cohort::ByAddressType;
use brk_error::Result;
use brk_indexer::Indexer;
use brk_types::{Cents, Date, Height, OutputType, Sats, Timestamp, TxIndex, TypeIndex, ONE_DAY_IN_SEC};
use brk_types::{
Cents, Date, Height, ONE_DAY_IN_SEC, OutputType, Sats, Timestamp, TxIndex, TypeIndex,
};
use rayon::prelude::*;
use rustc_hash::FxHashSet;
use tracing::{debug, info};
@@ -401,12 +403,11 @@ pub(crate) fn process_blocks(
// Main thread: Update UTXO cohorts
vecs.utxo_cohorts
.receive(transacted, height, timestamp, block_price);
if let Some(min_h) = vecs.utxo_cohorts
.send(height_to_sent, chain_state, ctx.price_range_max)
if let Some(min_h) =
vecs.utxo_cohorts
.send(height_to_sent, chain_state, ctx.price_range_max)
{
min_supply_modified = Some(
min_supply_modified.map_or(min_h, |cur| cur.min(min_h)),
);
min_supply_modified = Some(min_supply_modified.map_or(min_h, |cur| cur.min(min_h)));
}
});
@@ -423,8 +424,7 @@ pub(crate) fn process_blocks(
let h = height.to_usize();
let is_last_of_day = height == last_height
|| *cached_timestamps[h] / ONE_DAY_IN_SEC
!= *cached_timestamps[h + 1] / ONE_DAY_IN_SEC;
|| *cached_timestamps[h] / ONE_DAY_IN_SEC != *cached_timestamps[h + 1] / ONE_DAY_IN_SEC;
let date_opt = is_last_of_day.then(|| Date::from(timestamp));
push_cohort_states(

View File

@@ -1,9 +1,7 @@
use brk_cohort::{ByAddressType, ByAnyAddress};
use brk_indexer::Indexer;
use brk_types::{
Height, OutPoint, OutputType, Sats, StoredU64, TxIndex, TypeIndex,
};
use vecdb::{Reader, ReadableVec, VecIndex};
use brk_types::{Height, OutPoint, OutputType, Sats, StoredU64, TxIndex, TypeIndex};
use vecdb::{ReadableVec, Reader, VecIndex};
use crate::{
distribution::{
@@ -46,9 +44,21 @@ impl<'a> TxOutReaders<'a> {
output_count: usize,
) -> Vec<TxOutData> {
let end = first_txoutindex + output_count;
self.indexer.vecs.outputs.value.collect_range_into_at(first_txoutindex, end, &mut self.values_buf);
self.indexer.vecs.outputs.outputtype.collect_range_into_at(first_txoutindex, end, &mut self.outputtypes_buf);
self.indexer.vecs.outputs.typeindex.collect_range_into_at(first_txoutindex, end, &mut self.typeindexes_buf);
self.indexer.vecs.outputs.value.collect_range_into_at(
first_txoutindex,
end,
&mut self.values_buf,
);
self.indexer.vecs.outputs.outputtype.collect_range_into_at(
first_txoutindex,
end,
&mut self.outputtypes_buf,
);
self.indexer.vecs.outputs.typeindex.collect_range_into_at(
first_txoutindex,
end,
&mut self.typeindexes_buf,
);
self.values_buf
.iter()
@@ -94,12 +104,31 @@ impl<'a> TxInReaders<'a> {
current_height: Height,
) -> (Vec<Sats>, Vec<Height>, Vec<OutputType>, Vec<TypeIndex>) {
let end = first_txinindex + input_count;
let values: Vec<Sats> = self.txins.spent.value.collect_range_at(first_txinindex, end);
self.indexer.vecs.inputs.outpoint.collect_range_into_at(first_txinindex, end, &mut self.outpoints_buf);
let outputtypes: Vec<OutputType> = self.indexer.vecs.inputs.outputtype.collect_range_at(first_txinindex, end);
let typeindexes: Vec<TypeIndex> = self.indexer.vecs.inputs.typeindex.collect_range_at(first_txinindex, end);
let values: Vec<Sats> = self
.txins
.spent
.value
.collect_range_at(first_txinindex, end);
self.indexer.vecs.inputs.outpoint.collect_range_into_at(
first_txinindex,
end,
&mut self.outpoints_buf,
);
let outputtypes: Vec<OutputType> = self
.indexer
.vecs
.inputs
.outputtype
.collect_range_at(first_txinindex, end);
let typeindexes: Vec<TypeIndex> = self
.indexer
.vecs
.inputs
.typeindex
.collect_range_at(first_txinindex, end);
let prev_heights: Vec<Height> = self.outpoints_buf
let prev_heights: Vec<Height> = self
.outpoints_buf
.iter()
.map(|outpoint| {
if outpoint.is_coinbase() {
@@ -175,7 +204,11 @@ impl IndexToTxIndexBuf {
txindex_to_count: &impl ReadableVec<TxIndex, StoredU64>,
) -> &[TxIndex] {
let first = block_first_txindex.to_usize();
txindex_to_count.collect_range_into_at(first, first + block_tx_count as usize, &mut self.counts);
txindex_to_count.collect_range_into_at(
first,
first + block_tx_count as usize,
&mut self.counts,
);
let total: u64 = self.counts.iter().map(|c| u64::from(*c)).sum();
self.result.clear();
@@ -183,7 +216,8 @@ impl IndexToTxIndexBuf {
for (offset, count) in self.counts.iter().enumerate() {
let txindex = TxIndex::from(first + offset);
self.result.extend(std::iter::repeat_n(txindex, u64::from(*count) as usize));
self.result
.extend(std::iter::repeat_n(txindex, u64::from(*count) as usize));
}
&self.result

View File

@@ -71,9 +71,15 @@ pub(crate) fn recover_state(
}
// Import UTXO cohort states - all must succeed
debug!("importing UTXO cohort states at height {}", consistent_height);
debug!(
"importing UTXO cohort states at height {}",
consistent_height
);
if !utxo_cohorts.import_separate_states(consistent_height) {
warn!("UTXO cohort state import failed at height {}", consistent_height);
warn!(
"UTXO cohort state import failed at height {}",
consistent_height
);
return Ok(RecoveredState {
starting_height: Height::ZERO,
});
@@ -81,9 +87,15 @@ pub(crate) fn recover_state(
debug!("UTXO cohort states imported");
// Import address cohort states - all must succeed
debug!("importing address cohort states at height {}", consistent_height);
debug!(
"importing address cohort states at height {}",
consistent_height
);
if !address_cohorts.import_separate_states(consistent_height) {
warn!("Address cohort state import failed at height {}", consistent_height);
warn!(
"Address cohort state import failed at height {}",
consistent_height
);
return Ok(RecoveredState {
starting_height: Height::ZERO,
});
@@ -163,16 +175,25 @@ fn rollback_states(
return Height::ZERO;
};
let chain_height = Height::from(s).incremented();
debug!("chain_state rolled back to stamp {:?}, height {}", s, chain_height);
debug!(
"chain_state rolled back to stamp {:?}, height {}",
s, chain_height
);
heights.insert(chain_height);
let Ok(stamps) = address_indexes_rollbacks else {
warn!("address_indexes rollback failed: {:?}", address_indexes_rollbacks);
warn!(
"address_indexes rollback failed: {:?}",
address_indexes_rollbacks
);
return Height::ZERO;
};
for (i, s) in stamps.iter().enumerate() {
let h = Height::from(*s).incremented();
debug!("address_indexes[{}] rolled back to stamp {:?}, height {}", i, s, h);
debug!(
"address_indexes[{}] rolled back to stamp {:?}, height {}",
i, s, h
);
heights.insert(h);
}
@@ -182,7 +203,10 @@ fn rollback_states(
};
for (i, s) in stamps.iter().enumerate() {
let h = Height::from(*s).incremented();
debug!("address_data[{}] rolled back to stamp {:?}, height {}", i, s, h);
debug!(
"address_data[{}] rolled back to stamp {:?}, height {}",
i, s, h
);
heights.insert(h);
}

View File

@@ -4,7 +4,7 @@ use brk_error::Result;
use brk_types::{EmptyAddressData, FundedAddressData, Height};
use rayon::prelude::*;
use tracing::info;
use vecdb::{AnyStoredVec, AnyVec, VecIndex, WritableVec, Stamp};
use vecdb::{AnyStoredVec, AnyVec, Stamp, VecIndex, WritableVec};
use crate::distribution::{
Vecs,
@@ -65,8 +65,8 @@ pub(crate) fn write(
// Incremental supply_state write: only rewrite from the earliest modified height
let supply_state_len = vecs.supply_state.len();
let truncate_to = min_supply_modified
.map_or(supply_state_len, |h| h.to_usize().min(supply_state_len));
let truncate_to =
min_supply_modified.map_or(supply_state_len, |h| h.to_usize().min(supply_state_len));
vecs.supply_state
.truncate_if_needed(Height::from(truncate_to))?;
for block_state in &chain_state[truncate_to..] {

View File

@@ -2,7 +2,9 @@ use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Bitcoin, Height, Indexes, Sats, StoredF64, Version};
use rayon::prelude::*;
use vecdb::{AnyStoredVec, AnyVec, EagerVec, Exit, ImportableVec, PcoVec, Rw, StorageMode, WritableVec};
use vecdb::{
AnyStoredVec, AnyVec, EagerVec, Exit, ImportableVec, PcoVec, Rw, StorageMode, WritableVec,
};
use crate::{
blocks,
@@ -51,7 +53,8 @@ impl ActivityMetrics {
cfg.version,
)?,
coinblocks_destroyed: cfg.import_cumulative_sum("coinblocks_destroyed", Version::ZERO)?,
coinblocks_destroyed: cfg
.import_cumulative_sum("coinblocks_destroyed", Version::ZERO)?,
coindays_destroyed: cfg.import_cumulative_sum("coindays_destroyed", Version::ZERO)?,
})
}
@@ -151,25 +154,27 @@ impl ActivityMetrics {
exit,
)?;
self.coinblocks_destroyed.compute(starting_indexes.height, &window_starts, exit, |v| {
v.compute_transform(
starting_indexes.height,
&self.satblocks_destroyed,
|(i, v, ..)| (i, StoredF64::from(Bitcoin::from(v))),
exit,
)?;
Ok(())
})?;
self.coinblocks_destroyed
.compute(starting_indexes.height, &window_starts, exit, |v| {
v.compute_transform(
starting_indexes.height,
&self.satblocks_destroyed,
|(i, v, ..)| (i, StoredF64::from(Bitcoin::from(v))),
exit,
)?;
Ok(())
})?;
self.coindays_destroyed.compute(starting_indexes.height, &window_starts, exit, |v| {
v.compute_transform(
starting_indexes.height,
&self.satdays_destroyed,
|(i, v, ..)| (i, StoredF64::from(Bitcoin::from(v))),
exit,
)?;
Ok(())
})?;
self.coindays_destroyed
.compute(starting_indexes.height, &window_starts, exit, |v| {
v.compute_transform(
starting_indexes.height,
&self.satdays_destroyed,
|(i, v, ..)| (i, StoredF64::from(Bitcoin::from(v))),
exit,
)?;
Ok(())
})?;
Ok(())
}

View File

@@ -28,19 +28,45 @@ pub struct AdjustedCohortMetrics<M: StorageMode = Rw> {
}
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 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)?;
@@ -48,7 +74,10 @@ impl CohortMetricsBase for AdjustedCohortMetrics {
Ok(())
}
fn compute_then_truncate_push_unrealized_states(
&mut self, height: Height, height_price: Cents, state: &mut CohortState,
&mut self,
height: Height,
height_price: Cents,
state: &mut CohortState,
) -> Result<()> {
state.apply_pending();
self.cost_basis.truncate_push_minmax(height, state)?;
@@ -69,9 +98,7 @@ impl CohortMetricsBase for AdjustedCohortMetrics {
}
impl AdjustedCohortMetrics {
pub(crate) fn forced_import(
cfg: &ImportConfig,
) -> Result<Self> {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
let supply = SupplyMetrics::forced_import(cfg)?;
let unrealized = UnrealizedBase::forced_import(cfg)?;
let realized = RealizedWithAdjusted::forced_import(cfg)?;
@@ -125,5 +152,4 @@ impl AdjustedCohortMetrics {
Ok(())
}
}

View File

@@ -84,8 +84,7 @@ impl CohortMetricsBase for AllCohortMetrics {
state.apply_pending();
self.cost_basis.truncate_push_minmax(height, state)?;
let unrealized_state = state.compute_unrealized_state(height_price);
self.unrealized
.truncate_push(height, &unrealized_state)?;
self.unrealized.truncate_push(height, &unrealized_state)?;
self.cost_basis
.extended
.truncate_push_percentiles(height, state, height_price)?;

View File

@@ -8,8 +8,8 @@ use vecdb::{AnyStoredVec, Exit, ReadableVec, Rw, StorageMode};
use crate::{blocks, distribution::state::CohortState, prices};
use crate::distribution::metrics::{
ActivityMetrics, CohortMetricsBase, CostBasisBase, ImportConfig, OutputsMetrics,
RealizedBase, RelativeWithRelToAll, SupplyMetrics, UnrealizedBase,
ActivityMetrics, CohortMetricsBase, CostBasisBase, ImportConfig, OutputsMetrics, RealizedBase,
RelativeWithRelToAll, SupplyMetrics, UnrealizedBase,
};
/// Basic cohort metrics: no extensions, with relative (rel_to_all).
@@ -28,26 +28,55 @@ pub struct BasicCohortMetrics<M: StorageMode = Rw> {
}
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 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,
&mut self,
height: Height,
height_price: Cents,
state: &mut CohortState,
) -> Result<()> {
state.apply_pending();
self.cost_basis.truncate_push_minmax(height, state)?;
@@ -68,9 +97,7 @@ impl CohortMetricsBase for BasicCohortMetrics {
}
impl BasicCohortMetrics {
pub(crate) fn forced_import(
cfg: &ImportConfig,
) -> Result<Self> {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
let supply = SupplyMetrics::forced_import(cfg)?;
let unrealized = UnrealizedBase::forced_import(cfg)?;
let realized = RealizedBase::forced_import(cfg)?;

View File

@@ -29,19 +29,45 @@ pub struct ExtendedCohortMetrics<M: StorageMode = Rw> {
}
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 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)?;
@@ -50,13 +76,18 @@ impl CohortMetricsBase for ExtendedCohortMetrics {
Ok(())
}
fn compute_then_truncate_push_unrealized_states(
&mut self, height: Height, height_price: Cents, state: &mut CohortState,
&mut self,
height: Height,
height_price: Cents,
state: &mut CohortState,
) -> Result<()> {
state.apply_pending();
self.cost_basis.truncate_push_minmax(height, state)?;
let unrealized_state = state.compute_unrealized_state(height_price);
self.unrealized.truncate_push(height, &unrealized_state)?;
self.cost_basis.extended.truncate_push_percentiles(height, state, height_price)?;
self.cost_basis
.extended
.truncate_push_percentiles(height, state, height_price)?;
Ok(())
}
fn collect_all_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
@@ -73,9 +104,7 @@ impl CohortMetricsBase for ExtendedCohortMetrics {
}
impl ExtendedCohortMetrics {
pub(crate) fn forced_import(
cfg: &ImportConfig,
) -> Result<Self> {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
let supply = SupplyMetrics::forced_import(cfg)?;
let unrealized = UnrealizedBase::forced_import(cfg)?;
let realized = RealizedWithExtended::forced_import(cfg)?;
@@ -125,5 +154,4 @@ impl ExtendedCohortMetrics {
Ok(())
}
}

View File

@@ -29,19 +29,45 @@ pub struct ExtendedAdjustedCohortMetrics<M: StorageMode = Rw> {
}
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 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)?;
@@ -49,13 +75,18 @@ impl CohortMetricsBase for ExtendedAdjustedCohortMetrics {
Ok(())
}
fn compute_then_truncate_push_unrealized_states(
&mut self, height: Height, height_price: Cents, state: &mut CohortState,
&mut self,
height: Height,
height_price: Cents,
state: &mut CohortState,
) -> Result<()> {
state.apply_pending();
self.cost_basis.truncate_push_minmax(height, state)?;
let unrealized_state = state.compute_unrealized_state(height_price);
self.unrealized.truncate_push(height, &unrealized_state)?;
self.cost_basis.extended.truncate_push_percentiles(height, state, height_price)?;
self.cost_basis
.extended
.truncate_push_percentiles(height, state, height_price)?;
Ok(())
}
fn collect_all_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
@@ -72,9 +103,7 @@ impl CohortMetricsBase for ExtendedAdjustedCohortMetrics {
}
impl ExtendedAdjustedCohortMetrics {
pub(crate) fn forced_import(
cfg: &ImportConfig,
) -> Result<Self> {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
let supply = SupplyMetrics::forced_import(cfg)?;
let unrealized = UnrealizedBase::forced_import(cfg)?;
let realized = RealizedWithExtendedAdjusted::forced_import(cfg)?;
@@ -129,5 +158,4 @@ impl ExtendedAdjustedCohortMetrics {
Ok(())
}
}

View File

@@ -7,14 +7,14 @@ use vecdb::{BytesVec, BytesVecValue, Database, ImportableVec};
use crate::{
indexes,
internal::{
CentsType, ComputedFromHeight, ComputedFromHeightCumulative, ComputedFromHeightCumulativeSum,
ComputedFromHeightRatio, FiatFromHeight, NumericValue, PercentFromHeight,
PercentRollingEmas1w1m, PercentRollingWindows, Price, RollingEmas1w1m, RollingEmas2w,
RollingWindows, ValueFromHeight, ValueFromHeightChange, ValueFromHeightCumulative,
CentsType, ComputedFromHeight, ComputedFromHeightCumulative,
ComputedFromHeightCumulativeSum, ComputedFromHeightRatio, FiatFromHeight, NumericValue,
PercentFromHeight, PercentRollingEmas1w1m, PercentRollingWindows, Price, RollingEmas1w1m,
RollingEmas2w, RollingWindows, ValueFromHeight, ValueFromHeightChange,
ValueFromHeightCumulative,
},
};
/// Configuration for importing metrics.
#[derive(Clone, Copy)]
pub struct ImportConfig<'a> {
pub db: &'a Database,
@@ -25,7 +25,6 @@ pub struct ImportConfig<'a> {
}
impl<'a> ImportConfig<'a> {
/// Get full metric name with filter prefix.
pub(crate) fn name(&self, suffix: &str) -> String {
if self.full_name.is_empty() {
suffix.to_string()
@@ -36,14 +35,17 @@ impl<'a> ImportConfig<'a> {
}
}
// --- Computed types ---
pub(crate) fn import_computed<T: NumericValue + JsonSchema>(
&self,
suffix: &str,
offset: Version,
) -> Result<ComputedFromHeight<T>> {
ComputedFromHeight::forced_import(self.db, &self.name(suffix), self.version + offset, self.indexes)
ComputedFromHeight::forced_import(
self.db,
&self.name(suffix),
self.version + offset,
self.indexes,
)
}
pub(crate) fn import_cumulative<T: NumericValue + JsonSchema>(
@@ -51,7 +53,12 @@ impl<'a> ImportConfig<'a> {
suffix: &str,
offset: Version,
) -> Result<ComputedFromHeightCumulative<T>> {
ComputedFromHeightCumulative::forced_import(self.db, &self.name(suffix), self.version + offset, self.indexes)
ComputedFromHeightCumulative::forced_import(
self.db,
&self.name(suffix),
self.version + offset,
self.indexes,
)
}
pub(crate) fn import_cumulative_sum<T: NumericValue + JsonSchema>(
@@ -59,17 +66,25 @@ impl<'a> ImportConfig<'a> {
suffix: &str,
offset: Version,
) -> Result<ComputedFromHeightCumulativeSum<T>> {
ComputedFromHeightCumulativeSum::forced_import(self.db, &self.name(suffix), self.version + offset, self.indexes)
ComputedFromHeightCumulativeSum::forced_import(
self.db,
&self.name(suffix),
self.version + offset,
self.indexes,
)
}
// --- Percent types ---
pub(crate) fn import_percent_bp16(
&self,
suffix: &str,
offset: Version,
) -> Result<PercentFromHeight<BasisPoints16>> {
PercentFromHeight::forced_import(self.db, &self.name(suffix), self.version + offset, self.indexes)
PercentFromHeight::forced_import(
self.db,
&self.name(suffix),
self.version + offset,
self.indexes,
)
}
pub(crate) fn import_percent_bps16(
@@ -77,62 +92,158 @@ impl<'a> ImportConfig<'a> {
suffix: &str,
offset: Version,
) -> Result<PercentFromHeight<BasisPointsSigned16>> {
PercentFromHeight::forced_import(self.db, &self.name(suffix), self.version + offset, self.indexes)
PercentFromHeight::forced_import(
self.db,
&self.name(suffix),
self.version + offset,
self.indexes,
)
}
// --- Value types ---
pub(crate) fn import_fiat<C: CentsType>(&self, suffix: &str, offset: Version) -> Result<FiatFromHeight<C>> {
FiatFromHeight::forced_import(self.db, &self.name(suffix), self.version + offset, self.indexes)
pub(crate) fn import_fiat<C: CentsType>(
&self,
suffix: &str,
offset: Version,
) -> Result<FiatFromHeight<C>> {
FiatFromHeight::forced_import(
self.db,
&self.name(suffix),
self.version + offset,
self.indexes,
)
}
pub(crate) fn import_value(&self, suffix: &str, offset: Version) -> Result<ValueFromHeight> {
ValueFromHeight::forced_import(self.db, &self.name(suffix), self.version + offset, self.indexes)
ValueFromHeight::forced_import(
self.db,
&self.name(suffix),
self.version + offset,
self.indexes,
)
}
pub(crate) fn import_value_cumulative(&self, suffix: &str, offset: Version) -> Result<ValueFromHeightCumulative> {
ValueFromHeightCumulative::forced_import(self.db, &self.name(suffix), self.version + offset, self.indexes)
pub(crate) fn import_value_cumulative(
&self,
suffix: &str,
offset: Version,
) -> Result<ValueFromHeightCumulative> {
ValueFromHeightCumulative::forced_import(
self.db,
&self.name(suffix),
self.version + offset,
self.indexes,
)
}
pub(crate) fn import_value_change(&self, suffix: &str, offset: Version) -> Result<ValueFromHeightChange> {
ValueFromHeightChange::forced_import(self.db, &self.name(suffix), self.version + offset, self.indexes)
pub(crate) fn import_value_change(
&self,
suffix: &str,
offset: Version,
) -> Result<ValueFromHeightChange> {
ValueFromHeightChange::forced_import(
self.db,
&self.name(suffix),
self.version + offset,
self.indexes,
)
}
// --- Price and ratio ---
pub(crate) fn import_price(&self, suffix: &str, offset: Version) -> Result<Price<ComputedFromHeight<Cents>>> {
Price::forced_import(self.db, &self.name(suffix), self.version + offset, self.indexes)
pub(crate) fn import_price(
&self,
suffix: &str,
offset: Version,
) -> Result<Price<ComputedFromHeight<Cents>>> {
Price::forced_import(
self.db,
&self.name(suffix),
self.version + offset,
self.indexes,
)
}
pub(crate) fn import_ratio(&self, suffix: &str, offset: Version) -> Result<ComputedFromHeightRatio> {
ComputedFromHeightRatio::forced_import(self.db, &self.name(suffix), self.version + offset, self.indexes)
pub(crate) fn import_ratio(
&self,
suffix: &str,
offset: Version,
) -> Result<ComputedFromHeightRatio> {
ComputedFromHeightRatio::forced_import(
self.db,
&self.name(suffix),
self.version + offset,
self.indexes,
)
}
// --- Bytes ---
pub(crate) fn import_bytes<T: BytesVecValue>(&self, suffix: &str, offset: Version) -> Result<BytesVec<Height, T>> {
Ok(BytesVec::forced_import(self.db, &self.name(suffix), self.version + offset)?)
pub(crate) fn import_bytes<T: BytesVecValue>(
&self,
suffix: &str,
offset: Version,
) -> Result<BytesVec<Height, T>> {
Ok(BytesVec::forced_import(
self.db,
&self.name(suffix),
self.version + offset,
)?)
}
// --- Rolling ---
pub(crate) fn import_rolling<T: NumericValue + JsonSchema>(&self, suffix: &str, offset: Version) -> Result<RollingWindows<T>> {
RollingWindows::forced_import(self.db, &self.name(suffix), self.version + offset, self.indexes)
pub(crate) fn import_rolling<T: NumericValue + JsonSchema>(
&self,
suffix: &str,
offset: Version,
) -> Result<RollingWindows<T>> {
RollingWindows::forced_import(
self.db,
&self.name(suffix),
self.version + offset,
self.indexes,
)
}
pub(crate) fn import_percent_rolling_bp16(&self, suffix: &str, offset: Version) -> Result<PercentRollingWindows<BasisPoints16>> {
PercentRollingWindows::forced_import(self.db, &self.name(suffix), self.version + offset, self.indexes)
pub(crate) fn import_percent_rolling_bp16(
&self,
suffix: &str,
offset: Version,
) -> Result<PercentRollingWindows<BasisPoints16>> {
PercentRollingWindows::forced_import(
self.db,
&self.name(suffix),
self.version + offset,
self.indexes,
)
}
pub(crate) fn import_emas_1w_1m<T: NumericValue + JsonSchema>(&self, suffix: &str, offset: Version) -> Result<RollingEmas1w1m<T>> {
RollingEmas1w1m::forced_import(self.db, &self.name(suffix), self.version + offset, self.indexes)
pub(crate) fn import_emas_1w_1m<T: NumericValue + JsonSchema>(
&self,
suffix: &str,
offset: Version,
) -> Result<RollingEmas1w1m<T>> {
RollingEmas1w1m::forced_import(
self.db,
&self.name(suffix),
self.version + offset,
self.indexes,
)
}
pub(crate) fn import_percent_emas_1w_1m_bp16(&self, suffix: &str, offset: Version) -> Result<PercentRollingEmas1w1m<BasisPoints16>> {
PercentRollingEmas1w1m::forced_import(self.db, &self.name(suffix), self.version + offset, self.indexes)
pub(crate) fn import_percent_emas_1w_1m_bp16(
&self,
suffix: &str,
offset: Version,
) -> Result<PercentRollingEmas1w1m<BasisPoints16>> {
PercentRollingEmas1w1m::forced_import(
self.db,
&self.name(suffix),
self.version + offset,
self.indexes,
)
}
pub(crate) fn import_emas_2w(&self, suffix: &str, offset: Version) -> Result<RollingEmas2w> {
RollingEmas2w::forced_import(self.db, &self.name(suffix), self.version + offset, self.indexes)
RollingEmas2w::forced_import(
self.db,
&self.name(suffix),
self.version + offset,
self.indexes,
)
}
}

View File

@@ -69,12 +69,18 @@ impl CostBasisBase {
) -> Result<()> {
self.min.cents.height.compute_min_of_others(
starting_indexes.height,
&others.iter().map(|v| &v.min.cents.height).collect::<Vec<_>>(),
&others
.iter()
.map(|v| &v.min.cents.height)
.collect::<Vec<_>>(),
exit,
)?;
self.max.cents.height.compute_max_of_others(
starting_indexes.height,
&others.iter().map(|v| &v.max.cents.height).collect::<Vec<_>>(),
&others
.iter()
.map(|v| &v.max.cents.height)
.collect::<Vec<_>>(),
exit,
)?;
Ok(())

View File

@@ -5,10 +5,7 @@ use vecdb::{AnyStoredVec, Rw, StorageMode, WritableVec};
use crate::{
distribution::state::CohortState,
internal::{
PERCENTILES_LEN, PercentFromHeight, PercentilesVecs,
compute_spot_percentile_rank,
},
internal::{PERCENTILES_LEN, PercentFromHeight, PercentilesVecs, compute_spot_percentile_rank},
};
use crate::distribution::metrics::ImportConfig;
@@ -44,8 +41,10 @@ impl CostBasisExtended {
cfg.version,
cfg.indexes,
)?,
spot_cost_basis_percentile: cfg.import_percent_bp16("spot_cost_basis_percentile", Version::ZERO)?,
spot_invested_capital_percentile: cfg.import_percent_bp16("spot_invested_capital_percentile", Version::ZERO)?,
spot_cost_basis_percentile: cfg
.import_percent_bp16("spot_cost_basis_percentile", Version::ZERO)?,
spot_invested_capital_percentile: cfg
.import_percent_bp16("spot_invested_capital_percentile", Version::ZERO)?,
})
}

View File

@@ -25,11 +25,6 @@ use vecdb::{AnyStoredVec, Exit};
use crate::{blocks, distribution::state::CohortState, prices};
/// 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;
@@ -45,14 +40,8 @@ pub trait CohortMetricsBase: Send + Sync {
fn cost_basis_base(&self) -> &CostBasisBase;
fn cost_basis_base_mut(&mut self) -> &mut CostBasisBase;
// === Required methods (vary by extension level) ===
/// Validate computed versions against base version.
/// Extended types also validate cost_basis extended versions.
fn validate_computed_versions(&mut self, base_version: Version) -> Result<()>;
/// Compute and push unrealized states.
/// Extended types also push cost_basis percentiles.
fn compute_then_truncate_push_unrealized_states(
&mut self,
height: Height,
@@ -60,12 +49,8 @@ pub trait CohortMetricsBase: Send + Sync {
state: &mut CohortState,
) -> Result<()>;
/// Collect all stored vecs for parallel writing.
fn collect_all_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec>;
// === Default methods (shared across all cohort metric types, use base fields only) ===
/// Get minimum length across height-indexed vectors written in block loop.
fn min_stateful_height_len(&self) -> usize {
self.supply()
.min_len()
@@ -76,7 +61,6 @@ pub trait CohortMetricsBase: Send + Sync {
.min(self.cost_basis_base().min_stateful_height_len())
}
/// Push state values to height-indexed vectors.
fn truncate_push(&mut self, height: Height, state: &CohortState) -> Result<()> {
self.supply_mut()
.truncate_push(height, state.supply.value)?;
@@ -225,12 +209,18 @@ pub trait CohortMetricsBase: Send + Sync {
)?;
self.unrealized_base_mut().compute_from_stateful(
starting_indexes,
&others.iter().map(|v| v.unrealized_base()).collect::<Vec<_>>(),
&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<_>>(),
&others
.iter()
.map(|v| v.cost_basis_base())
.collect::<Vec<_>>(),
exit,
)?;
Ok(())

View File

@@ -10,18 +10,14 @@ use crate::{
use crate::distribution::metrics::ImportConfig;
/// Adjusted realized metrics (only for adjusted cohorts: all, sth, max_age).
#[derive(Traversable)]
pub struct RealizedAdjusted<M: StorageMode = Rw> {
// === Adjusted Value (computed: cohort - up_to_1h) ===
pub adjusted_value_created: ComputedFromHeight<Cents, M>,
pub adjusted_value_destroyed: ComputedFromHeight<Cents, M>,
// === Adjusted Value Created/Destroyed Rolling Sums ===
pub adjusted_value_created_sum: RollingWindows<Cents, M>,
pub adjusted_value_destroyed_sum: RollingWindows<Cents, M>,
// === Adjusted SOPR (rolling window ratios) ===
pub adjusted_sopr: RollingWindows<StoredF64, M>,
pub adjusted_sopr_ema: RollingEmas1w1m<StoredF64, M>,
}
@@ -30,9 +26,12 @@ impl RealizedAdjusted {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
Ok(RealizedAdjusted {
adjusted_value_created: cfg.import_computed("adjusted_value_created", Version::ZERO)?,
adjusted_value_destroyed: cfg.import_computed("adjusted_value_destroyed", Version::ZERO)?,
adjusted_value_created_sum: cfg.import_rolling("adjusted_value_created", Version::ONE)?,
adjusted_value_destroyed_sum: cfg.import_rolling("adjusted_value_destroyed", Version::ONE)?,
adjusted_value_destroyed: cfg
.import_computed("adjusted_value_destroyed", Version::ZERO)?,
adjusted_value_created_sum: cfg
.import_rolling("adjusted_value_created", Version::ONE)?,
adjusted_value_destroyed_sum: cfg
.import_rolling("adjusted_value_destroyed", Version::ONE)?,
adjusted_sopr: cfg.import_rolling("adjusted_sopr", Version::ONE)?,
adjusted_sopr_ema: cfg.import_emas_1w_1m("adjusted_sopr_24h", Version::ONE)?,
})
@@ -66,19 +65,31 @@ impl RealizedAdjusted {
// Adjusted value created/destroyed rolling sums
let window_starts = blocks.count.window_starts();
self.adjusted_value_created_sum.compute_rolling_sum(
starting_indexes.height, &window_starts, &self.adjusted_value_created.height, exit,
starting_indexes.height,
&window_starts,
&self.adjusted_value_created.height,
exit,
)?;
self.adjusted_value_destroyed_sum.compute_rolling_sum(
starting_indexes.height, &window_starts, &self.adjusted_value_destroyed.height, exit,
starting_indexes.height,
&window_starts,
&self.adjusted_value_destroyed.height,
exit,
)?;
// SOPR ratios from rolling sums
for ((sopr, vc), vd) in self.adjusted_sopr.as_mut_array().into_iter()
for ((sopr, vc), vd) in self
.adjusted_sopr
.as_mut_array()
.into_iter()
.zip(self.adjusted_value_created_sum.as_array())
.zip(self.adjusted_value_destroyed_sum.as_array())
{
sopr.compute_binary::<Cents, Cents, RatioCents64>(
starting_indexes.height, &vc.height, &vd.height, exit,
starting_indexes.height,
&vc.height,
&vd.height,
exit,
)?;
}

View File

@@ -1,56 +1,49 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{
BasisPoints16, BasisPoints32, BasisPointsSigned16,
Bitcoin, Cents, CentsSats, CentsSigned, CentsSquaredSats, Dollars, Height, Indexes, Sats, StoredF32, StoredF64, Version,
BasisPoints16, BasisPoints32, BasisPointsSigned16, Bitcoin, Cents, CentsSats, CentsSigned,
CentsSquaredSats, Dollars, Height, Indexes, Sats, StoredF32, StoredF64, Version,
};
use vecdb::{
AnyStoredVec, AnyVec, BytesVec, Exit, ReadableCloneableVec,
ReadableVec, Rw, StorageMode, WritableVec,
AnyStoredVec, AnyVec, BytesVec, Exit, ReadableCloneableVec, ReadableVec, Rw, StorageMode,
WritableVec,
};
use crate::{
blocks,
distribution::state::RealizedState,
internal::{
CentsPlus, CentsUnsignedToDollars, ComputedFromHeightCumulative, ComputedFromHeight,
ComputedFromHeightRatio, FiatFromHeight, NegCentsUnsignedToDollars, PercentFromHeight,
PercentRollingEmas1w1m, PercentRollingWindows, ValueFromHeightCumulative, LazyFromHeight,
Price,
RatioCentsBp16, RatioCentsSignedCentsBps16, RatioCentsSignedDollarsBps16, RatioCents64,
RollingEmas1w1m, RollingEmas2w, RollingWindows, Identity,
CentsPlus, CentsUnsignedToDollars, ComputedFromHeight, ComputedFromHeightCumulative,
ComputedFromHeightRatio, FiatFromHeight, Identity, LazyFromHeight,
NegCentsUnsignedToDollars, PercentFromHeight, PercentRollingEmas1w1m,
PercentRollingWindows, Price, RatioCents64, RatioCentsBp16, RatioCentsSignedCentsBps16,
RatioCentsSignedDollarsBps16, RollingEmas1w1m, RollingEmas2w, RollingWindows,
ValueFromHeightCumulative,
},
prices,
};
use crate::distribution::metrics::ImportConfig;
/// Base realized metrics (always computed).
#[derive(Traversable)]
pub struct RealizedBase<M: StorageMode = Rw> {
// === Realized Cap ===
pub realized_cap_cents: ComputedFromHeight<Cents, M>,
pub realized_cap: LazyFromHeight<Dollars, Cents>,
pub realized_price: Price<ComputedFromHeight<Cents, M>>,
pub realized_price_ratio: ComputedFromHeightRatio<M>,
pub realized_cap_change_1m: ComputedFromHeight<CentsSigned, M>,
// === Investor Price ===
pub investor_price: Price<ComputedFromHeight<Cents, M>>,
pub investor_price_ratio: ComputedFromHeightRatio<M>,
// === Floor/Ceiling Price Bands ===
pub lower_price_band: Price<ComputedFromHeight<Cents, M>>,
pub upper_price_band: Price<ComputedFromHeight<Cents, M>>,
// === Raw values for aggregation ===
pub cap_raw: M::Stored<BytesVec<Height, CentsSats>>,
pub investor_cap_raw: M::Stored<BytesVec<Height, CentsSquaredSats>>,
// === MVRV ===
pub mvrv: LazyFromHeight<StoredF32>,
// === Realized Profit/Loss ===
pub realized_profit: ComputedFromHeightCumulative<Cents, M>,
pub realized_profit_ema_1w: ComputedFromHeight<Cents, M>,
pub realized_loss: ComputedFromHeightCumulative<Cents, M>,
@@ -60,50 +53,38 @@ pub struct RealizedBase<M: StorageMode = Rw> {
pub net_realized_pnl_ema_1w: ComputedFromHeight<CentsSigned, M>,
pub gross_pnl: FiatFromHeight<Cents, M>,
// === Realized vs Realized Cap Ratios ===
pub realized_profit_rel_to_realized_cap: PercentFromHeight<BasisPoints16, M>,
pub realized_loss_rel_to_realized_cap: PercentFromHeight<BasisPoints16, M>,
pub net_realized_pnl_rel_to_realized_cap: PercentFromHeight<BasisPointsSigned16, M>,
// === Value Created/Destroyed Splits (stored) ===
pub profit_value_created: ComputedFromHeight<Cents, M>,
pub profit_value_destroyed: ComputedFromHeight<Cents, M>,
pub loss_value_created: ComputedFromHeight<Cents, M>,
pub loss_value_destroyed: ComputedFromHeight<Cents, M>,
// === Value Created/Destroyed Totals ===
pub value_created: ComputedFromHeight<Cents, M>,
pub value_destroyed: ComputedFromHeight<Cents, M>,
// === Capitulation/Profit Flow (lazy aliases) ===
pub capitulation_flow: LazyFromHeight<Dollars, Cents>,
pub profit_flow: LazyFromHeight<Dollars, Cents>,
// === Value Created/Destroyed Rolling Sums ===
pub value_created_sum: RollingWindows<Cents, M>,
pub value_destroyed_sum: RollingWindows<Cents, M>,
// === SOPR (rolling window ratios) ===
pub sopr: RollingWindows<StoredF64, M>,
pub sopr_24h_ema: RollingEmas1w1m<StoredF64, M>,
// === Sell Side Risk ===
pub gross_pnl_sum: RollingWindows<Cents, M>,
pub sell_side_risk_ratio: PercentRollingWindows<BasisPoints16, M>,
pub sell_side_risk_ratio_24h_ema: PercentRollingEmas1w1m<BasisPoints16, M>,
// === Net Realized PnL Deltas ===
pub net_pnl_change_1m: ComputedFromHeight<CentsSigned, M>,
pub net_pnl_change_1m_rel_to_realized_cap:
PercentFromHeight<BasisPointsSigned16, M>,
pub net_pnl_change_1m_rel_to_market_cap:
PercentFromHeight<BasisPointsSigned16, M>,
pub net_pnl_change_1m_rel_to_realized_cap: PercentFromHeight<BasisPointsSigned16, M>,
pub net_pnl_change_1m_rel_to_market_cap: PercentFromHeight<BasisPointsSigned16, M>,
// === Peak Regret ===
pub peak_regret: ComputedFromHeightCumulative<Cents, M>,
pub peak_regret_rel_to_realized_cap: PercentFromHeight<BasisPoints16, M>,
// === Sent in Profit/Loss ===
pub sent_in_profit: ValueFromHeightCumulative<M>,
pub sent_in_profit_ema: RollingEmas2w<M>,
pub sent_in_loss: ValueFromHeightCumulative<M>,
@@ -111,15 +92,16 @@ pub struct RealizedBase<M: StorageMode = Rw> {
}
impl RealizedBase {
/// Import realized base metrics from database.
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
let v0 = Version::ZERO;
let v1 = Version::ONE;
let realized_cap_cents = cfg.import_computed("realized_cap_cents", v0)?;
let realized_cap = LazyFromHeight::from_computed::<CentsUnsignedToDollars>(
&cfg.name("realized_cap"), cfg.version,
realized_cap_cents.height.read_only_boxed_clone(), &realized_cap_cents,
&cfg.name("realized_cap"),
cfg.version,
realized_cap_cents.height.read_only_boxed_clone(),
&realized_cap_cents,
);
let realized_profit = cfg.import_cumulative("realized_profit", v0)?;
@@ -128,8 +110,10 @@ impl RealizedBase {
let realized_loss_ema_1w = cfg.import_computed("realized_loss_ema_1w", v0)?;
let neg_realized_loss = LazyFromHeight::from_height_source::<NegCentsUnsignedToDollars>(
&cfg.name("neg_realized_loss"), cfg.version + Version::ONE,
realized_loss.height.read_only_boxed_clone(), cfg.indexes,
&cfg.name("neg_realized_loss"),
cfg.version + Version::ONE,
realized_loss.height.read_only_boxed_clone(),
cfg.indexes,
);
let net_realized_pnl = cfg.import_cumulative("net_realized_pnl", v0)?;
@@ -161,17 +145,23 @@ impl RealizedBase {
let value_destroyed = cfg.import_computed("value_destroyed", v0)?;
let capitulation_flow = LazyFromHeight::from_computed::<CentsUnsignedToDollars>(
&cfg.name("capitulation_flow"), cfg.version,
loss_value_destroyed.height.read_only_boxed_clone(), &loss_value_destroyed,
&cfg.name("capitulation_flow"),
cfg.version,
loss_value_destroyed.height.read_only_boxed_clone(),
&loss_value_destroyed,
);
let profit_flow = LazyFromHeight::from_computed::<CentsUnsignedToDollars>(
&cfg.name("profit_flow"), cfg.version,
profit_value_destroyed.height.read_only_boxed_clone(), &profit_value_destroyed,
&cfg.name("profit_flow"),
cfg.version,
profit_value_destroyed.height.read_only_boxed_clone(),
&profit_value_destroyed,
);
let realized_price_ratio = cfg.import_ratio("realized_price", v1)?;
let mvrv = LazyFromHeight::from_lazy::<Identity<StoredF32>, BasisPoints32>(
&cfg.name("mvrv"), cfg.version, &realized_price_ratio.ratio,
&cfg.name("mvrv"),
cfg.version,
&realized_price_ratio.ratio,
);
// Rolling windows
@@ -183,7 +173,8 @@ impl RealizedBase {
// EMAs
let sopr_24h_ema = cfg.import_emas_1w_1m("sopr_24h", v1)?;
let sell_side_risk_ratio_24h_ema = cfg.import_percent_emas_1w_1m_bp16("sell_side_risk_ratio_24h", v1)?;
let sell_side_risk_ratio_24h_ema =
cfg.import_percent_emas_1w_1m_bp16("sell_side_risk_ratio_24h", v1)?;
let peak_regret_rel_to_realized_cap =
cfg.import_percent_bp16("realized_peak_regret_rel_to_realized_cap", v1)?;
@@ -228,10 +219,10 @@ impl RealizedBase {
sell_side_risk_ratio,
sell_side_risk_ratio_24h_ema,
net_pnl_change_1m: cfg.import_computed("net_pnl_change_1m", Version::new(3))?,
net_pnl_change_1m_rel_to_realized_cap:
cfg.import_percent_bps16("net_pnl_change_1m_rel_to_realized_cap", Version::new(3))?,
net_pnl_change_1m_rel_to_market_cap:
cfg.import_percent_bps16("net_pnl_change_1m_rel_to_market_cap", Version::new(3))?,
net_pnl_change_1m_rel_to_realized_cap: cfg
.import_percent_bps16("net_pnl_change_1m_rel_to_realized_cap", Version::new(3))?,
net_pnl_change_1m_rel_to_market_cap: cfg
.import_percent_bps16("net_pnl_change_1m_rel_to_market_cap", Version::new(3))?,
peak_regret,
peak_regret_rel_to_realized_cap,
sent_in_profit: cfg.import_value_cumulative("sent_in_profit", v0)?,
@@ -241,7 +232,6 @@ impl RealizedBase {
})
}
/// Get minimum length across height-indexed vectors written in block loop.
pub(crate) fn min_stateful_height_len(&self) -> usize {
self.realized_cap
.height
@@ -260,7 +250,6 @@ impl RealizedBase {
.min(self.sent_in_loss.base.sats.height.len())
}
/// Push realized state values to height-indexed vectors.
pub(crate) fn truncate_push(&mut self, height: Height, state: &RealizedState) -> Result<()> {
self.realized_cap_cents
.height
@@ -271,7 +260,8 @@ impl RealizedBase {
self.realized_loss
.height
.truncate_push(height, state.loss())?;
self.investor_price.cents
self.investor_price
.cents
.height
.truncate_push(height, state.investor_price())?;
self.cap_raw.truncate_push(height, state.cap_raw())?;
@@ -306,7 +296,6 @@ impl RealizedBase {
Ok(())
}
/// 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,
@@ -325,7 +314,6 @@ impl RealizedBase {
]
}
/// Compute aggregate values from separate cohorts.
pub(crate) fn compute_from_stateful(
&mut self,
starting_indexes: &Indexes,
@@ -362,7 +350,8 @@ impl RealizedBase {
.iter()
.map(|o| o.investor_price.cents.height.version())
.fold(vecdb::Version::ZERO, |acc, v| acc + v);
self.investor_price.cents
self.investor_price
.cents
.height
.validate_computed_version_or_reset(investor_price_dep_version)?;
@@ -404,7 +393,8 @@ impl RealizedBase {
} else {
Cents::new((sum_investor_cap / sum_cap.inner()) as u64)
};
self.investor_price.cents
self.investor_price
.cents
.height
.truncate_push(height, investor_price)?;
}
@@ -474,7 +464,6 @@ impl RealizedBase {
Ok(())
}
/// First phase of computed metrics (indexes from height).
pub(crate) fn compute_rest_part1(
&mut self,
starting_indexes: &Indexes,
@@ -492,7 +481,10 @@ impl RealizedBase {
&self.realized_profit.height,
&self.realized_loss.height,
|(i, profit, loss, ..)| {
(i, CentsSigned::new(profit.inner() as i64 - loss.inner() as i64))
(
i,
CentsSigned::new(profit.inner() as i64 - loss.inner() as i64),
)
},
exit,
)?;
@@ -512,7 +504,6 @@ impl RealizedBase {
Ok(())
}
/// Second phase of computed metrics (base-only parts: realized price, rolling sums, EMAs).
#[allow(clippy::too_many_arguments)]
pub(crate) fn compute_rest_part2_base(
&mut self,
@@ -608,34 +599,54 @@ impl RealizedBase {
exit,
)?;
// === Rolling sum intermediates ===
let window_starts = blocks.count.window_starts();
self.value_created_sum.compute_rolling_sum(
starting_indexes.height, &window_starts, &self.value_created.height, exit,
starting_indexes.height,
&window_starts,
&self.value_created.height,
exit,
)?;
self.value_destroyed_sum.compute_rolling_sum(
starting_indexes.height, &window_starts, &self.value_destroyed.height, exit,
starting_indexes.height,
&window_starts,
&self.value_destroyed.height,
exit,
)?;
self.gross_pnl_sum.compute_rolling_sum(
starting_indexes.height, &window_starts, &self.gross_pnl.cents.height, exit,
starting_indexes.height,
&window_starts,
&self.gross_pnl.cents.height,
exit,
)?;
// Compute SOPR from rolling sums
for ((sopr, vc), vd) in self.sopr.as_mut_array().into_iter()
for ((sopr, vc), vd) in self
.sopr
.as_mut_array()
.into_iter()
.zip(self.value_created_sum.as_array())
.zip(self.value_destroyed_sum.as_array())
{
sopr.compute_binary::<Cents, Cents, RatioCents64>(
starting_indexes.height, &vc.height, &vd.height, exit,
starting_indexes.height,
&vc.height,
&vd.height,
exit,
)?;
}
// Compute sell-side risk ratios
for (ssrr, rv) in self.sell_side_risk_ratio.as_mut_array().into_iter()
for (ssrr, rv) in self
.sell_side_risk_ratio
.as_mut_array()
.into_iter()
.zip(self.gross_pnl_sum.as_array())
{
ssrr.compute_binary::<Cents, Cents, RatioCentsBp16>(
starting_indexes.height, &rv.height, &self.realized_cap_cents.height, exit,
starting_indexes.height,
&rv.height,
&self.realized_cap_cents.height,
exit,
)?;
}
@@ -652,14 +663,12 @@ impl RealizedBase {
&self.realized_loss.height,
exit,
)?;
self.net_realized_pnl_ema_1w
.height
.compute_rolling_ema(
starting_indexes.height,
&blocks.count.height_1w_ago,
&self.net_realized_pnl.height,
exit,
)?;
self.net_realized_pnl_ema_1w.height.compute_rolling_ema(
starting_indexes.height,
&blocks.count.height_1w_ago,
&self.net_realized_pnl.height,
exit,
)?;
// 14-day EMA of sent in profit/loss
self.sent_in_profit_ema.compute(
@@ -726,14 +735,12 @@ impl RealizedBase {
)?;
// Net realized PnL cumulative 30d delta
self.net_pnl_change_1m
.height
.compute_rolling_change(
starting_indexes.height,
&blocks.count.height_1m_ago,
&self.net_realized_pnl.cumulative.height,
exit,
)?;
self.net_pnl_change_1m.height.compute_rolling_change(
starting_indexes.height,
&blocks.count.height_1m_ago,
&self.net_realized_pnl.cumulative.height,
exit,
)?;
self.net_pnl_change_1m_rel_to_realized_cap
.compute_binary::<CentsSigned, Cents, RatioCentsSignedCentsBps16>(

View File

@@ -6,8 +6,8 @@ use vecdb::{Exit, ReadableVec, Rw, StorageMode};
use crate::{
blocks,
internal::{
ComputedFromHeightRatioExtension, PercentFromHeight,
RatioCents64, RatioDollarsBp16, RollingWindows,
ComputedFromHeightRatioExtension, PercentFromHeight, RatioCents64, RatioDollarsBp16,
RollingWindows,
},
};
@@ -15,19 +15,15 @@ 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: PercentFromHeight<BasisPoints16, M>,
// === Realized Profit/Loss Rolling Sums ===
pub realized_profit_sum: RollingWindows<Cents, M>,
pub realized_loss_sum: RollingWindows<Cents, M>,
// === Realized Profit to Loss Ratio (from rolling sums) ===
pub realized_profit_to_loss_ratio: RollingWindows<StoredF64, M>,
// === Extended ratio metrics for realized/investor price ===
pub realized_price_ratio_ext: ComputedFromHeightRatioExtension<M>,
pub investor_price_ratio_ext: ComputedFromHeightRatioExtension<M>,
}
@@ -35,15 +31,23 @@ pub struct RealizedExtended<M: StorageMode = Rw> {
impl RealizedExtended {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
Ok(RealizedExtended {
realized_cap_rel_to_own_market_cap: cfg.import_percent_bp16("realized_cap_rel_to_own_market_cap", Version::ZERO)?,
realized_cap_rel_to_own_market_cap: cfg
.import_percent_bp16("realized_cap_rel_to_own_market_cap", Version::ZERO)?,
realized_profit_sum: cfg.import_rolling("realized_profit", Version::ONE)?,
realized_loss_sum: cfg.import_rolling("realized_loss", Version::ONE)?,
realized_profit_to_loss_ratio: cfg.import_rolling("realized_profit_to_loss_ratio", Version::ONE)?,
realized_profit_to_loss_ratio: cfg
.import_rolling("realized_profit_to_loss_ratio", Version::ONE)?,
realized_price_ratio_ext: ComputedFromHeightRatioExtension::forced_import(
cfg.db, &cfg.name("realized_price"), cfg.version + Version::ONE, cfg.indexes,
cfg.db,
&cfg.name("realized_price"),
cfg.version + Version::ONE,
cfg.indexes,
)?,
investor_price_ratio_ext: ComputedFromHeightRatioExtension::forced_import(
cfg.db, &cfg.name("investor_price"), cfg.version, cfg.indexes,
cfg.db,
&cfg.name("investor_price"),
cfg.version,
cfg.indexes,
)?,
})
}
@@ -60,10 +64,16 @@ impl RealizedExtended {
// Realized profit/loss rolling sums
let window_starts = blocks.count.window_starts();
self.realized_profit_sum.compute_rolling_sum(
starting_indexes.height, &window_starts, &base.realized_profit.height, exit,
starting_indexes.height,
&window_starts,
&base.realized_profit.height,
exit,
)?;
self.realized_loss_sum.compute_rolling_sum(
starting_indexes.height, &window_starts, &base.realized_loss.height, exit,
starting_indexes.height,
&window_starts,
&base.realized_loss.height,
exit,
)?;
// Realized cap relative to own market cap
@@ -76,12 +86,18 @@ impl RealizedExtended {
)?;
// Realized profit to loss ratios
for ((ratio, profit), loss) in self.realized_profit_to_loss_ratio.as_mut_array().into_iter()
for ((ratio, profit), loss) in self
.realized_profit_to_loss_ratio
.as_mut_array()
.into_iter()
.zip(self.realized_profit_sum.as_array())
.zip(self.realized_loss_sum.as_array())
{
ratio.compute_binary::<Cents, Cents, RatioCents64>(
starting_indexes.height, &profit.height, &loss.height, exit,
starting_indexes.height,
&profit.height,
&loss.height,
exit,
)?;
}

View File

@@ -4,29 +4,23 @@ use brk_types::{BasisPoints16, BasisPointsSigned16, Dollars, Height, Sats, Store
use vecdb::{Exit, ReadableCloneableVec, ReadableVec, Rw, StorageMode};
use crate::internal::{
Bps16ToFloat, LazyFromHeight,
NegRatioDollarsBps16, PercentFromHeight, RatioDollarsBp16, RatioDollarsBps16, RatioSatsBp16,
Bps16ToFloat, LazyFromHeight, NegRatioDollarsBps16, PercentFromHeight, RatioDollarsBp16,
RatioDollarsBps16, RatioSatsBp16,
};
use crate::distribution::metrics::{ImportConfig, RealizedBase, 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(Traversable)]
pub struct RelativeBase<M: StorageMode = Rw> {
// === Supply in Profit/Loss Relative to Own Supply ===
pub supply_in_profit_rel_to_own_supply: PercentFromHeight<BasisPoints16, M>,
pub supply_in_loss_rel_to_own_supply: PercentFromHeight<BasisPoints16, M>,
// === Unrealized vs Market Cap ===
pub unrealized_profit_rel_to_market_cap: PercentFromHeight<BasisPoints16, M>,
pub unrealized_loss_rel_to_market_cap: PercentFromHeight<BasisPoints16, M>,
pub neg_unrealized_loss_rel_to_market_cap: PercentFromHeight<BasisPointsSigned16, M>,
pub net_unrealized_pnl_rel_to_market_cap: PercentFromHeight<BasisPointsSigned16, M>,
pub nupl: LazyFromHeight<StoredF32, BasisPointsSigned16>,
// === Invested Capital in Profit/Loss as % of Realized Cap ===
pub invested_capital_in_profit_rel_to_realized_cap: PercentFromHeight<BasisPoints16, M>,
pub invested_capital_in_loss_rel_to_realized_cap: PercentFromHeight<BasisPoints16, M>,
}
@@ -42,27 +36,34 @@ impl RelativeBase {
let nupl = LazyFromHeight::from_computed::<Bps16ToFloat>(
&cfg.name("nupl"),
cfg.version + v2,
net_unrealized_pnl_rel_to_market_cap.bps.height.read_only_boxed_clone(),
net_unrealized_pnl_rel_to_market_cap
.bps
.height
.read_only_boxed_clone(),
&net_unrealized_pnl_rel_to_market_cap.bps,
);
Ok(Self {
supply_in_profit_rel_to_own_supply:
cfg.import_percent_bp16("supply_in_profit_rel_to_own_supply", v1)?,
supply_in_loss_rel_to_own_supply:
cfg.import_percent_bp16("supply_in_loss_rel_to_own_supply", v1)?,
unrealized_profit_rel_to_market_cap:
cfg.import_percent_bp16("unrealized_profit_rel_to_market_cap", v2)?,
unrealized_loss_rel_to_market_cap:
cfg.import_percent_bp16("unrealized_loss_rel_to_market_cap", v2)?,
neg_unrealized_loss_rel_to_market_cap:
cfg.import_percent_bps16("neg_unrealized_loss_rel_to_market_cap", v2)?,
supply_in_profit_rel_to_own_supply: cfg
.import_percent_bp16("supply_in_profit_rel_to_own_supply", v1)?,
supply_in_loss_rel_to_own_supply: cfg
.import_percent_bp16("supply_in_loss_rel_to_own_supply", v1)?,
unrealized_profit_rel_to_market_cap: cfg
.import_percent_bp16("unrealized_profit_rel_to_market_cap", v2)?,
unrealized_loss_rel_to_market_cap: cfg
.import_percent_bp16("unrealized_loss_rel_to_market_cap", v2)?,
neg_unrealized_loss_rel_to_market_cap: cfg
.import_percent_bps16("neg_unrealized_loss_rel_to_market_cap", v2)?,
net_unrealized_pnl_rel_to_market_cap,
nupl,
invested_capital_in_profit_rel_to_realized_cap:
cfg.import_percent_bp16("invested_capital_in_profit_rel_to_realized_cap", Version::ZERO)?,
invested_capital_in_loss_rel_to_realized_cap:
cfg.import_percent_bp16("invested_capital_in_loss_rel_to_realized_cap", Version::ZERO)?,
invested_capital_in_profit_rel_to_realized_cap: cfg.import_percent_bp16(
"invested_capital_in_profit_rel_to_realized_cap",
Version::ZERO,
)?,
invested_capital_in_loss_rel_to_realized_cap: cfg.import_percent_bp16(
"invested_capital_in_loss_rel_to_realized_cap",
Version::ZERO,
)?,
})
}
@@ -77,35 +78,59 @@ impl RelativeBase {
) -> Result<()> {
self.supply_in_profit_rel_to_own_supply
.compute_binary::<Sats, Sats, RatioSatsBp16>(
max_from, &unrealized.supply_in_profit.sats.height, supply_total_sats, exit,
max_from,
&unrealized.supply_in_profit.sats.height,
supply_total_sats,
exit,
)?;
self.supply_in_loss_rel_to_own_supply
.compute_binary::<Sats, Sats, RatioSatsBp16>(
max_from, &unrealized.supply_in_loss.sats.height, supply_total_sats, exit,
max_from,
&unrealized.supply_in_loss.sats.height,
supply_total_sats,
exit,
)?;
self.unrealized_profit_rel_to_market_cap
.compute_binary::<Dollars, Dollars, RatioDollarsBp16>(
max_from, &unrealized.unrealized_profit.usd.height, market_cap, exit,
max_from,
&unrealized.unrealized_profit.usd.height,
market_cap,
exit,
)?;
self.unrealized_loss_rel_to_market_cap
.compute_binary::<Dollars, Dollars, RatioDollarsBp16>(
max_from, &unrealized.unrealized_loss.usd.height, market_cap, exit,
max_from,
&unrealized.unrealized_loss.usd.height,
market_cap,
exit,
)?;
self.neg_unrealized_loss_rel_to_market_cap
.compute_binary::<Dollars, Dollars, NegRatioDollarsBps16>(
max_from, &unrealized.unrealized_loss.usd.height, market_cap, exit,
max_from,
&unrealized.unrealized_loss.usd.height,
market_cap,
exit,
)?;
self.net_unrealized_pnl_rel_to_market_cap
.compute_binary::<Dollars, Dollars, RatioDollarsBps16>(
max_from, &unrealized.net_unrealized_pnl.usd.height, market_cap, exit,
max_from,
&unrealized.net_unrealized_pnl.usd.height,
market_cap,
exit,
)?;
self.invested_capital_in_profit_rel_to_realized_cap
.compute_binary::<Dollars, Dollars, RatioDollarsBp16>(
max_from, &unrealized.invested_capital_in_profit.usd.height, &realized.realized_cap.height, exit,
max_from,
&unrealized.invested_capital_in_profit.usd.height,
&realized.realized_cap.height,
exit,
)?;
self.invested_capital_in_loss_rel_to_realized_cap
.compute_binary::<Dollars, Dollars, RatioDollarsBp16>(
max_from, &unrealized.invested_capital_in_loss.usd.height, &realized.realized_cap.height, exit,
max_from,
&unrealized.invested_capital_in_loss.usd.height,
&realized.realized_cap.height,
exit,
)?;
Ok(())
}

View File

@@ -12,14 +12,10 @@ use crate::distribution::metrics::{ImportConfig, UnrealizedBase};
/// Extended relative metrics for own market cap (extended && rel_to_all).
#[derive(Traversable)]
pub struct RelativeExtendedOwnMarketCap<M: StorageMode = Rw> {
pub unrealized_profit_rel_to_own_market_cap:
PercentFromHeight<BasisPoints16, M>,
pub unrealized_loss_rel_to_own_market_cap:
PercentFromHeight<BasisPoints16, M>,
pub neg_unrealized_loss_rel_to_own_market_cap:
PercentFromHeight<BasisPointsSigned16, M>,
pub net_unrealized_pnl_rel_to_own_market_cap:
PercentFromHeight<BasisPointsSigned16, M>,
pub unrealized_profit_rel_to_own_market_cap: PercentFromHeight<BasisPoints16, M>,
pub unrealized_loss_rel_to_own_market_cap: PercentFromHeight<BasisPoints16, M>,
pub neg_unrealized_loss_rel_to_own_market_cap: PercentFromHeight<BasisPointsSigned16, M>,
pub net_unrealized_pnl_rel_to_own_market_cap: PercentFromHeight<BasisPointsSigned16, M>,
}
impl RelativeExtendedOwnMarketCap {
@@ -27,14 +23,14 @@ impl RelativeExtendedOwnMarketCap {
let v2 = Version::new(2);
Ok(Self {
unrealized_profit_rel_to_own_market_cap:
cfg.import_percent_bp16("unrealized_profit_rel_to_own_market_cap", v2)?,
unrealized_loss_rel_to_own_market_cap:
cfg.import_percent_bp16("unrealized_loss_rel_to_own_market_cap", v2)?,
neg_unrealized_loss_rel_to_own_market_cap:
cfg.import_percent_bps16("neg_unrealized_loss_rel_to_own_market_cap", v2)?,
net_unrealized_pnl_rel_to_own_market_cap:
cfg.import_percent_bps16("net_unrealized_pnl_rel_to_own_market_cap", v2)?,
unrealized_profit_rel_to_own_market_cap: cfg
.import_percent_bp16("unrealized_profit_rel_to_own_market_cap", v2)?,
unrealized_loss_rel_to_own_market_cap: cfg
.import_percent_bp16("unrealized_loss_rel_to_own_market_cap", v2)?,
neg_unrealized_loss_rel_to_own_market_cap: cfg
.import_percent_bps16("neg_unrealized_loss_rel_to_own_market_cap", v2)?,
net_unrealized_pnl_rel_to_own_market_cap: cfg
.import_percent_bps16("net_unrealized_pnl_rel_to_own_market_cap", v2)?,
})
}
@@ -47,19 +43,31 @@ impl RelativeExtendedOwnMarketCap {
) -> Result<()> {
self.unrealized_profit_rel_to_own_market_cap
.compute_binary::<Dollars, Dollars, RatioDollarsBp16>(
max_from, &unrealized.unrealized_profit.usd.height, own_market_cap, exit,
max_from,
&unrealized.unrealized_profit.usd.height,
own_market_cap,
exit,
)?;
self.unrealized_loss_rel_to_own_market_cap
.compute_binary::<Dollars, Dollars, RatioDollarsBp16>(
max_from, &unrealized.unrealized_loss.usd.height, own_market_cap, exit,
max_from,
&unrealized.unrealized_loss.usd.height,
own_market_cap,
exit,
)?;
self.neg_unrealized_loss_rel_to_own_market_cap
.compute_binary::<Dollars, Dollars, NegRatioDollarsBps16>(
max_from, &unrealized.unrealized_loss.usd.height, own_market_cap, exit,
max_from,
&unrealized.unrealized_loss.usd.height,
own_market_cap,
exit,
)?;
self.net_unrealized_pnl_rel_to_own_market_cap
.compute_binary::<Dollars, Dollars, RatioDollarsBps16>(
max_from, &unrealized.net_unrealized_pnl.usd.height, own_market_cap, exit,
max_from,
&unrealized.net_unrealized_pnl.usd.height,
own_market_cap,
exit,
)?;
Ok(())
}

View File

@@ -12,14 +12,10 @@ use crate::distribution::metrics::{ImportConfig, UnrealizedBase};
/// Extended relative metrics for own total unrealized PnL (extended only).
#[derive(Traversable)]
pub struct RelativeExtendedOwnPnl<M: StorageMode = Rw> {
pub unrealized_profit_rel_to_own_gross_pnl:
PercentFromHeight<BasisPoints16, M>,
pub unrealized_loss_rel_to_own_gross_pnl:
PercentFromHeight<BasisPoints16, M>,
pub neg_unrealized_loss_rel_to_own_gross_pnl:
PercentFromHeight<BasisPointsSigned16, M>,
pub net_unrealized_pnl_rel_to_own_gross_pnl:
PercentFromHeight<BasisPointsSigned16, M>,
pub unrealized_profit_rel_to_own_gross_pnl: PercentFromHeight<BasisPoints16, M>,
pub unrealized_loss_rel_to_own_gross_pnl: PercentFromHeight<BasisPoints16, M>,
pub neg_unrealized_loss_rel_to_own_gross_pnl: PercentFromHeight<BasisPointsSigned16, M>,
pub net_unrealized_pnl_rel_to_own_gross_pnl: PercentFromHeight<BasisPointsSigned16, M>,
}
impl RelativeExtendedOwnPnl {
@@ -28,14 +24,14 @@ impl RelativeExtendedOwnPnl {
let v2 = Version::new(2);
Ok(Self {
unrealized_profit_rel_to_own_gross_pnl:
cfg.import_percent_bp16("unrealized_profit_rel_to_own_gross_pnl", v1)?,
unrealized_loss_rel_to_own_gross_pnl:
cfg.import_percent_bp16("unrealized_loss_rel_to_own_gross_pnl", v1)?,
neg_unrealized_loss_rel_to_own_gross_pnl:
cfg.import_percent_bps16("neg_unrealized_loss_rel_to_own_gross_pnl", v1)?,
net_unrealized_pnl_rel_to_own_gross_pnl:
cfg.import_percent_bps16("net_unrealized_pnl_rel_to_own_gross_pnl", v2)?,
unrealized_profit_rel_to_own_gross_pnl: cfg
.import_percent_bp16("unrealized_profit_rel_to_own_gross_pnl", v1)?,
unrealized_loss_rel_to_own_gross_pnl: cfg
.import_percent_bp16("unrealized_loss_rel_to_own_gross_pnl", v1)?,
neg_unrealized_loss_rel_to_own_gross_pnl: cfg
.import_percent_bps16("neg_unrealized_loss_rel_to_own_gross_pnl", v1)?,
net_unrealized_pnl_rel_to_own_gross_pnl: cfg
.import_percent_bps16("net_unrealized_pnl_rel_to_own_gross_pnl", v2)?,
})
}
@@ -47,19 +43,31 @@ impl RelativeExtendedOwnPnl {
) -> Result<()> {
self.unrealized_profit_rel_to_own_gross_pnl
.compute_binary::<Dollars, Dollars, RatioDollarsBp16>(
max_from, &unrealized.unrealized_profit.usd.height, &unrealized.gross_pnl.usd.height, exit,
max_from,
&unrealized.unrealized_profit.usd.height,
&unrealized.gross_pnl.usd.height,
exit,
)?;
self.unrealized_loss_rel_to_own_gross_pnl
.compute_binary::<Dollars, Dollars, RatioDollarsBp16>(
max_from, &unrealized.unrealized_loss.usd.height, &unrealized.gross_pnl.usd.height, exit,
max_from,
&unrealized.unrealized_loss.usd.height,
&unrealized.gross_pnl.usd.height,
exit,
)?;
self.neg_unrealized_loss_rel_to_own_gross_pnl
.compute_binary::<Dollars, Dollars, NegRatioDollarsBps16>(
max_from, &unrealized.unrealized_loss.usd.height, &unrealized.gross_pnl.usd.height, exit,
max_from,
&unrealized.unrealized_loss.usd.height,
&unrealized.gross_pnl.usd.height,
exit,
)?;
self.net_unrealized_pnl_rel_to_own_gross_pnl
.compute_binary::<Dollars, Dollars, RatioDollarsBps16>(
max_from, &unrealized.net_unrealized_pnl.usd.height, &unrealized.gross_pnl.usd.height, exit,
max_from,
&unrealized.net_unrealized_pnl.usd.height,
&unrealized.gross_pnl.usd.height,
exit,
)?;
Ok(())
}

View File

@@ -10,23 +10,20 @@ use crate::distribution::metrics::{ImportConfig, UnrealizedBase};
/// Relative-to-all metrics (not present for the "all" cohort itself).
#[derive(Traversable)]
pub struct RelativeToAll<M: StorageMode = Rw> {
pub supply_rel_to_circulating_supply:
PercentFromHeight<BasisPoints16, M>,
pub supply_in_profit_rel_to_circulating_supply:
PercentFromHeight<BasisPoints16, M>,
pub supply_in_loss_rel_to_circulating_supply:
PercentFromHeight<BasisPoints16, M>,
pub supply_rel_to_circulating_supply: PercentFromHeight<BasisPoints16, M>,
pub supply_in_profit_rel_to_circulating_supply: PercentFromHeight<BasisPoints16, M>,
pub supply_in_loss_rel_to_circulating_supply: PercentFromHeight<BasisPoints16, M>,
}
impl RelativeToAll {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
Ok(Self {
supply_rel_to_circulating_supply:
cfg.import_percent_bp16("supply_rel_to_circulating_supply", Version::ONE)?,
supply_in_profit_rel_to_circulating_supply:
cfg.import_percent_bp16("supply_in_profit_rel_to_circulating_supply", Version::ONE)?,
supply_in_loss_rel_to_circulating_supply:
cfg.import_percent_bp16("supply_in_loss_rel_to_circulating_supply", Version::ONE)?,
supply_rel_to_circulating_supply: cfg
.import_percent_bp16("supply_rel_to_circulating_supply", Version::ONE)?,
supply_in_profit_rel_to_circulating_supply: cfg
.import_percent_bp16("supply_in_profit_rel_to_circulating_supply", Version::ONE)?,
supply_in_loss_rel_to_circulating_supply: cfg
.import_percent_bp16("supply_in_loss_rel_to_circulating_supply", Version::ONE)?,
})
}
@@ -40,15 +37,24 @@ impl RelativeToAll {
) -> Result<()> {
self.supply_rel_to_circulating_supply
.compute_binary::<Sats, Sats, RatioSatsBp16>(
max_from, supply_total_sats, all_supply_sats, exit,
max_from,
supply_total_sats,
all_supply_sats,
exit,
)?;
self.supply_in_profit_rel_to_circulating_supply
.compute_binary::<Sats, Sats, RatioSatsBp16>(
max_from, &unrealized.supply_in_profit.sats.height, all_supply_sats, exit,
max_from,
&unrealized.supply_in_profit.sats.height,
all_supply_sats,
exit,
)?;
self.supply_in_loss_rel_to_circulating_supply
.compute_binary::<Sats, Sats, RatioSatsBp16>(
max_from, &unrealized.supply_in_loss.sats.height, all_supply_sats, exit,
max_from,
&unrealized.supply_in_loss.sats.height,
all_supply_sats,
exit,
)?;
Ok(())
}

View File

@@ -6,10 +6,7 @@ use vecdb::{Exit, ReadableVec, Rw, StorageMode};
use crate::distribution::metrics::{ImportConfig, RealizedBase, UnrealizedBase};
use super::{
RelativeBase, RelativeExtendedOwnMarketCap, RelativeExtendedOwnPnl,
RelativeToAll,
};
use super::{RelativeBase, RelativeExtendedOwnMarketCap, RelativeExtendedOwnPnl, RelativeToAll};
/// Full extended relative metrics (base + rel_to_all + own_market_cap + own_pnl).
/// Used by: sth, lth, age_range cohorts.

View File

@@ -7,8 +7,8 @@ use rayon::prelude::*;
use vecdb::{AnyStoredVec, AnyVec, Exit, Rw, StorageMode, WritableVec};
use crate::internal::{
HalveCents, HalveDollars, HalveSats, HalveSatsToBitcoin,
LazyValueFromHeight, ValueFromHeightChange, ValueFromHeight,
HalveCents, HalveDollars, HalveSats, HalveSatsToBitcoin, LazyValueFromHeight, ValueFromHeight,
ValueFromHeightChange,
};
use super::ImportConfig;

View File

@@ -2,8 +2,8 @@ use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Cents, CentsSats, CentsSigned, CentsSquaredSats, Height, Indexes, Version};
use vecdb::{
AnyStoredVec, AnyVec, BytesVec, Exit, ReadableCloneableVec, ReadableVec,
Rw, StorageMode, WritableVec,
AnyStoredVec, AnyVec, BytesVec, Exit, ReadableCloneableVec, ReadableVec, Rw, StorageMode,
WritableVec,
};
use crate::{
@@ -19,36 +19,28 @@ use brk_types::Dollars;
use crate::distribution::metrics::ImportConfig;
/// Base unrealized profit/loss metrics (always computed).
#[derive(Traversable)]
pub struct UnrealizedBase<M: StorageMode = Rw> {
// === Supply in Profit/Loss ===
pub supply_in_profit: ValueFromHeight<M>,
pub supply_in_loss: ValueFromHeight<M>,
// === Unrealized Profit/Loss ===
pub unrealized_profit: FiatFromHeight<Cents, M>,
pub unrealized_loss: FiatFromHeight<Cents, M>,
// === Invested Capital in Profit/Loss ===
pub invested_capital_in_profit: FiatFromHeight<Cents, M>,
pub invested_capital_in_loss: FiatFromHeight<Cents, M>,
// === Raw values for precise aggregation (used to compute pain/greed indices) ===
pub invested_capital_in_profit_raw: M::Stored<BytesVec<Height, CentsSats>>,
pub invested_capital_in_loss_raw: M::Stored<BytesVec<Height, CentsSats>>,
pub investor_cap_in_profit_raw: M::Stored<BytesVec<Height, CentsSquaredSats>>,
pub investor_cap_in_loss_raw: M::Stored<BytesVec<Height, CentsSquaredSats>>,
// === Pain/Greed Indices ===
pub pain_index: FiatFromHeight<Cents, M>,
pub greed_index: FiatFromHeight<Cents, M>,
pub net_sentiment: FiatFromHeight<CentsSigned, M>,
// === Negated ===
pub neg_unrealized_loss: LazyFromHeight<Dollars, Cents>,
// === Net and Total ===
pub net_unrealized_pnl: FiatFromHeight<CentsSigned, M>,
pub gross_pnl: FiatFromHeight<Cents, M>,
}
@@ -65,7 +57,8 @@ impl UnrealizedBase {
let invested_capital_in_profit = cfg.import_fiat("invested_capital_in_profit", v0)?;
let invested_capital_in_loss = cfg.import_fiat("invested_capital_in_loss", v0)?;
let invested_capital_in_profit_raw = cfg.import_bytes("invested_capital_in_profit_raw", v0)?;
let invested_capital_in_profit_raw =
cfg.import_bytes("invested_capital_in_profit_raw", v0)?;
let invested_capital_in_loss_raw = cfg.import_bytes("invested_capital_in_loss_raw", v0)?;
let investor_cap_in_profit_raw = cfg.import_bytes("investor_cap_in_profit_raw", v0)?;
let investor_cap_in_loss_raw = cfg.import_bytes("investor_cap_in_loss_raw", v0)?;
@@ -193,39 +186,30 @@ impl UnrealizedBase {
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
.cents
.height
.compute_sum_of_others(
starting_indexes.height,
&others
.iter()
.map(|v| &v.unrealized_profit.cents.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.cents.height.compute_sum_of_others(
starting_indexes.height,
&others
.iter()
.map(|v| &v.unrealized_profit.cents.height)
.collect::<Vec<_>>(),
exit,
)?;
self.unrealized_loss.cents.height.compute_sum_of_others(
starting_indexes.height,
&others
@@ -273,7 +257,10 @@ impl UnrealizedBase {
// Pre-collect all cohort data to avoid per-element BytesVec reads in nested loop
let invested_profit_ranges: Vec<Vec<CentsSats>> = others
.iter()
.map(|o| o.invested_capital_in_profit_raw.collect_range_at(start, end))
.map(|o| {
o.invested_capital_in_profit_raw
.collect_range_at(start, end)
})
.collect();
let invested_loss_ranges: Vec<Vec<CentsSats>> = others
.iter()
@@ -336,10 +323,7 @@ impl UnrealizedBase {
}
let investor_price_losers = investor_cap.inner() / invested_cap.inner();
let spot_u128 = spot.as_u128();
(
h,
Cents::new((investor_price_losers - spot_u128) as u64),
)
(h, Cents::new((investor_price_losers - spot_u128) as u64))
},
exit,
)?;
@@ -356,10 +340,7 @@ impl UnrealizedBase {
}
let investor_price_winners = investor_cap.inner() / invested_cap.inner();
let spot_u128 = spot.as_u128();
(
h,
Cents::new((spot_u128 - investor_price_winners) as u64),
)
(h, Cents::new((spot_u128 - investor_price_winners) as u64))
},
exit,
)?;

View File

@@ -18,8 +18,7 @@ impl AddressCohortState {
pub(crate) fn new(path: &Path, name: &str) -> Self {
Self {
addr_count: 0,
inner: CohortState::new(path, name)
.with_price_rounding(COST_BASIS_PRICE_DIGITS),
inner: CohortState::new(path, name).with_price_rounding(COST_BASIS_PRICE_DIGITS),
}
}
@@ -136,5 +135,4 @@ impl AddressCohortState {
self.inner.decrement_snapshot(&snapshot);
}
}

View File

@@ -1,7 +1,7 @@
use std::path::Path;
use brk_error::Result;
use brk_types::{Age, CentsSats, Cents, CostBasisSnapshot, Height, Sats, SupplyState};
use brk_types::{Age, Cents, CentsSats, CostBasisSnapshot, Height, Sats, SupplyState};
use super::super::cost_basis::{CostBasisData, Percentiles, RealizedState, UnrealizedState};
@@ -256,8 +256,7 @@ impl CohortState {
}
pub(crate) fn compute_unrealized_state(&mut self, height_price: Cents) -> UnrealizedState {
self.cost_basis_data
.compute_unrealized_state(height_price)
self.cost_basis_data.compute_unrealized_state(height_price)
}
pub(crate) fn write(&mut self, height: Height, cleanup: bool) -> Result<()> {

View File

@@ -6,7 +6,7 @@ use std::{
use brk_error::{Error, Result};
use brk_types::{
CentsCompact, CentsSats, CentsSquaredSats, Cents, CostBasisDistribution, Height, Sats,
Cents, CentsCompact, CentsSats, CentsSquaredSats, CostBasisDistribution, Height, Sats,
};
use rustc_hash::FxHashMap;
use vecdb::Bytes;
@@ -95,7 +95,13 @@ impl CostBasisData {
pub(crate) fn iter(&self) -> impl Iterator<Item = (CentsCompact, &Sats)> {
self.assert_pending_empty();
self.state.as_ref().unwrap().base.map.iter().map(|(&k, v)| (k, v))
self.state
.as_ref()
.unwrap()
.base
.map
.iter()
.map(|(&k, v)| (k, v))
}
pub(crate) fn is_empty(&self) -> bool {
@@ -105,7 +111,8 @@ impl CostBasisData {
pub(crate) fn first_key_value(&self) -> Option<(CentsCompact, &Sats)> {
self.assert_pending_empty();
self.state
.as_ref().unwrap()
.as_ref()
.unwrap()
.base
.map
.first_key_value()
@@ -115,7 +122,8 @@ impl CostBasisData {
pub(crate) fn last_key_value(&self) -> Option<(CentsCompact, &Sats)> {
self.assert_pending_empty();
self.state
.as_ref().unwrap()
.as_ref()
.unwrap()
.base
.map
.last_key_value()
@@ -179,7 +187,14 @@ impl CostBasisData {
self.percentiles_dirty = true;
}
for (cents, (inc, dec)) in self.pending.drain() {
let entry = self.state.as_mut().unwrap().base.map.entry(cents).or_default();
let entry = self
.state
.as_mut()
.unwrap()
.base
.map
.entry(cents)
.or_default();
*entry += inc;
if *entry < dec {
panic!(
@@ -322,7 +337,10 @@ impl CostBasisData {
}
}
fs::write(self.path_state(height), self.state.as_ref().unwrap().serialize()?)?;
fs::write(
self.path_state(height),
self.state.as_ref().unwrap().serialize()?,
)?;
Ok(())
}

View File

@@ -1,6 +1,6 @@
use std::cmp::Ordering;
use brk_types::{CentsSats, CentsSquaredSats, Cents, Sats};
use brk_types::{Cents, CentsSats, CentsSquaredSats, Sats};
/// Realized state using u128 for raw cent*sat values internally.
/// This avoids overflow and defers division to output time for efficiency.
@@ -156,14 +156,22 @@ impl RealizedState {
/// Increment using pre-computed snapshot values (for address path)
#[inline]
pub(crate) fn increment_snapshot(&mut self, price_sats: CentsSats, investor_cap: CentsSquaredSats) {
pub(crate) fn increment_snapshot(
&mut self,
price_sats: CentsSats,
investor_cap: CentsSquaredSats,
) {
self.cap_raw += price_sats.as_u128();
self.investor_cap_raw += investor_cap;
}
/// Decrement using pre-computed snapshot values (for address path)
#[inline]
pub(crate) fn decrement_snapshot(&mut self, price_sats: CentsSats, investor_cap: CentsSquaredSats) {
pub(crate) fn decrement_snapshot(
&mut self,
price_sats: CentsSats,
investor_cap: CentsSquaredSats,
) {
self.cap_raw -= price_sats.as_u128();
self.investor_cap_raw -= investor_cap;
}

View File

@@ -35,7 +35,6 @@ impl UnrealizedState {
invested_capital_in_profit_raw: 0,
invested_capital_in_loss_raw: 0,
};
}
/// Internal cache state using u128 for raw cent*sat values.
@@ -279,5 +278,4 @@ impl CachedUnrealizedState {
state
}
}

View File

@@ -4,12 +4,12 @@ use brk_error::Result;
use brk_indexer::Indexer;
use brk_traversable::Traversable;
use brk_types::{
Cents, EmptyAddressData, EmptyAddressIndex, FundedAddressData, FundedAddressIndex,
Height, Indexes, SupplyState, Timestamp, TxIndex, Version,
Cents, EmptyAddressData, EmptyAddressIndex, FundedAddressData, FundedAddressIndex, Height,
Indexes, SupplyState, Timestamp, TxIndex, Version,
};
use tracing::{debug, info};
use vecdb::{
AnyVec, BytesVec, Database, Exit, ImportableVec, LazyVecFrom1, PAGE_SIZE, ReadOnlyClone,
AnyVec, BytesVec, Database, Exit, ImportableVec, LazyVecFrom1, ReadOnlyClone,
ReadableCloneableVec, ReadableVec, Rw, Stamp, StorageMode, WritableVec,
};
@@ -22,7 +22,9 @@ use crate::{
},
state::BlockState,
},
indexes, inputs, outputs, prices, transactions,
indexes, inputs,
internal::{finalize_db, open_db},
outputs, prices, transactions,
};
use super::{
@@ -34,8 +36,6 @@ use super::{
};
const VERSION: Version = Version::new(22);
/// Main struct holding all computed vectors and state for stateful computation.
#[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw> {
#[traversable(skip)]
@@ -95,8 +95,7 @@ impl Vecs {
let db_path = parent.join(super::DB_NAME);
let states_path = db_path.join("states");
let db = Database::open(&db_path)?;
db.set_min_len(PAGE_SIZE * 20_000_000)?;
let db = open_db(parent, super::DB_NAME, 20_000_000)?;
db.set_min_regions(50_000)?;
let version = parent_version + VERSION;
@@ -139,8 +138,7 @@ impl Vecs {
let total_addr_count = TotalAddrCountVecs::forced_import(&db, version, indexes)?;
// Per-block delta of total (global + per-type)
let new_addr_count =
NewAddrCountVecs::forced_import(&db, version, indexes)?;
let new_addr_count = NewAddrCountVecs::forced_import(&db, version, indexes)?;
// Growth rate: new / addr_count (global + per-type)
let growth_rate = GrowthRateVecs::forced_import(&db, version, indexes)?;
@@ -180,13 +178,7 @@ impl Vecs {
states_path,
};
this.db.retain_regions(
this.iter_any_exportable()
.flat_map(|v| v.region_names())
.collect(),
)?;
this.db.compact()?;
finalize_db(&this.db, &this)?;
Ok(this)
}
@@ -308,7 +300,10 @@ impl Vecs {
Height::ZERO
} else if chain_state.len() == usize::from(recovered_height) {
// Normal resume: chain_state already matches, reuse as-is
debug!("reusing in-memory chain_state ({} entries)", chain_state.len());
debug!(
"reusing in-memory chain_state ({} entries)",
chain_state.len()
);
recovered_height
} else {
debug!("rebuilding chain_state from stored values");
@@ -359,8 +354,7 @@ impl Vecs {
let cached_prices = std::mem::take(&mut self.cached_prices);
let cached_timestamps = std::mem::take(&mut self.cached_timestamps);
let cached_price_range_max =
std::mem::take(&mut self.cached_price_range_max);
let cached_price_range_max = std::mem::take(&mut self.cached_price_range_max);
process_blocks(
self,
@@ -424,8 +418,12 @@ impl Vecs {
self.address_activity
.compute_rest(starting_indexes.height, &window_starts, exit)?;
self.new_addr_count
.compute(starting_indexes.height, &window_starts, &self.total_addr_count, exit)?;
self.new_addr_count.compute(
starting_indexes.height,
&window_starts,
&self.total_addr_count,
exit,
)?;
// 6e. Compute growth_rate = new_addr_count / addr_count
self.growth_rate.compute(