global: big snapshot

This commit is contained in:
nym21
2026-01-09 20:00:20 +01:00
parent cb0abc324e
commit 426d7797a3
442 changed files with 17952 additions and 20071 deletions

View File

@@ -5,29 +5,70 @@ use brk_types::{Height, StoredU64, Version};
use derive_more::{Deref, DerefMut};
use rayon::prelude::*;
use vecdb::{
AnyStoredVec, AnyVec, Database, EagerVec, Exit, GenericStoredVec, ImportableVec,
IterableCloneableVec, PcoVec, TypedVecIterator,
AnyStoredVec, AnyVec, Database, EagerVec, Exit, GenericStoredVec, PcoVec, TypedVecIterator,
};
use crate::{ComputeIndexes, indexes, internal::DerivedComputedBlockLast};
use crate::{ComputeIndexes, indexes, internal::ComputedBlockLast};
/// Address count per address type (runtime state).
#[derive(Debug, Default, Deref, DerefMut)]
pub struct AddressTypeToAddressCount(ByAddressType<u64>);
impl From<(&AddressTypeToHeightToAddressCount, Height)> for AddressTypeToAddressCount {
impl AddressTypeToAddressCount {
#[inline]
fn from((groups, starting_height): (&AddressTypeToHeightToAddressCount, Height)) -> Self {
pub fn sum(&self) -> u64 {
self.0.values().sum()
}
}
impl From<(&AddressTypeToAddrCountVecs, Height)> for AddressTypeToAddressCount {
#[inline]
fn from((groups, starting_height): (&AddressTypeToAddrCountVecs, Height)) -> Self {
if let Some(prev_height) = starting_height.decremented() {
Self(ByAddressType {
p2pk65: groups.p2pk65.into_iter().get_unwrap(prev_height).into(),
p2pk33: groups.p2pk33.into_iter().get_unwrap(prev_height).into(),
p2pkh: groups.p2pkh.into_iter().get_unwrap(prev_height).into(),
p2sh: groups.p2sh.into_iter().get_unwrap(prev_height).into(),
p2wpkh: groups.p2wpkh.into_iter().get_unwrap(prev_height).into(),
p2wsh: groups.p2wsh.into_iter().get_unwrap(prev_height).into(),
p2tr: groups.p2tr.into_iter().get_unwrap(prev_height).into(),
p2a: groups.p2a.into_iter().get_unwrap(prev_height).into(),
p2pk65: groups
.p2pk65
.height
.into_iter()
.get_unwrap(prev_height)
.into(),
p2pk33: groups
.p2pk33
.height
.into_iter()
.get_unwrap(prev_height)
.into(),
p2pkh: groups
.p2pkh
.height
.into_iter()
.get_unwrap(prev_height)
.into(),
p2sh: groups
.p2sh
.height
.into_iter()
.get_unwrap(prev_height)
.into(),
p2wpkh: groups
.p2wpkh
.height
.into_iter()
.get_unwrap(prev_height)
.into(),
p2wsh: groups
.p2wsh
.height
.into_iter()
.get_unwrap(prev_height)
.into(),
p2tr: groups
.p2tr
.height
.into_iter()
.get_unwrap(prev_height)
.into(),
p2a: groups.p2a.height.into_iter().get_unwrap(prev_height).into(),
})
} else {
Default::default()
@@ -35,200 +76,213 @@ impl From<(&AddressTypeToHeightToAddressCount, Height)> for AddressTypeToAddress
}
}
/// Address count per address type, indexed by height.
#[derive(Debug, Clone, Deref, DerefMut, Traversable)]
pub struct AddressTypeToHeightToAddressCount(ByAddressType<EagerVec<PcoVec<Height, StoredU64>>>);
impl From<ByAddressType<EagerVec<PcoVec<Height, StoredU64>>>>
for AddressTypeToHeightToAddressCount
{
#[inline]
fn from(value: ByAddressType<EagerVec<PcoVec<Height, StoredU64>>>) -> Self {
Self(value)
}
}
impl AddressTypeToHeightToAddressCount {
pub fn min_len(&self) -> usize {
self.p2pk65
.len()
.min(self.p2pk33.len())
.min(self.p2pkh.len())
.min(self.p2sh.len())
.min(self.p2wpkh.len())
.min(self.p2wsh.len())
.min(self.p2tr.len())
.min(self.p2a.len())
}
pub fn forced_import(db: &Database, name: &str, version: Version) -> Result<Self> {
Ok(Self::from(ByAddressType::new_with_name(|type_name| {
Ok(EagerVec::forced_import(
db,
&format!("{type_name}_{name}"),
version,
)?)
})?))
}
/// Returns a parallel iterator over all vecs for parallel writing.
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
let inner = &mut self.0;
[
&mut inner.p2pk65 as &mut dyn AnyStoredVec,
&mut inner.p2pk33 as &mut dyn AnyStoredVec,
&mut inner.p2pkh as &mut dyn AnyStoredVec,
&mut inner.p2sh as &mut dyn AnyStoredVec,
&mut inner.p2wpkh as &mut dyn AnyStoredVec,
&mut inner.p2wsh as &mut dyn AnyStoredVec,
&mut inner.p2tr as &mut dyn AnyStoredVec,
&mut inner.p2a as &mut dyn AnyStoredVec,
]
.into_par_iter()
}
pub fn write(&mut self) -> Result<()> {
self.p2pk65.write()?;
self.p2pk33.write()?;
self.p2pkh.write()?;
self.p2sh.write()?;
self.p2wpkh.write()?;
self.p2wsh.write()?;
self.p2tr.write()?;
self.p2a.write()?;
Ok(())
}
pub fn truncate_push(
&mut self,
height: Height,
addresstype_to_usize: &AddressTypeToAddressCount,
) -> Result<()> {
self.p2pk65
.truncate_push(height, addresstype_to_usize.p2pk65.into())?;
self.p2pk33
.truncate_push(height, addresstype_to_usize.p2pk33.into())?;
self.p2pkh
.truncate_push(height, addresstype_to_usize.p2pkh.into())?;
self.p2sh
.truncate_push(height, addresstype_to_usize.p2sh.into())?;
self.p2wpkh
.truncate_push(height, addresstype_to_usize.p2wpkh.into())?;
self.p2wsh
.truncate_push(height, addresstype_to_usize.p2wsh.into())?;
self.p2tr
.truncate_push(height, addresstype_to_usize.p2tr.into())?;
self.p2a
.truncate_push(height, addresstype_to_usize.p2a.into())?;
Ok(())
}
pub fn reset(&mut self) -> Result<()> {
use vecdb::GenericStoredVec;
self.p2pk65.reset()?;
self.p2pk33.reset()?;
self.p2pkh.reset()?;
self.p2sh.reset()?;
self.p2wpkh.reset()?;
self.p2wsh.reset()?;
self.p2tr.reset()?;
self.p2a.reset()?;
Ok(())
}
}
/// Address count per address type, indexed by various indexes (dateindex, etc.).
/// Address count per address type, with height + derived indexes.
#[derive(Clone, Deref, DerefMut, Traversable)]
pub struct AddressTypeToIndexesToAddressCount(ByAddressType<DerivedComputedBlockLast<StoredU64>>);
pub struct AddressTypeToAddrCountVecs(ByAddressType<ComputedBlockLast<StoredU64>>);
impl From<ByAddressType<DerivedComputedBlockLast<StoredU64>>>
for AddressTypeToIndexesToAddressCount
{
impl From<ByAddressType<ComputedBlockLast<StoredU64>>> for AddressTypeToAddrCountVecs {
#[inline]
fn from(value: ByAddressType<DerivedComputedBlockLast<StoredU64>>) -> Self {
fn from(value: ByAddressType<ComputedBlockLast<StoredU64>>) -> Self {
Self(value)
}
}
impl AddressTypeToIndexesToAddressCount {
impl AddressTypeToAddrCountVecs {
pub fn forced_import(
db: &Database,
name: &str,
version: Version,
indexes: &indexes::Vecs,
sources: &AddressTypeToHeightToAddressCount,
) -> Result<Self> {
Ok(Self::from(ByAddressType::<
DerivedComputedBlockLast<StoredU64>,
>::try_zip_with_name(
sources,
|type_name, source| {
DerivedComputedBlockLast::forced_import(
Ok(Self::from(
ByAddressType::<ComputedBlockLast<StoredU64>>::new_with_name(|type_name| {
ComputedBlockLast::forced_import(
db,
&format!("{type_name}_{name}"),
source.boxed_clone(),
version,
indexes,
)
},
)?))
})?,
))
}
pub fn compute(
pub fn min_len(&self) -> usize {
self.p2pk65
.height
.len()
.min(self.p2pk33.height.len())
.min(self.p2pkh.height.len())
.min(self.p2sh.height.len())
.min(self.p2wpkh.height.len())
.min(self.p2wsh.height.len())
.min(self.p2tr.height.len())
.min(self.p2a.height.len())
}
pub fn par_iter_height_mut(&mut self) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
let inner = &mut self.0;
[
&mut inner.p2pk65.height as &mut dyn AnyStoredVec,
&mut inner.p2pk33.height as &mut dyn AnyStoredVec,
&mut inner.p2pkh.height as &mut dyn AnyStoredVec,
&mut inner.p2sh.height as &mut dyn AnyStoredVec,
&mut inner.p2wpkh.height as &mut dyn AnyStoredVec,
&mut inner.p2wsh.height as &mut dyn AnyStoredVec,
&mut inner.p2tr.height as &mut dyn AnyStoredVec,
&mut inner.p2a.height as &mut dyn AnyStoredVec,
]
.into_par_iter()
}
pub fn write_height(&mut self) -> Result<()> {
self.p2pk65.height.write()?;
self.p2pk33.height.write()?;
self.p2pkh.height.write()?;
self.p2sh.height.write()?;
self.p2wpkh.height.write()?;
self.p2wsh.height.write()?;
self.p2tr.height.write()?;
self.p2a.height.write()?;
Ok(())
}
pub fn truncate_push_height(
&mut self,
height: Height,
addr_counts: &AddressTypeToAddressCount,
) -> Result<()> {
self.p2pk65
.height
.truncate_push(height, addr_counts.p2pk65.into())?;
self.p2pk33
.height
.truncate_push(height, addr_counts.p2pk33.into())?;
self.p2pkh
.height
.truncate_push(height, addr_counts.p2pkh.into())?;
self.p2sh
.height
.truncate_push(height, addr_counts.p2sh.into())?;
self.p2wpkh
.height
.truncate_push(height, addr_counts.p2wpkh.into())?;
self.p2wsh
.height
.truncate_push(height, addr_counts.p2wsh.into())?;
self.p2tr
.height
.truncate_push(height, addr_counts.p2tr.into())?;
self.p2a
.height
.truncate_push(height, addr_counts.p2a.into())?;
Ok(())
}
pub fn reset_height(&mut self) -> Result<()> {
use vecdb::GenericStoredVec;
self.p2pk65.height.reset()?;
self.p2pk33.height.reset()?;
self.p2pkh.height.reset()?;
self.p2sh.height.reset()?;
self.p2wpkh.height.reset()?;
self.p2wsh.height.reset()?;
self.p2tr.height.reset()?;
self.p2a.height.reset()?;
Ok(())
}
pub fn compute_rest(
&mut self,
indexes: &indexes::Vecs,
starting_indexes: &ComputeIndexes,
exit: &Exit,
addresstype_to_height_to_addresscount: &AddressTypeToHeightToAddressCount,
) -> Result<()> {
self.p2pk65.derive_from(
indexes,
starting_indexes,
&addresstype_to_height_to_addresscount.p2pk65,
exit,
)?;
self.p2pk33.derive_from(
indexes,
starting_indexes,
&addresstype_to_height_to_addresscount.p2pk33,
exit,
)?;
self.p2pkh.derive_from(
indexes,
starting_indexes,
&addresstype_to_height_to_addresscount.p2pkh,
exit,
)?;
self.p2sh.derive_from(
indexes,
starting_indexes,
&addresstype_to_height_to_addresscount.p2sh,
exit,
)?;
self.p2wpkh.derive_from(
indexes,
starting_indexes,
&addresstype_to_height_to_addresscount.p2wpkh,
exit,
)?;
self.p2wsh.derive_from(
indexes,
starting_indexes,
&addresstype_to_height_to_addresscount.p2wsh,
exit,
)?;
self.p2tr.derive_from(
indexes,
starting_indexes,
&addresstype_to_height_to_addresscount.p2tr,
exit,
)?;
self.p2a.derive_from(
indexes,
starting_indexes,
&addresstype_to_height_to_addresscount.p2a,
exit,
)?;
self.p2pk65.compute_rest(indexes, starting_indexes, exit)?;
self.p2pk33.compute_rest(indexes, starting_indexes, exit)?;
self.p2pkh.compute_rest(indexes, starting_indexes, exit)?;
self.p2sh.compute_rest(indexes, starting_indexes, exit)?;
self.p2wpkh.compute_rest(indexes, starting_indexes, exit)?;
self.p2wsh.compute_rest(indexes, starting_indexes, exit)?;
self.p2tr.compute_rest(indexes, starting_indexes, exit)?;
self.p2a.compute_rest(indexes, starting_indexes, exit)?;
Ok(())
}
pub fn by_height(&self) -> Vec<&EagerVec<PcoVec<Height, StoredU64>>> {
vec![
&self.p2pk65.height,
&self.p2pk33.height,
&self.p2pkh.height,
&self.p2sh.height,
&self.p2wpkh.height,
&self.p2wsh.height,
&self.p2tr.height,
&self.p2a.height,
]
}
}
#[derive(Clone, Traversable)]
pub struct AddrCountVecs {
pub all: ComputedBlockLast<StoredU64>,
pub by_addresstype: AddressTypeToAddrCountVecs,
}
impl AddrCountVecs {
pub fn forced_import(
db: &Database,
name: &str,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
Ok(Self {
all: ComputedBlockLast::forced_import(db, name, version, indexes)?,
by_addresstype: AddressTypeToAddrCountVecs::forced_import(db, name, version, indexes)?,
})
}
pub fn min_len(&self) -> usize {
self.all.height.len().min(self.by_addresstype.min_len())
}
pub fn par_iter_height_mut(&mut self) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
rayon::iter::once(&mut self.all.height as &mut dyn AnyStoredVec)
.chain(self.by_addresstype.par_iter_height_mut())
}
pub fn reset_height(&mut self) -> Result<()> {
self.all.height.reset()?;
self.by_addresstype.reset_height()?;
Ok(())
}
pub fn truncate_push_height(
&mut self,
height: Height,
total: u64,
addr_counts: &AddressTypeToAddressCount,
) -> Result<()> {
self.all.height.truncate_push(height, total.into())?;
self.by_addresstype
.truncate_push_height(height, addr_counts)?;
Ok(())
}
pub fn compute_rest(
&mut self,
indexes: &indexes::Vecs,
starting_indexes: &ComputeIndexes,
exit: &Exit,
) -> Result<()> {
self.by_addresstype
.compute_rest(indexes, starting_indexes, exit)?;
let sources = self.by_addresstype.by_height();
self.all
.compute_all(indexes, starting_indexes, exit, |height_vec| {
Ok(height_vec.compute_sum_of_others(starting_indexes.height, &sources, exit)?)
})?;
Ok(())
}
}

View File

@@ -3,10 +3,7 @@ mod data;
mod indexes;
mod type_map;
pub use address_count::{
AddressTypeToAddressCount, AddressTypeToHeightToAddressCount,
AddressTypeToIndexesToAddressCount,
};
pub use address_count::{AddrCountVecs, AddressTypeToAddressCount};
pub use data::AddressesDataVecs;
pub use indexes::AnyAddressIndexesVecs;
pub use type_map::{AddressTypeToTypeIndexMap, AddressTypeToVec, HeightToAddressTypeToVec};

View File

@@ -5,16 +5,13 @@ use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{DateIndex, Dollars, Height, StoredU64, Version};
use rayon::prelude::*;
use vecdb::{
AnyStoredVec, AnyVec, Database, EagerVec, Exit, GenericStoredVec, ImportableVec,
IterableCloneableVec, IterableVec, PcoVec,
};
use vecdb::{AnyStoredVec, AnyVec, Database, Exit, GenericStoredVec, IterableVec};
use crate::{
ComputeIndexes,
distribution::state::AddressCohortState,
indexes,
internal::DerivedComputedBlockLast,
internal::ComputedBlockLast,
price,
};
@@ -38,11 +35,7 @@ pub struct AddressCohortVecs {
#[traversable(flatten)]
pub metrics: CohortMetrics,
/// Address count at each height
pub height_to_addr_count: EagerVec<PcoVec<Height, StoredU64>>,
/// Address count indexed by various dimensions
pub indexes_to_addr_count: DerivedComputedBlockLast<StoredU64>,
pub addr_count: ComputedBlockLast<StoredU64>,
}
impl AddressCohortVecs {
@@ -75,9 +68,6 @@ impl AddressCohortVecs {
up_to_1h_realized: None,
};
let height_to_addr_count =
EagerVec::forced_import(db, &cfg.name("addr_count"), version + VERSION)?;
Ok(Self {
starting_height: None,
@@ -86,14 +76,12 @@ impl AddressCohortVecs {
metrics: CohortMetrics::forced_import(&cfg, all_supply)?,
indexes_to_addr_count: DerivedComputedBlockLast::forced_import(
addr_count: ComputedBlockLast::forced_import(
db,
&cfg.name("addr_count"),
height_to_addr_count.boxed_clone(),
version + VERSION,
indexes,
)?,
height_to_addr_count,
})
}
@@ -114,7 +102,7 @@ impl AddressCohortVecs {
/// Returns a parallel iterator over all vecs for parallel writing.
pub fn par_iter_vecs_mut(&mut self) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
rayon::iter::once(&mut self.height_to_addr_count as &mut dyn AnyStoredVec)
rayon::iter::once(&mut self.addr_count.height as &mut dyn AnyStoredVec)
.chain(self.metrics.par_iter_mut())
}
@@ -135,7 +123,8 @@ impl Filtered for AddressCohortVecs {
impl DynCohortVecs for AddressCohortVecs {
fn min_stateful_height_len(&self) -> usize {
self.height_to_addr_count
self.addr_count
.height
.len()
.min(self.metrics.min_stateful_height_len())
}
@@ -166,21 +155,25 @@ impl DynCohortVecs for AddressCohortVecs {
state.inner.supply.value = self
.metrics
.supply
.height_to_supply
.supply
.sats
.height
.read_once(prev_height)?;
state.inner.supply.utxo_count = *self
.metrics
.supply
.height_to_utxo_count
.outputs
.utxo_count
.height
.read_once(prev_height)?;
state.addr_count = *self.height_to_addr_count.read_once(prev_height)?;
state.addr_count = *self.addr_count.height.read_once(prev_height)?;
// Restore realized cap if present
if let Some(realized_metrics) = self.metrics.realized.as_mut()
&& let Some(realized_state) = state.inner.realized.as_mut()
{
realized_state.cap = realized_metrics
.height_to_realized_cap
.realized_cap
.height
.read_once(prev_height)?;
}
@@ -200,7 +193,8 @@ impl DynCohortVecs for AddressCohortVecs {
fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> {
use vecdb::GenericStoredVec;
self.height_to_addr_count
self.addr_count
.height
.validate_computed_version_or_reset(base_version)?;
self.metrics.validate_computed_versions(base_version)?;
Ok(())
@@ -213,7 +207,8 @@ impl DynCohortVecs for AddressCohortVecs {
// Push addr_count from state
if let Some(state) = self.state.as_ref() {
self.height_to_addr_count
self.addr_count
.height
.truncate_push(height, state.addr_count.into())?;
self.metrics.truncate_push(height, &state.inner)?;
}
@@ -247,12 +242,8 @@ impl DynCohortVecs for AddressCohortVecs {
starting_indexes: &ComputeIndexes,
exit: &Exit,
) -> Result<()> {
self.indexes_to_addr_count.derive_from(
indexes,
starting_indexes,
&self.height_to_addr_count,
exit,
)?;
self.addr_count
.compute_rest(indexes, starting_indexes, exit)?;
self.metrics
.compute_rest_part1(indexes, price, starting_indexes, exit)?;
Ok(())
@@ -266,11 +257,11 @@ impl CohortVecs for AddressCohortVecs {
others: &[&Self],
exit: &Exit,
) -> Result<()> {
self.height_to_addr_count.compute_sum_of_others(
self.addr_count.height.compute_sum_of_others(
starting_indexes.height,
others
.iter()
.map(|v| &v.height_to_addr_count)
.map(|v| &v.addr_count.height)
.collect::<Vec<_>>()
.as_slice(),
exit,

View File

@@ -149,12 +149,15 @@ impl DynCohortVecs for UTXOCohortVecs {
state.supply.value = self
.metrics
.supply
.height_to_supply
.supply
.sats
.height
.read_once(prev_height)?;
state.supply.utxo_count = *self
.metrics
.supply
.height_to_utxo_count
.outputs
.utxo_count
.height
.read_once(prev_height)?;
// Restore realized cap if present
@@ -162,7 +165,8 @@ impl DynCohortVecs for UTXOCohortVecs {
&& let Some(realized_state) = state.realized.as_mut()
{
realized_state.cap = realized_metrics
.height_to_realized_cap
.realized_cap
.height
.read_once(prev_height)?;
}

View File

@@ -1,6 +1,6 @@
use brk_error::Result;
use brk_types::{DateIndex, Dollars, Height};
use log::info;
use tracing::info;
use vecdb::{Exit, IterableVec};
use crate::{ComputeIndexes, indexes, price};

View File

@@ -4,12 +4,12 @@ use brk_cohort::ByAddressType;
use brk_error::Result;
use brk_indexer::Indexer;
use brk_types::{DateIndex, Height, OutputType, Sats, TxIndex, TypeIndex};
use log::info;
use rayon::prelude::*;
use tracing::info;
use vecdb::{Exit, IterableVec, TypedVecIterator, VecIndex};
use crate::{
blocks, transactions, indexes, price,
blocks,
distribution::{
address::AddressTypeToAddressCount,
block::{
@@ -19,7 +19,7 @@ use crate::{
compute::write::{process_address_updates, write},
state::{BlockState, Transacted},
},
inputs, outputs,
indexes, inputs, outputs, price, transactions,
};
use super::{
@@ -58,25 +58,25 @@ pub fn process_blocks(
// References to vectors using correct field paths
// From indexer.vecs:
let height_to_first_txindex = &indexer.vecs.tx.height_to_first_txindex;
let height_to_first_txoutindex = &indexer.vecs.txout.height_to_first_txoutindex;
let height_to_first_txinindex = &indexer.vecs.txin.height_to_first_txinindex;
let height_to_first_txindex = &indexer.vecs.transactions.first_txindex;
let height_to_first_txoutindex = &indexer.vecs.outputs.first_txoutindex;
let height_to_first_txinindex = &indexer.vecs.inputs.first_txinindex;
// From transactions and inputs/outputs (via .height or .height.sum_cum.sum patterns):
let height_to_tx_count = &transactions.count.indexes_to_tx_count.height;
let height_to_output_count = &outputs.count.indexes_to_count.height.sum_cum.sum.0;
let height_to_input_count = &inputs.count.indexes_to_count.height.sum_cum.sum.0;
let height_to_tx_count = &transactions.count.tx_count.height;
let height_to_output_count = &outputs.count.total_count.height.sum_cum.sum.0;
let height_to_input_count = &inputs.count.height.sum_cum.sum.0;
// From blocks:
let height_to_timestamp = &blocks.time.height_to_timestamp_fixed;
let height_to_date = &blocks.time.height_to_date_fixed;
let dateindex_to_first_height = &indexes.time.dateindex_to_first_height;
let dateindex_to_height_count = &indexes.time.dateindex_to_height_count;
let txindex_to_output_count = &indexes.transaction.txindex_to_output_count;
let txindex_to_input_count = &indexes.transaction.txindex_to_input_count;
let height_to_timestamp = &blocks.time.timestamp_fixed;
let height_to_date = &blocks.time.date_fixed;
let dateindex_to_first_height = &indexes.dateindex.first_height;
let dateindex_to_height_count = &indexes.dateindex.height_count;
let txindex_to_output_count = &indexes.txindex.output_count;
let txindex_to_input_count = &indexes.txindex.input_count;
// From price (optional):
let height_to_price = price.map(|p| &p.usd.chainindexes_to_price_close.height);
let dateindex_to_price = price.map(|p| &p.usd.timeindexes_to_price_close.dateindex);
let height_to_price = price.map(|p| &p.usd.split.close.height);
let dateindex_to_price = price.map(|p| &p.usd.split.close.dateindex);
// Access pre-computed vectors from context for thread-safe access
let height_to_price_vec = &ctx.height_to_price;
@@ -103,7 +103,7 @@ pub fn process_blocks(
// Build txindex -> height lookup map for efficient prev_height computation
let mut txindex_to_height: RangeMap<TxIndex, Height> = {
let mut map = RangeMap::with_capacity(last_height.to_usize() + 1);
for first_txindex in indexer.vecs.tx.height_to_first_txindex.into_iter() {
for first_txindex in indexer.vecs.transactions.first_txindex.into_iter() {
map.push(first_txindex);
}
map
@@ -114,65 +114,30 @@ pub fn process_blocks(
let mut txin_iters = TxInIterators::new(indexer, inputs, &mut txindex_to_height);
// Create iterators for first address indexes per type
let mut first_p2a_iter = indexer
.vecs
.address
.height_to_first_p2aaddressindex
.into_iter();
let mut first_p2pk33_iter = indexer
.vecs
.address
.height_to_first_p2pk33addressindex
.into_iter();
let mut first_p2pk65_iter = indexer
.vecs
.address
.height_to_first_p2pk65addressindex
.into_iter();
let mut first_p2pkh_iter = indexer
.vecs
.address
.height_to_first_p2pkhaddressindex
.into_iter();
let mut first_p2sh_iter = indexer
.vecs
.address
.height_to_first_p2shaddressindex
.into_iter();
let mut first_p2tr_iter = indexer
.vecs
.address
.height_to_first_p2traddressindex
.into_iter();
let mut first_p2wpkh_iter = indexer
.vecs
.address
.height_to_first_p2wpkhaddressindex
.into_iter();
let mut first_p2wsh_iter = indexer
.vecs
.address
.height_to_first_p2wshaddressindex
.into_iter();
let mut first_p2a_iter = indexer.vecs.addresses.first_p2aaddressindex.into_iter();
let mut first_p2pk33_iter = indexer.vecs.addresses.first_p2pk33addressindex.into_iter();
let mut first_p2pk65_iter = indexer.vecs.addresses.first_p2pk65addressindex.into_iter();
let mut first_p2pkh_iter = indexer.vecs.addresses.first_p2pkhaddressindex.into_iter();
let mut first_p2sh_iter = indexer.vecs.addresses.first_p2shaddressindex.into_iter();
let mut first_p2tr_iter = indexer.vecs.addresses.first_p2traddressindex.into_iter();
let mut first_p2wpkh_iter = indexer.vecs.addresses.first_p2wpkhaddressindex.into_iter();
let mut first_p2wsh_iter = indexer.vecs.addresses.first_p2wshaddressindex.into_iter();
// Track running totals - recover from previous height if resuming
let (mut addresstype_to_addr_count, mut addresstype_to_empty_addr_count) =
if starting_height > Height::ZERO {
let addr_count = AddressTypeToAddressCount::from((
&vecs.addresstype_to_height_to_addr_count,
starting_height,
));
let empty_addr_count = AddressTypeToAddressCount::from((
&vecs.addresstype_to_height_to_empty_addr_count,
starting_height,
));
(addr_count, empty_addr_count)
} else {
(
AddressTypeToAddressCount::default(),
AddressTypeToAddressCount::default(),
)
};
let (mut addr_counts, mut empty_addr_counts) = if starting_height > Height::ZERO {
let addr_counts =
AddressTypeToAddressCount::from((&vecs.addr_count.by_addresstype, starting_height));
let empty_addr_counts = AddressTypeToAddressCount::from((
&vecs.empty_addr_count.by_addresstype,
starting_height,
));
(addr_counts, empty_addr_counts)
} else {
(
AddressTypeToAddressCount::default(),
AddressTypeToAddressCount::default(),
)
};
let mut cache = AddressCache::new();
@@ -333,8 +298,8 @@ pub fn process_blocks(
&mut vecs.address_cohorts,
&mut lookup,
block_price,
&mut addresstype_to_addr_count,
&mut addresstype_to_empty_addr_count,
&mut addr_counts,
&mut empty_addr_counts,
);
// Process sent inputs (addresses sending funds)
@@ -344,8 +309,8 @@ pub fn process_blocks(
&mut vecs.address_cohorts,
&mut lookup,
block_price,
&mut addresstype_to_addr_count,
&mut addresstype_to_empty_addr_count,
&mut addr_counts,
&mut empty_addr_counts,
height_to_price_vec.as_deref(),
height_to_timestamp_vec,
height,
@@ -361,10 +326,13 @@ pub fn process_blocks(
});
// Push to height-indexed vectors
vecs.addresstype_to_height_to_addr_count
.truncate_push(height, &addresstype_to_addr_count)?;
vecs.addresstype_to_height_to_empty_addr_count
.truncate_push(height, &addresstype_to_empty_addr_count)?;
vecs.addr_count
.truncate_push_height(height, addr_counts.sum(), &addr_counts)?;
vecs.empty_addr_count.truncate_push_height(
height,
empty_addr_counts.sum(),
&empty_addr_counts,
)?;
// Get date info for unrealized state computation
let date = height_to_date_iter.get_unwrap(height);

View File

@@ -26,11 +26,10 @@ impl ComputeContext {
blocks: &blocks::Vecs,
price: Option<&price::Vecs>,
) -> Self {
let height_to_timestamp: Vec<Timestamp> =
blocks.time.height_to_timestamp_fixed.into_iter().collect();
let height_to_timestamp: Vec<Timestamp> = blocks.time.timestamp_fixed.into_iter().collect();
let height_to_price: Option<Vec<Dollars>> = price
.map(|p| &p.usd.chainindexes_to_price_close.height)
.map(|p| &p.usd.split.close.height)
.map(|v| v.into_iter().map(|d| *d).collect());
Self {

View File

@@ -37,9 +37,9 @@ pub struct TxOutIterators<'a> {
impl<'a> TxOutIterators<'a> {
pub fn new(indexer: &'a Indexer) -> Self {
Self {
value_iter: indexer.vecs.txout.txoutindex_to_value.into_iter(),
outputtype_iter: indexer.vecs.txout.txoutindex_to_outputtype.into_iter(),
typeindex_iter: indexer.vecs.txout.txoutindex_to_typeindex.into_iter(),
value_iter: indexer.vecs.outputs.value.into_iter(),
outputtype_iter: indexer.vecs.outputs.outputtype.into_iter(),
typeindex_iter: indexer.vecs.outputs.typeindex.into_iter(),
}
}
@@ -75,10 +75,10 @@ impl<'a> TxInIterators<'a> {
txindex_to_height: &'a mut RangeMap<TxIndex, Height>,
) -> Self {
Self {
value_iter: txins.spent.txinindex_to_value.into_iter(),
outpoint_iter: indexer.vecs.txin.txinindex_to_outpoint.into_iter(),
outputtype_iter: indexer.vecs.txin.txinindex_to_outputtype.into_iter(),
typeindex_iter: indexer.vecs.txin.txinindex_to_typeindex.into_iter(),
value_iter: txins.spent.value.into_iter(),
outpoint_iter: indexer.vecs.inputs.outpoint.into_iter(),
outputtype_iter: indexer.vecs.inputs.outputtype.into_iter(),
typeindex_iter: indexer.vecs.inputs.typeindex.into_iter(),
txindex_to_height,
}
}

View File

@@ -95,15 +95,18 @@ pub fn reset_state(
}
/// Check if we can resume from a checkpoint or need to start fresh.
pub fn determine_start_mode(computed_min: Height, chain_state_height: Height) -> StartMode {
///
/// - `min_available`: minimum height we have data for across all stateful vecs
/// - `resume_target`: the height we want to resume processing from
pub fn determine_start_mode(min_available: Height, resume_target: Height) -> StartMode {
// No data to resume from
if chain_state_height.is_zero() {
if resume_target.is_zero() {
return StartMode::Fresh;
}
match computed_min.cmp(&chain_state_height) {
Ordering::Greater => unreachable!("min height > chain state height"),
Ordering::Equal => StartMode::Resume(chain_state_height),
match min_available.cmp(&resume_target) {
Ordering::Greater => unreachable!("min_available > resume_target"),
Ordering::Equal => StartMode::Resume(resume_target),
Ordering::Less => StartMode::Fresh,
}
}

View File

@@ -2,8 +2,8 @@ use std::time::Instant;
use brk_error::Result;
use brk_types::Height;
use log::info;
use rayon::prelude::*;
use tracing::info;
use vecdb::{AnyStoredVec, GenericStoredVec, Stamp};
use crate::distribution::{
@@ -77,11 +77,8 @@ pub fn write(
vecs.any_address_indexes
.par_iter_mut()
.chain(vecs.addresses_data.par_iter_mut())
.chain(vecs.addresstype_to_height_to_addr_count.par_iter_mut())
.chain(
vecs.addresstype_to_height_to_empty_addr_count
.par_iter_mut(),
)
.chain(vecs.addr_count.par_iter_height_mut())
.chain(vecs.empty_addr_count.par_iter_height_mut())
.chain(rayon::iter::once(
&mut vecs.chain_state as &mut dyn AnyStoredVec,
))

View File

@@ -2,14 +2,11 @@ use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Bitcoin, Height, Sats, StoredF64, Version};
use rayon::prelude::*;
use vecdb::{
AnyStoredVec, AnyVec, EagerVec, Exit, GenericStoredVec, ImportableVec, IterableCloneableVec,
PcoVec,
};
use vecdb::{AnyStoredVec, AnyVec, EagerVec, Exit, GenericStoredVec, ImportableVec, PcoVec};
use crate::{
ComputeIndexes, indexes,
internal::{ComputedBlockSumCum, DerivedValueBlockSumCum},
internal::{ComputedBlockSumCum, LazyComputedValueBlockSumCum},
};
use super::ImportConfig;
@@ -17,63 +14,54 @@ use super::ImportConfig;
/// Activity metrics for a cohort.
#[derive(Clone, Traversable)]
pub struct ActivityMetrics {
/// Total satoshis sent at each height
pub height_to_sent: EagerVec<PcoVec<Height, Sats>>,
/// Sent amounts indexed by various dimensions (derives from height_to_sent)
pub indexes_to_sent: DerivedValueBlockSumCum,
/// Total satoshis sent at each height + derived indexes
pub sent: LazyComputedValueBlockSumCum,
/// Satoshi-blocks destroyed (supply * blocks_old when spent)
pub height_to_satblocks_destroyed: EagerVec<PcoVec<Height, Sats>>,
pub satblocks_destroyed: EagerVec<PcoVec<Height, Sats>>,
/// Satoshi-days destroyed (supply * days_old when spent)
pub height_to_satdays_destroyed: EagerVec<PcoVec<Height, Sats>>,
pub satdays_destroyed: EagerVec<PcoVec<Height, Sats>>,
/// Coin-blocks destroyed (in BTC rather than sats)
pub indexes_to_coinblocks_destroyed: ComputedBlockSumCum<StoredF64>,
pub coinblocks_destroyed: ComputedBlockSumCum<StoredF64>,
/// Coin-days destroyed (in BTC rather than sats)
pub indexes_to_coindays_destroyed: ComputedBlockSumCum<StoredF64>,
pub coindays_destroyed: ComputedBlockSumCum<StoredF64>,
}
impl ActivityMetrics {
/// Import activity metrics from database.
pub fn forced_import(cfg: &ImportConfig) -> Result<Self> {
let height_to_sent: EagerVec<PcoVec<Height, Sats>> =
EagerVec::forced_import(cfg.db, &cfg.name("sent"), cfg.version)?;
let indexes_to_sent = DerivedValueBlockSumCum::forced_import(
cfg.db,
&cfg.name("sent"),
cfg.version,
cfg.indexes,
height_to_sent.boxed_clone(),
cfg.price,
)?;
Ok(Self {
height_to_sent,
indexes_to_sent,
sent: LazyComputedValueBlockSumCum::forced_import(
cfg.db,
&cfg.name("sent"),
cfg.version,
cfg.indexes,
cfg.price,
)?,
height_to_satblocks_destroyed: EagerVec::forced_import(
satblocks_destroyed: EagerVec::forced_import(
cfg.db,
&cfg.name("satblocks_destroyed"),
cfg.version,
)?,
height_to_satdays_destroyed: EagerVec::forced_import(
satdays_destroyed: EagerVec::forced_import(
cfg.db,
&cfg.name("satdays_destroyed"),
cfg.version,
)?,
indexes_to_coinblocks_destroyed: ComputedBlockSumCum::forced_import(
coinblocks_destroyed: ComputedBlockSumCum::forced_import(
cfg.db,
&cfg.name("coinblocks_destroyed"),
cfg.version,
cfg.indexes,
)?,
indexes_to_coindays_destroyed: ComputedBlockSumCum::forced_import(
coindays_destroyed: ComputedBlockSumCum::forced_import(
cfg.db,
&cfg.name("coindays_destroyed"),
cfg.version,
@@ -84,10 +72,12 @@ impl ActivityMetrics {
/// Get minimum length across height-indexed vectors.
pub fn min_len(&self) -> usize {
self.height_to_sent
self.sent
.sats
.height
.len()
.min(self.height_to_satblocks_destroyed.len())
.min(self.height_to_satdays_destroyed.len())
.min(self.satblocks_destroyed.len())
.min(self.satdays_destroyed.len())
}
/// Push activity state values to height-indexed vectors.
@@ -98,28 +88,28 @@ impl ActivityMetrics {
satblocks_destroyed: Sats,
satdays_destroyed: Sats,
) -> Result<()> {
self.height_to_sent.truncate_push(height, sent)?;
self.height_to_satblocks_destroyed
self.sent.sats.height.truncate_push(height, sent)?;
self.satblocks_destroyed
.truncate_push(height, satblocks_destroyed)?;
self.height_to_satdays_destroyed
self.satdays_destroyed
.truncate_push(height, satdays_destroyed)?;
Ok(())
}
/// Write height-indexed vectors to disk.
pub fn write(&mut self) -> Result<()> {
self.height_to_sent.write()?;
self.height_to_satblocks_destroyed.write()?;
self.height_to_satdays_destroyed.write()?;
self.sent.sats.height.write()?;
self.satblocks_destroyed.write()?;
self.satdays_destroyed.write()?;
Ok(())
}
/// Returns a parallel iterator over all vecs for parallel writing.
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
vec![
&mut self.height_to_sent as &mut dyn AnyStoredVec,
&mut self.height_to_satblocks_destroyed as &mut dyn AnyStoredVec,
&mut self.height_to_satdays_destroyed as &mut dyn AnyStoredVec,
&mut self.sent.sats.height as &mut dyn AnyStoredVec,
&mut self.satblocks_destroyed as &mut dyn AnyStoredVec,
&mut self.satdays_destroyed as &mut dyn AnyStoredVec,
]
.into_par_iter()
}
@@ -137,24 +127,27 @@ impl ActivityMetrics {
others: &[&Self],
exit: &Exit,
) -> Result<()> {
self.height_to_sent.compute_sum_of_others(
starting_indexes.height,
&others.iter().map(|v| &v.height_to_sent).collect::<Vec<_>>(),
exit,
)?;
self.height_to_satblocks_destroyed.compute_sum_of_others(
self.sent.sats.height.compute_sum_of_others(
starting_indexes.height,
&others
.iter()
.map(|v| &v.height_to_satblocks_destroyed)
.map(|v| &v.sent.sats.height)
.collect::<Vec<_>>(),
exit,
)?;
self.height_to_satdays_destroyed.compute_sum_of_others(
self.satblocks_destroyed.compute_sum_of_others(
starting_indexes.height,
&others
.iter()
.map(|v| &v.height_to_satdays_destroyed)
.map(|v| &v.satblocks_destroyed)
.collect::<Vec<_>>(),
exit,
)?;
self.satdays_destroyed.compute_sum_of_others(
starting_indexes.height,
&others
.iter()
.map(|v| &v.satdays_destroyed)
.collect::<Vec<_>>(),
exit,
)?;
@@ -168,29 +161,24 @@ impl ActivityMetrics {
starting_indexes: &ComputeIndexes,
exit: &Exit,
) -> Result<()> {
self.indexes_to_sent.derive_from(
indexes,
starting_indexes,
&self.height_to_sent,
exit,
)?;
self.sent.compute_rest(indexes, starting_indexes, exit)?;
self.indexes_to_coinblocks_destroyed
self.coinblocks_destroyed
.compute_all(indexes, starting_indexes, exit, |v| {
v.compute_transform(
starting_indexes.height,
&self.height_to_satblocks_destroyed,
&self.satblocks_destroyed,
|(i, v, ..)| (i, StoredF64::from(Bitcoin::from(v))),
exit,
)?;
Ok(())
})?;
self.indexes_to_coindays_destroyed
self.coindays_destroyed
.compute_all(indexes, starting_indexes, exit, |v| {
v.compute_transform(
starting_indexes.height,
&self.height_to_satdays_destroyed,
&self.satdays_destroyed,
|(i, v, ..)| (i, StoredF64::from(Bitcoin::from(v))),
exit,
)?;

View File

@@ -2,15 +2,13 @@ use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{DateIndex, Dollars, Height, Version};
use rayon::prelude::*;
use vecdb::{
AnyStoredVec, AnyVec, EagerVec, Exit, GenericStoredVec, ImportableVec, IterableCloneableVec,
PcoVec,
};
use vecdb::{AnyStoredVec, AnyVec, Exit, GenericStoredVec};
use crate::{
ComputeIndexes,
distribution::state::CohortState,
internal::{CostBasisPercentiles, DerivedComputedBlockLast},
indexes,
internal::{ComputedBlockLast, CostBasisPercentiles},
};
use super::ImportConfig;
@@ -19,12 +17,10 @@ use super::ImportConfig;
#[derive(Clone, Traversable)]
pub struct CostBasisMetrics {
/// Minimum cost basis for any UTXO at this height
pub height_to_min_cost_basis: EagerVec<PcoVec<Height, Dollars>>,
pub indexes_to_min_cost_basis: DerivedComputedBlockLast<Dollars>,
pub min: ComputedBlockLast<Dollars>,
/// Maximum cost basis for any UTXO at this height
pub height_to_max_cost_basis: EagerVec<PcoVec<Height, Dollars>>,
pub indexes_to_max_cost_basis: DerivedComputedBlockLast<Dollars>,
pub max: ComputedBlockLast<Dollars>,
/// Cost basis distribution percentiles (median, quartiles, etc.)
pub percentiles: Option<CostBasisPercentiles>,
@@ -35,29 +31,19 @@ impl CostBasisMetrics {
pub fn forced_import(cfg: &ImportConfig) -> Result<Self> {
let extended = cfg.extended();
let height_to_min_cost_basis =
EagerVec::forced_import(cfg.db, &cfg.name("min_cost_basis"), cfg.version)?;
let height_to_max_cost_basis =
EagerVec::forced_import(cfg.db, &cfg.name("max_cost_basis"), cfg.version)?;
Ok(Self {
indexes_to_min_cost_basis: DerivedComputedBlockLast::forced_import(
min: ComputedBlockLast::forced_import(
cfg.db,
&cfg.name("min_cost_basis"),
height_to_min_cost_basis.boxed_clone(),
cfg.version,
cfg.indexes,
)?,
indexes_to_max_cost_basis: DerivedComputedBlockLast::forced_import(
max: ComputedBlockLast::forced_import(
cfg.db,
&cfg.name("max_cost_basis"),
height_to_max_cost_basis.boxed_clone(),
cfg.version,
cfg.indexes,
)?,
height_to_min_cost_basis,
height_to_max_cost_basis,
percentiles: extended
.then(|| {
CostBasisPercentiles::forced_import(
@@ -74,9 +60,7 @@ impl CostBasisMetrics {
/// Get minimum length across height-indexed vectors written in block loop.
pub fn min_stateful_height_len(&self) -> usize {
self.height_to_min_cost_basis
.len()
.min(self.height_to_max_cost_basis.len())
self.min.height.len().min(self.max.height.len())
}
/// Get minimum length across dateindex-indexed vectors written in block loop.
@@ -89,14 +73,14 @@ impl CostBasisMetrics {
/// Push min/max cost basis from state.
pub fn truncate_push_minmax(&mut self, height: Height, state: &CohortState) -> Result<()> {
self.height_to_min_cost_basis.truncate_push(
self.min.height.truncate_push(
height,
state
.price_to_amount_first_key_value()
.map(|(dollars, _)| dollars)
.unwrap_or(Dollars::NAN),
)?;
self.height_to_max_cost_basis.truncate_push(
self.max.height.truncate_push(
height,
state
.price_to_amount_last_key_value()
@@ -122,8 +106,8 @@ impl CostBasisMetrics {
/// Write height-indexed vectors to disk.
pub fn write(&mut self) -> Result<()> {
self.height_to_min_cost_basis.write()?;
self.height_to_max_cost_basis.write()?;
self.min.height.write()?;
self.max.height.write()?;
if let Some(percentiles) = self.percentiles.as_mut() {
percentiles.write()?;
}
@@ -132,10 +116,7 @@ impl CostBasisMetrics {
/// Returns a parallel iterator over all vecs for parallel writing.
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
let mut vecs: Vec<&mut dyn AnyStoredVec> = vec![
&mut self.height_to_min_cost_basis,
&mut self.height_to_max_cost_basis,
];
let mut vecs: Vec<&mut dyn AnyStoredVec> = vec![&mut self.min.height, &mut self.max.height];
if let Some(percentiles) = self.percentiles.as_mut() {
vecs.extend(
percentiles
@@ -163,20 +144,14 @@ impl CostBasisMetrics {
others: &[&Self],
exit: &Exit,
) -> Result<()> {
self.height_to_min_cost_basis.compute_min_of_others(
self.min.height.compute_min_of_others(
starting_indexes.height,
&others
.iter()
.map(|v| &v.height_to_min_cost_basis)
.collect::<Vec<_>>(),
&others.iter().map(|v| &v.min.height).collect::<Vec<_>>(),
exit,
)?;
self.height_to_max_cost_basis.compute_max_of_others(
self.max.height.compute_max_of_others(
starting_indexes.height,
&others
.iter()
.map(|v| &v.height_to_max_cost_basis)
.collect::<Vec<_>>(),
&others.iter().map(|v| &v.max.height).collect::<Vec<_>>(),
exit,
)?;
Ok(())
@@ -185,24 +160,12 @@ impl CostBasisMetrics {
/// First phase of computed metrics (indexes from height).
pub fn compute_rest_part1(
&mut self,
indexes: &crate::indexes::Vecs,
indexes: &indexes::Vecs,
starting_indexes: &ComputeIndexes,
exit: &Exit,
) -> Result<()> {
self.indexes_to_min_cost_basis.derive_from(
indexes,
starting_indexes,
&self.height_to_min_cost_basis,
exit,
)?;
self.indexes_to_max_cost_basis.derive_from(
indexes,
starting_indexes,
&self.height_to_max_cost_basis,
exit,
)?;
self.min.compute_rest(indexes, starting_indexes, exit)?;
self.max.compute_rest(indexes, starting_indexes, exit)?;
Ok(())
}
}

View File

@@ -1,6 +1,7 @@
mod activity;
mod config;
mod cost_basis;
mod outputs;
mod realized;
mod relative;
mod supply;
@@ -9,6 +10,7 @@ mod unrealized;
pub use activity::*;
pub use config::*;
pub use cost_basis::*;
pub use outputs::*;
pub use realized::*;
pub use relative::*;
pub use supply::*;
@@ -29,9 +31,12 @@ pub struct CohortMetrics {
#[traversable(skip)]
pub filter: Filter,
/// Supply and UTXO count (always computed)
/// Supply metrics (always computed)
pub supply: SupplyMetrics,
/// Output metrics - UTXO count (always computed)
pub outputs: OutputsMetrics,
/// Transaction activity (always computed)
pub activity: ActivityMetrics,
@@ -58,6 +63,7 @@ impl CohortMetrics {
let compute_dollars = cfg.compute_dollars();
let supply = SupplyMetrics::forced_import(cfg)?;
let outputs = OutputsMetrics::forced_import(cfg)?;
let unrealized = compute_dollars
.then(|| UnrealizedMetrics::forced_import(cfg))
@@ -71,6 +77,7 @@ impl CohortMetrics {
Ok(Self {
filter: cfg.filter.clone(),
supply,
outputs,
activity: ActivityMetrics::forced_import(cfg)?,
realized: compute_dollars
.then(|| RealizedMetrics::forced_import(cfg))
@@ -85,7 +92,7 @@ impl CohortMetrics {
/// Get minimum length across height-indexed vectors written in block loop.
pub fn min_stateful_height_len(&self) -> usize {
let mut min = self.supply.min_len().min(self.activity.min_len());
let mut min = self.supply.min_len().min(self.outputs.min_len()).min(self.activity.min_len());
if let Some(realized) = &self.realized {
min = min.min(realized.min_stateful_height_len());
@@ -116,7 +123,8 @@ impl CohortMetrics {
/// Push state values to height-indexed vectors.
pub fn truncate_push(&mut self, height: Height, state: &CohortState) -> Result<()> {
self.supply.truncate_push(height, &state.supply)?;
self.supply.truncate_push(height, state.supply.value)?;
self.outputs.truncate_push(height, state.supply.utxo_count)?;
self.activity.truncate_push(
height,
state.sent,
@@ -136,6 +144,7 @@ impl CohortMetrics {
/// Write height-indexed vectors to disk.
pub fn write(&mut self) -> Result<()> {
self.supply.write()?;
self.outputs.write()?;
self.activity.write()?;
if let Some(realized) = self.realized.as_mut() {
@@ -158,6 +167,7 @@ impl CohortMetrics {
let mut vecs: Vec<&mut dyn AnyStoredVec> = Vec::new();
vecs.extend(self.supply.par_iter_mut().collect::<Vec<_>>());
vecs.extend(self.outputs.par_iter_mut().collect::<Vec<_>>());
vecs.extend(self.activity.par_iter_mut().collect::<Vec<_>>());
if let Some(realized) = self.realized.as_mut() {
@@ -242,6 +252,11 @@ impl CohortMetrics {
&others.iter().map(|v| &v.supply).collect::<Vec<_>>(),
exit,
)?;
self.outputs.compute_from_stateful(
starting_indexes,
&others.iter().map(|v| &v.outputs).collect::<Vec<_>>(),
exit,
)?;
self.activity.compute_from_stateful(
starting_indexes,
&others.iter().map(|v| &v.activity).collect::<Vec<_>>(),
@@ -294,6 +309,8 @@ impl CohortMetrics {
) -> Result<()> {
self.supply
.compute_rest_part1(indexes, price, starting_indexes, exit)?;
self.outputs
.compute_rest(indexes, starting_indexes, exit)?;
self.activity
.compute_rest_part1(indexes, starting_indexes, exit)?;
@@ -328,7 +345,7 @@ impl CohortMetrics {
indexes,
price,
starting_indexes,
&self.supply.height_to_supply_value.bitcoin,
&self.supply.supply.bitcoin.height,
height_to_market_cap,
dateindex_to_market_cap,
exit,

View File

@@ -0,0 +1,81 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Height, StoredU64};
use rayon::prelude::*;
use vecdb::{AnyStoredVec, AnyVec, Exit, GenericStoredVec};
use crate::{ComputeIndexes, indexes, internal::ComputedBlockLast};
use super::ImportConfig;
/// Output metrics for a cohort.
#[derive(Clone, Traversable)]
pub struct OutputsMetrics {
pub utxo_count: ComputedBlockLast<StoredU64>,
}
impl OutputsMetrics {
/// Import output metrics from database.
pub fn forced_import(cfg: &ImportConfig) -> Result<Self> {
Ok(Self {
utxo_count: ComputedBlockLast::forced_import(
cfg.db,
&cfg.name("utxo_count"),
cfg.version,
cfg.indexes,
)?,
})
}
/// Get minimum length across height-indexed vectors.
pub fn min_len(&self) -> usize {
self.utxo_count.height.len()
}
/// Push utxo count to height-indexed vector.
pub fn truncate_push(&mut self, height: Height, utxo_count: u64) -> Result<()> {
self.utxo_count
.height
.truncate_push(height, StoredU64::from(utxo_count))?;
Ok(())
}
/// Write height-indexed vectors to disk.
pub fn write(&mut self) -> Result<()> {
self.utxo_count.height.write()?;
Ok(())
}
/// Returns a parallel iterator over all vecs for parallel writing.
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
vec![&mut self.utxo_count.height as &mut dyn AnyStoredVec].into_par_iter()
}
/// Compute aggregate values from separate cohorts.
pub fn compute_from_stateful(
&mut self,
starting_indexes: &ComputeIndexes,
others: &[&Self],
exit: &Exit,
) -> Result<()> {
self.utxo_count.height.compute_sum_of_others(
starting_indexes.height,
&others
.iter()
.map(|v| &v.utxo_count.height)
.collect::<Vec<_>>(),
exit,
)?;
Ok(())
}
/// Compute derived metrics (dateindex from height).
pub fn compute_rest(
&mut self,
indexes: &indexes::Vecs,
starting_indexes: &ComputeIndexes,
exit: &Exit,
) -> Result<()> {
self.utxo_count.compute_rest(indexes, starting_indexes, exit)
}
}

View File

@@ -12,10 +12,9 @@ use crate::{
distribution::state::RealizedState,
indexes,
internal::{
BinaryBlockSum, BinaryBlockSumCumLast, ComputedBlockLast, ComputedBlockSum,
ComputedBlockSumCum, ComputedDateLast, ComputedRatioVecsDate, DerivedComputedBlockLast,
DerivedComputedBlockSum, DerivedComputedBlockSumCum, DollarsMinus, LazyBlockSum,
LazyBlockSumCum, LazyDateLast, PercentageDollarsF32, StoredF32Identity,
BinaryBlockSum, BinaryBlockSumCum, ComputedBlockLast, ComputedBlockSum,
ComputedBlockSumCum, ComputedDateLast, ComputedRatioVecsDate, DollarsMinus,
LazyBlockSum, LazyBlockSumCum, LazyDateLast, PercentageDollarsF32, StoredF32Identity,
},
price,
};
@@ -26,69 +25,57 @@ use super::ImportConfig;
#[derive(Clone, Traversable)]
pub struct RealizedMetrics {
// === Realized Cap ===
pub height_to_realized_cap: EagerVec<PcoVec<Height, Dollars>>,
pub indexes_to_realized_cap: DerivedComputedBlockLast<Dollars>,
pub indexes_to_realized_price: ComputedBlockLast<Dollars>,
pub indexes_to_realized_price_extra: ComputedRatioVecsDate,
pub indexes_to_realized_cap_rel_to_own_market_cap: Option<ComputedBlockLast<StoredF32>>,
pub indexes_to_realized_cap_30d_delta: ComputedDateLast<Dollars>,
pub realized_cap: ComputedBlockLast<Dollars>,
pub realized_price: ComputedBlockLast<Dollars>,
pub realized_price_extra: ComputedRatioVecsDate,
pub realized_cap_rel_to_own_market_cap: Option<ComputedBlockLast<StoredF32>>,
pub realized_cap_30d_delta: ComputedDateLast<Dollars>,
// === MVRV (Market Value to Realized Value) ===
// Proxy for indexes_to_realized_price_extra.ratio (close / realized_price = market_cap / realized_cap)
pub indexes_to_mvrv: LazyDateLast<StoredF32>,
// Proxy for realized_price_extra.ratio (close / realized_price = market_cap / realized_cap)
pub mvrv: LazyDateLast<StoredF32>,
// === Realized Profit/Loss ===
pub height_to_realized_profit: EagerVec<PcoVec<Height, Dollars>>,
pub indexes_to_realized_profit: DerivedComputedBlockSumCum<Dollars>,
pub height_to_realized_loss: EagerVec<PcoVec<Height, Dollars>>,
pub indexes_to_realized_loss: DerivedComputedBlockSumCum<Dollars>,
pub indexes_to_neg_realized_loss: LazyBlockSumCum<Dollars>,
pub indexes_to_net_realized_pnl: ComputedBlockSumCum<Dollars>,
pub indexes_to_realized_value: ComputedBlockSum<Dollars>,
pub realized_profit: ComputedBlockSumCum<Dollars>,
pub realized_loss: ComputedBlockSumCum<Dollars>,
pub neg_realized_loss: LazyBlockSumCum<Dollars>,
pub net_realized_pnl: ComputedBlockSumCum<Dollars>,
pub realized_value: ComputedBlockSum<Dollars>,
// === Realized vs Realized Cap Ratios (lazy) ===
pub indexes_to_realized_profit_rel_to_realized_cap:
BinaryBlockSumCumLast<StoredF32, Dollars, Dollars>,
pub indexes_to_realized_loss_rel_to_realized_cap:
BinaryBlockSumCumLast<StoredF32, Dollars, Dollars>,
pub indexes_to_net_realized_pnl_rel_to_realized_cap:
BinaryBlockSumCumLast<StoredF32, Dollars, Dollars>,
pub realized_profit_rel_to_realized_cap: BinaryBlockSumCum<StoredF32, Dollars, Dollars>,
pub realized_loss_rel_to_realized_cap: BinaryBlockSumCum<StoredF32, Dollars, Dollars>,
pub net_realized_pnl_rel_to_realized_cap: BinaryBlockSumCum<StoredF32, Dollars, Dollars>,
// === Total Realized PnL ===
pub indexes_to_total_realized_pnl: LazyBlockSum<Dollars>,
pub dateindex_to_realized_profit_to_loss_ratio: Option<EagerVec<PcoVec<DateIndex, StoredF64>>>,
pub total_realized_pnl: LazyBlockSum<Dollars>,
pub realized_profit_to_loss_ratio: Option<EagerVec<PcoVec<DateIndex, StoredF64>>>,
// === Value Created/Destroyed ===
pub height_to_value_created: EagerVec<PcoVec<Height, Dollars>>,
#[traversable(rename = "value_created_sum")]
pub indexes_to_value_created: DerivedComputedBlockSum<Dollars>,
pub height_to_value_destroyed: EagerVec<PcoVec<Height, Dollars>>,
#[traversable(rename = "value_destroyed_sum")]
pub indexes_to_value_destroyed: DerivedComputedBlockSum<Dollars>,
pub value_created: ComputedBlockSum<Dollars>,
pub value_destroyed: ComputedBlockSum<Dollars>,
// === Adjusted Value (lazy: cohort - up_to_1h) ===
pub indexes_to_adjusted_value_created: Option<BinaryBlockSum<Dollars, Dollars, Dollars>>,
pub indexes_to_adjusted_value_destroyed: Option<BinaryBlockSum<Dollars, Dollars, Dollars>>,
pub adjusted_value_created: Option<BinaryBlockSum<Dollars, Dollars, Dollars>>,
pub adjusted_value_destroyed: Option<BinaryBlockSum<Dollars, Dollars, Dollars>>,
// === SOPR (Spent Output Profit Ratio) ===
pub dateindex_to_sopr: EagerVec<PcoVec<DateIndex, StoredF64>>,
pub dateindex_to_sopr_7d_ema: EagerVec<PcoVec<DateIndex, StoredF64>>,
pub dateindex_to_sopr_30d_ema: EagerVec<PcoVec<DateIndex, StoredF64>>,
pub dateindex_to_adjusted_sopr: Option<EagerVec<PcoVec<DateIndex, StoredF64>>>,
pub dateindex_to_adjusted_sopr_7d_ema: Option<EagerVec<PcoVec<DateIndex, StoredF64>>>,
pub dateindex_to_adjusted_sopr_30d_ema: Option<EagerVec<PcoVec<DateIndex, StoredF64>>>,
pub sopr: EagerVec<PcoVec<DateIndex, StoredF64>>,
pub sopr_7d_ema: EagerVec<PcoVec<DateIndex, StoredF64>>,
pub sopr_30d_ema: EagerVec<PcoVec<DateIndex, StoredF64>>,
pub adjusted_sopr: Option<EagerVec<PcoVec<DateIndex, StoredF64>>>,
pub adjusted_sopr_7d_ema: Option<EagerVec<PcoVec<DateIndex, StoredF64>>>,
pub adjusted_sopr_30d_ema: Option<EagerVec<PcoVec<DateIndex, StoredF64>>>,
// === Sell Side Risk ===
pub dateindex_to_sell_side_risk_ratio: EagerVec<PcoVec<DateIndex, StoredF32>>,
pub dateindex_to_sell_side_risk_ratio_7d_ema: EagerVec<PcoVec<DateIndex, StoredF32>>,
pub dateindex_to_sell_side_risk_ratio_30d_ema: EagerVec<PcoVec<DateIndex, StoredF32>>,
pub sell_side_risk_ratio: EagerVec<PcoVec<DateIndex, StoredF32>>,
pub sell_side_risk_ratio_7d_ema: EagerVec<PcoVec<DateIndex, StoredF32>>,
pub sell_side_risk_ratio_30d_ema: EagerVec<PcoVec<DateIndex, StoredF32>>,
// === Net Realized PnL Deltas ===
pub indexes_to_net_realized_pnl_cumulative_30d_delta: ComputedDateLast<Dollars>,
pub indexes_to_net_realized_pnl_cumulative_30d_delta_rel_to_realized_cap:
ComputedDateLast<StoredF32>,
pub indexes_to_net_realized_pnl_cumulative_30d_delta_rel_to_market_cap:
ComputedDateLast<StoredF32>,
pub net_realized_pnl_cumulative_30d_delta: ComputedDateLast<Dollars>,
pub net_realized_pnl_cumulative_30d_delta_rel_to_realized_cap: ComputedDateLast<StoredF32>,
pub net_realized_pnl_cumulative_30d_delta_rel_to_market_cap: ComputedDateLast<StoredF32>,
}
impl RealizedMetrics {
@@ -99,26 +86,44 @@ impl RealizedMetrics {
let extended = cfg.extended();
let compute_adjusted = cfg.compute_adjusted();
let height_to_realized_loss: EagerVec<PcoVec<Height, Dollars>> =
EagerVec::forced_import(cfg.db, &cfg.name("realized_loss"), cfg.version)?;
let indexes_to_realized_loss = DerivedComputedBlockSumCum::forced_import(
// Import combined types using forced_import which handles height + derived
let realized_cap = ComputedBlockLast::forced_import(
cfg.db,
&cfg.name("realized_loss"),
height_to_realized_loss.boxed_clone(),
&cfg.name("realized_cap"),
cfg.version,
cfg.indexes,
)?;
let indexes_to_neg_realized_loss = LazyBlockSumCum::from_derived::<Negate>(
let realized_profit = ComputedBlockSumCum::forced_import(
cfg.db,
&cfg.name("realized_profit"),
cfg.version,
cfg.indexes,
)?;
let realized_loss = ComputedBlockSumCum::forced_import(
cfg.db,
&cfg.name("realized_loss"),
cfg.version,
cfg.indexes,
)?;
let neg_realized_loss = LazyBlockSumCum::from_computed::<Negate>(
&cfg.name("neg_realized_loss"),
cfg.version + v1,
height_to_realized_loss.boxed_clone(),
&indexes_to_realized_loss,
realized_loss.height.boxed_clone(),
&realized_loss,
);
let net_realized_pnl = ComputedBlockSumCum::forced_import(
cfg.db,
&cfg.name("net_realized_pnl"),
cfg.version,
cfg.indexes,
)?;
// realized_value is the source for total_realized_pnl (they're identical)
let indexes_to_realized_value = ComputedBlockSum::forced_import(
let realized_value = ComputedBlockSum::forced_import(
cfg.db,
&cfg.name("realized_value"),
cfg.version,
@@ -126,132 +131,92 @@ impl RealizedMetrics {
)?;
// total_realized_pnl is a lazy alias to realized_value
let indexes_to_total_realized_pnl = LazyBlockSum::from_computed::<Ident>(
let total_realized_pnl = LazyBlockSum::from_computed::<Ident>(
&cfg.name("total_realized_pnl"),
cfg.version + v1,
indexes_to_realized_value.height.boxed_clone(),
&indexes_to_realized_value,
realized_value.height.boxed_clone(),
&realized_value,
);
// Extract vecs needed for lazy ratio construction
let height_to_realized_cap: EagerVec<PcoVec<Height, Dollars>> =
EagerVec::forced_import(cfg.db, &cfg.name("realized_cap"), cfg.version)?;
let indexes_to_realized_cap = DerivedComputedBlockLast::forced_import(
cfg.db,
&cfg.name("realized_cap"),
height_to_realized_cap.boxed_clone(),
cfg.version,
cfg.indexes,
)?;
let height_to_realized_profit: EagerVec<PcoVec<Height, Dollars>> =
EagerVec::forced_import(cfg.db, &cfg.name("realized_profit"), cfg.version)?;
let indexes_to_realized_profit = DerivedComputedBlockSumCum::forced_import(
cfg.db,
&cfg.name("realized_profit"),
height_to_realized_profit.boxed_clone(),
cfg.version,
cfg.indexes,
)?;
let indexes_to_net_realized_pnl = ComputedBlockSumCum::forced_import(
cfg.db,
&cfg.name("net_realized_pnl"),
cfg.version,
cfg.indexes,
)?;
// Construct lazy ratio vecs (before struct assignment to satisfy borrow checker)
let indexes_to_realized_profit_rel_to_realized_cap =
BinaryBlockSumCumLast::from_derived::<PercentageDollarsF32>(
// Construct lazy ratio vecs
let realized_profit_rel_to_realized_cap =
BinaryBlockSumCum::from_computed_last::<PercentageDollarsF32>(
&cfg.name("realized_profit_rel_to_realized_cap"),
cfg.version + v1,
height_to_realized_profit.boxed_clone(),
height_to_realized_cap.boxed_clone(),
&indexes_to_realized_profit,
&indexes_to_realized_cap,
realized_profit.height.boxed_clone(),
realized_cap.height.boxed_clone(),
&realized_profit,
&realized_cap,
);
let indexes_to_realized_loss_rel_to_realized_cap =
BinaryBlockSumCumLast::from_derived::<PercentageDollarsF32>(
let realized_loss_rel_to_realized_cap =
BinaryBlockSumCum::from_computed_last::<PercentageDollarsF32>(
&cfg.name("realized_loss_rel_to_realized_cap"),
cfg.version + v1,
height_to_realized_loss.boxed_clone(),
height_to_realized_cap.boxed_clone(),
&indexes_to_realized_loss,
&indexes_to_realized_cap,
realized_loss.height.boxed_clone(),
realized_cap.height.boxed_clone(),
&realized_loss,
&realized_cap,
);
let indexes_to_net_realized_pnl_rel_to_realized_cap =
BinaryBlockSumCumLast::from_computed_derived::<PercentageDollarsF32>(
let net_realized_pnl_rel_to_realized_cap =
BinaryBlockSumCum::from_computed_last::<PercentageDollarsF32>(
&cfg.name("net_realized_pnl_rel_to_realized_cap"),
cfg.version + v1,
indexes_to_net_realized_pnl.height.boxed_clone(),
height_to_realized_cap.boxed_clone(),
&indexes_to_net_realized_pnl,
&indexes_to_realized_cap,
net_realized_pnl.height.boxed_clone(),
realized_cap.height.boxed_clone(),
&net_realized_pnl,
&realized_cap,
);
let indexes_to_realized_price = ComputedBlockLast::forced_import(
let realized_price = ComputedBlockLast::forced_import(
cfg.db,
&cfg.name("realized_price"),
cfg.version + v1,
cfg.indexes,
)?;
let height_to_value_created =
EagerVec::forced_import(cfg.db, &cfg.name("value_created"), cfg.version)?;
let height_to_value_destroyed =
EagerVec::forced_import(cfg.db, &cfg.name("value_destroyed"), cfg.version)?;
let indexes_to_value_created = DerivedComputedBlockSum::forced_import(
let value_created = ComputedBlockSum::forced_import(
cfg.db,
&cfg.name("value_created"),
height_to_value_created.boxed_clone(),
cfg.version,
cfg.indexes,
)?;
let indexes_to_value_destroyed = DerivedComputedBlockSum::forced_import(
let value_destroyed = ComputedBlockSum::forced_import(
cfg.db,
&cfg.name("value_destroyed"),
height_to_value_destroyed.boxed_clone(),
cfg.version,
cfg.indexes,
)?;
// Create lazy adjusted vecs if compute_adjusted and up_to_1h is available
let indexes_to_adjusted_value_created =
let adjusted_value_created =
(compute_adjusted && cfg.up_to_1h_realized.is_some()).then(|| {
let up_to_1h = cfg.up_to_1h_realized.unwrap();
BinaryBlockSum::from_derived::<DollarsMinus>(
BinaryBlockSum::from_computed::<DollarsMinus>(
&cfg.name("adjusted_value_created"),
cfg.version,
height_to_value_created.boxed_clone(),
up_to_1h.height_to_value_created.boxed_clone(),
&indexes_to_value_created,
&up_to_1h.indexes_to_value_created,
&value_created,
&up_to_1h.value_created,
)
});
let indexes_to_adjusted_value_destroyed =
let adjusted_value_destroyed =
(compute_adjusted && cfg.up_to_1h_realized.is_some()).then(|| {
let up_to_1h = cfg.up_to_1h_realized.unwrap();
BinaryBlockSum::from_derived::<DollarsMinus>(
BinaryBlockSum::from_computed::<DollarsMinus>(
&cfg.name("adjusted_value_destroyed"),
cfg.version,
height_to_value_destroyed.boxed_clone(),
up_to_1h.height_to_value_destroyed.boxed_clone(),
&indexes_to_value_destroyed,
&up_to_1h.indexes_to_value_destroyed,
&value_destroyed,
&up_to_1h.value_destroyed,
)
});
// Create realized_price_extra first so we can reference its ratio for MVRV proxy
let indexes_to_realized_price_extra = ComputedRatioVecsDate::forced_import(
let realized_price_extra = ComputedRatioVecsDate::forced_import(
cfg.db,
&cfg.name("realized_price"),
Some(&indexes_to_realized_price),
Some(&realized_price),
cfg.version + v1,
cfg.indexes,
extended,
@@ -260,21 +225,18 @@ impl RealizedMetrics {
// MVRV is a lazy proxy for realized_price_extra.ratio
// ratio = close / realized_price = market_cap / realized_cap = MVRV
let indexes_to_mvrv = LazyDateLast::from_source::<StoredF32Identity>(
let mvrv = LazyDateLast::from_source::<StoredF32Identity>(
&cfg.name("mvrv"),
cfg.version,
&indexes_to_realized_price_extra.ratio,
&realized_price_extra.ratio,
);
Ok(Self {
// === Realized Cap ===
height_to_realized_cap,
indexes_to_realized_cap,
indexes_to_realized_price_extra,
indexes_to_realized_price,
indexes_to_mvrv,
indexes_to_realized_cap_rel_to_own_market_cap: extended
realized_cap,
realized_price,
realized_price_extra,
realized_cap_rel_to_own_market_cap: extended
.then(|| {
ComputedBlockLast::forced_import(
cfg.db,
@@ -284,30 +246,31 @@ impl RealizedMetrics {
)
})
.transpose()?,
indexes_to_realized_cap_30d_delta: ComputedDateLast::forced_import(
realized_cap_30d_delta: ComputedDateLast::forced_import(
cfg.db,
&cfg.name("realized_cap_30d_delta"),
cfg.version,
cfg.indexes,
)?,
// === MVRV ===
mvrv,
// === Realized Profit/Loss ===
height_to_realized_profit,
indexes_to_realized_profit,
height_to_realized_loss,
indexes_to_realized_loss,
indexes_to_neg_realized_loss,
indexes_to_net_realized_pnl,
indexes_to_realized_value,
realized_profit,
realized_loss,
neg_realized_loss,
net_realized_pnl,
realized_value,
// === Realized vs Realized Cap Ratios (lazy) ===
indexes_to_realized_profit_rel_to_realized_cap,
indexes_to_realized_loss_rel_to_realized_cap,
indexes_to_net_realized_pnl_rel_to_realized_cap,
realized_profit_rel_to_realized_cap,
realized_loss_rel_to_realized_cap,
net_realized_pnl_rel_to_realized_cap,
// === Total Realized PnL ===
indexes_to_total_realized_pnl,
dateindex_to_realized_profit_to_loss_ratio: extended
total_realized_pnl,
realized_profit_to_loss_ratio: extended
.then(|| {
EagerVec::forced_import(
cfg.db,
@@ -318,37 +281,27 @@ impl RealizedMetrics {
.transpose()?,
// === Value Created/Destroyed ===
height_to_value_created,
indexes_to_value_created,
height_to_value_destroyed,
indexes_to_value_destroyed,
value_created,
value_destroyed,
// === Adjusted Value (lazy: cohort - up_to_1h) ===
indexes_to_adjusted_value_created,
indexes_to_adjusted_value_destroyed,
adjusted_value_created,
adjusted_value_destroyed,
// === SOPR ===
dateindex_to_sopr: EagerVec::forced_import(
cfg.db,
&cfg.name("sopr"),
cfg.version + v1,
)?,
dateindex_to_sopr_7d_ema: EagerVec::forced_import(
cfg.db,
&cfg.name("sopr_7d_ema"),
cfg.version + v1,
)?,
dateindex_to_sopr_30d_ema: EagerVec::forced_import(
sopr: EagerVec::forced_import(cfg.db, &cfg.name("sopr"), cfg.version + v1)?,
sopr_7d_ema: EagerVec::forced_import(cfg.db, &cfg.name("sopr_7d_ema"), cfg.version + v1)?,
sopr_30d_ema: EagerVec::forced_import(
cfg.db,
&cfg.name("sopr_30d_ema"),
cfg.version + v1,
)?,
dateindex_to_adjusted_sopr: compute_adjusted
adjusted_sopr: compute_adjusted
.then(|| {
EagerVec::forced_import(cfg.db, &cfg.name("adjusted_sopr"), cfg.version + v1)
})
.transpose()?,
dateindex_to_adjusted_sopr_7d_ema: compute_adjusted
adjusted_sopr_7d_ema: compute_adjusted
.then(|| {
EagerVec::forced_import(
cfg.db,
@@ -357,7 +310,7 @@ impl RealizedMetrics {
)
})
.transpose()?,
dateindex_to_adjusted_sopr_30d_ema: compute_adjusted
adjusted_sopr_30d_ema: compute_adjusted
.then(|| {
EagerVec::forced_import(
cfg.db,
@@ -368,37 +321,37 @@ impl RealizedMetrics {
.transpose()?,
// === Sell Side Risk ===
dateindex_to_sell_side_risk_ratio: EagerVec::forced_import(
sell_side_risk_ratio: EagerVec::forced_import(
cfg.db,
&cfg.name("sell_side_risk_ratio"),
cfg.version + v1,
)?,
dateindex_to_sell_side_risk_ratio_7d_ema: EagerVec::forced_import(
sell_side_risk_ratio_7d_ema: EagerVec::forced_import(
cfg.db,
&cfg.name("sell_side_risk_ratio_7d_ema"),
cfg.version + v1,
)?,
dateindex_to_sell_side_risk_ratio_30d_ema: EagerVec::forced_import(
sell_side_risk_ratio_30d_ema: EagerVec::forced_import(
cfg.db,
&cfg.name("sell_side_risk_ratio_30d_ema"),
cfg.version + v1,
)?,
// === Net Realized PnL Deltas ===
indexes_to_net_realized_pnl_cumulative_30d_delta: ComputedDateLast::forced_import(
net_realized_pnl_cumulative_30d_delta: ComputedDateLast::forced_import(
cfg.db,
&cfg.name("net_realized_pnl_cumulative_30d_delta"),
cfg.version + v3,
cfg.indexes,
)?,
indexes_to_net_realized_pnl_cumulative_30d_delta_rel_to_realized_cap:
net_realized_pnl_cumulative_30d_delta_rel_to_realized_cap:
ComputedDateLast::forced_import(
cfg.db,
&cfg.name("net_realized_pnl_cumulative_30d_delta_rel_to_realized_cap"),
cfg.version + v3,
cfg.indexes,
)?,
indexes_to_net_realized_pnl_cumulative_30d_delta_rel_to_market_cap:
net_realized_pnl_cumulative_30d_delta_rel_to_market_cap:
ComputedDateLast::forced_import(
cfg.db,
&cfg.name("net_realized_pnl_cumulative_30d_delta_rel_to_market_cap"),
@@ -410,25 +363,29 @@ impl RealizedMetrics {
/// Get minimum length across height-indexed vectors written in block loop.
pub fn min_stateful_height_len(&self) -> usize {
self.height_to_realized_cap
self.realized_cap
.height
.len()
.min(self.height_to_realized_profit.len())
.min(self.height_to_realized_loss.len())
.min(self.height_to_value_created.len())
.min(self.height_to_value_destroyed.len())
.min(self.realized_profit.height.len())
.min(self.realized_loss.height.len())
.min(self.value_created.height.len())
.min(self.value_destroyed.height.len())
}
/// Push realized state values to height-indexed vectors.
pub fn truncate_push(&mut self, height: Height, state: &RealizedState) -> Result<()> {
self.height_to_realized_cap
.truncate_push(height, state.cap)?;
self.height_to_realized_profit
self.realized_cap.height.truncate_push(height, state.cap)?;
self.realized_profit
.height
.truncate_push(height, state.profit)?;
self.height_to_realized_loss
self.realized_loss
.height
.truncate_push(height, state.loss)?;
self.height_to_value_created
self.value_created
.height
.truncate_push(height, state.value_created)?;
self.height_to_value_destroyed
self.value_destroyed
.height
.truncate_push(height, state.value_destroyed)?;
Ok(())
@@ -436,22 +393,22 @@ impl RealizedMetrics {
/// Write height-indexed vectors to disk.
pub fn write(&mut self) -> Result<()> {
self.height_to_realized_cap.write()?;
self.height_to_realized_profit.write()?;
self.height_to_realized_loss.write()?;
self.height_to_value_created.write()?;
self.height_to_value_destroyed.write()?;
self.realized_cap.height.write()?;
self.realized_profit.height.write()?;
self.realized_loss.height.write()?;
self.value_created.height.write()?;
self.value_destroyed.height.write()?;
Ok(())
}
/// Returns a parallel iterator over all vecs for parallel writing.
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
[
&mut self.height_to_realized_cap as &mut dyn AnyStoredVec,
&mut self.height_to_realized_profit,
&mut self.height_to_realized_loss,
&mut self.height_to_value_created,
&mut self.height_to_value_destroyed,
&mut self.realized_cap.height as &mut dyn AnyStoredVec,
&mut self.realized_profit.height,
&mut self.realized_loss.height,
&mut self.value_created.height,
&mut self.value_destroyed.height,
]
.into_par_iter()
}
@@ -469,43 +426,43 @@ impl RealizedMetrics {
others: &[&Self],
exit: &Exit,
) -> Result<()> {
self.height_to_realized_cap.compute_sum_of_others(
self.realized_cap.height.compute_sum_of_others(
starting_indexes.height,
&others
.iter()
.map(|v| &v.height_to_realized_cap)
.map(|v| &v.realized_cap.height)
.collect::<Vec<_>>(),
exit,
)?;
self.height_to_realized_profit.compute_sum_of_others(
self.realized_profit.height.compute_sum_of_others(
starting_indexes.height,
&others
.iter()
.map(|v| &v.height_to_realized_profit)
.map(|v| &v.realized_profit.height)
.collect::<Vec<_>>(),
exit,
)?;
self.height_to_realized_loss.compute_sum_of_others(
self.realized_loss.height.compute_sum_of_others(
starting_indexes.height,
&others
.iter()
.map(|v| &v.height_to_realized_loss)
.map(|v| &v.realized_loss.height)
.collect::<Vec<_>>(),
exit,
)?;
self.height_to_value_created.compute_sum_of_others(
self.value_created.height.compute_sum_of_others(
starting_indexes.height,
&others
.iter()
.map(|v| &v.height_to_value_created)
.map(|v| &v.value_created.height)
.collect::<Vec<_>>(),
exit,
)?;
self.height_to_value_destroyed.compute_sum_of_others(
self.value_destroyed.height.compute_sum_of_others(
starting_indexes.height,
&others
.iter()
.map(|v| &v.height_to_value_destroyed)
.map(|v| &v.value_destroyed.height)
.collect::<Vec<_>>(),
exit,
)?;
@@ -520,34 +477,17 @@ impl RealizedMetrics {
starting_indexes: &ComputeIndexes,
exit: &Exit,
) -> Result<()> {
self.indexes_to_realized_cap.derive_from(
indexes,
starting_indexes,
&self.height_to_realized_cap,
exit,
)?;
self.indexes_to_realized_profit.derive_from(
indexes,
starting_indexes,
&self.height_to_realized_profit,
exit,
)?;
self.indexes_to_realized_loss.derive_from(
indexes,
starting_indexes,
&self.height_to_realized_loss,
exit,
)?;
self.realized_cap.compute_rest(indexes, starting_indexes, exit)?;
self.realized_profit.compute_rest(indexes, starting_indexes, exit)?;
self.realized_loss.compute_rest(indexes, starting_indexes, exit)?;
// net_realized_pnl = profit - loss
self.indexes_to_net_realized_pnl
self.net_realized_pnl
.compute_all(indexes, starting_indexes, exit, |vec| {
vec.compute_subtract(
starting_indexes.height,
&self.height_to_realized_profit,
&self.height_to_realized_loss,
&self.realized_profit.height,
&self.realized_loss.height,
exit,
)?;
Ok(())
@@ -556,30 +496,19 @@ impl RealizedMetrics {
// realized_value = profit + loss
// Note: total_realized_pnl is a lazy alias to realized_value since both
// compute profit + loss with sum aggregation, making them identical.
self.indexes_to_realized_value
self.realized_value
.compute_all(indexes, starting_indexes, exit, |vec| {
vec.compute_add(
starting_indexes.height,
&self.height_to_realized_profit,
&self.height_to_realized_loss,
&self.realized_profit.height,
&self.realized_loss.height,
exit,
)?;
Ok(())
})?;
self.indexes_to_value_created.derive_from(
indexes,
starting_indexes,
&self.height_to_value_created,
exit,
)?;
self.indexes_to_value_destroyed.derive_from(
indexes,
starting_indexes,
&self.height_to_value_destroyed,
exit,
)?;
self.value_created.compute_rest(indexes, starting_indexes, exit)?;
self.value_destroyed.compute_rest(indexes, starting_indexes, exit)?;
Ok(())
}
@@ -597,11 +526,11 @@ impl RealizedMetrics {
exit: &Exit,
) -> Result<()> {
// realized_price = realized_cap / supply
self.indexes_to_realized_price
self.realized_price
.compute_all(indexes, starting_indexes, exit, |vec| {
vec.compute_divide(
starting_indexes.height,
&self.height_to_realized_cap,
&self.realized_cap.height,
height_to_supply,
exit,
)?;
@@ -609,20 +538,20 @@ impl RealizedMetrics {
})?;
if let Some(price) = price {
self.indexes_to_realized_price_extra.compute_rest(
self.realized_price_extra.compute_rest(
price,
starting_indexes,
exit,
Some(&self.indexes_to_realized_price.dateindex.0),
Some(&self.realized_price.dateindex.0),
)?;
}
// realized_cap_30d_delta
self.indexes_to_realized_cap_30d_delta
self.realized_cap_30d_delta
.compute_all(starting_indexes, exit, |vec| {
vec.compute_change(
starting_indexes.dateindex,
&self.indexes_to_realized_cap.dateindex.0,
&self.realized_cap.dateindex.0,
30,
exit,
)?;
@@ -630,32 +559,24 @@ impl RealizedMetrics {
})?;
// SOPR = value_created / value_destroyed
self.dateindex_to_sopr.compute_divide(
self.sopr.compute_divide(
starting_indexes.dateindex,
&self.indexes_to_value_created.dateindex.0,
&self.indexes_to_value_destroyed.dateindex.0,
&self.value_created.dateindex.0,
&self.value_destroyed.dateindex.0,
exit,
)?;
self.dateindex_to_sopr_7d_ema.compute_ema(
starting_indexes.dateindex,
&self.dateindex_to_sopr,
7,
exit,
)?;
self.sopr_7d_ema
.compute_ema(starting_indexes.dateindex, &self.sopr, 7, exit)?;
self.dateindex_to_sopr_30d_ema.compute_ema(
starting_indexes.dateindex,
&self.dateindex_to_sopr,
30,
exit,
)?;
self.sopr_30d_ema
.compute_ema(starting_indexes.dateindex, &self.sopr, 30, exit)?;
// Optional: adjusted SOPR (lazy: cohort - up_to_1h)
if let (Some(adjusted_sopr), Some(adj_created), Some(adj_destroyed)) = (
self.dateindex_to_adjusted_sopr.as_mut(),
self.indexes_to_adjusted_value_created.as_ref(),
self.indexes_to_adjusted_value_destroyed.as_ref(),
self.adjusted_sopr.as_mut(),
self.adjusted_value_created.as_ref(),
self.adjusted_value_destroyed.as_ref(),
) {
adjusted_sopr.compute_divide(
starting_indexes.dateindex,
@@ -664,19 +585,19 @@ impl RealizedMetrics {
exit,
)?;
if let Some(ema_7d) = self.dateindex_to_adjusted_sopr_7d_ema.as_mut() {
if let Some(ema_7d) = self.adjusted_sopr_7d_ema.as_mut() {
ema_7d.compute_ema(
starting_indexes.dateindex,
self.dateindex_to_adjusted_sopr.as_ref().unwrap(),
self.adjusted_sopr.as_ref().unwrap(),
7,
exit,
)?;
}
if let Some(ema_30d) = self.dateindex_to_adjusted_sopr_30d_ema.as_mut() {
if let Some(ema_30d) = self.adjusted_sopr_30d_ema.as_mut() {
ema_30d.compute_ema(
starting_indexes.dateindex,
self.dateindex_to_adjusted_sopr.as_ref().unwrap(),
self.adjusted_sopr.as_ref().unwrap(),
30,
exit,
)?;
@@ -684,33 +605,29 @@ impl RealizedMetrics {
}
// sell_side_risk_ratio = realized_value / realized_cap
self.dateindex_to_sell_side_risk_ratio.compute_percentage(
self.sell_side_risk_ratio.compute_percentage(
starting_indexes.dateindex,
&self.indexes_to_realized_value.dateindex.0,
&self.indexes_to_realized_cap.dateindex.0,
&self.realized_value.dateindex.0,
&self.realized_cap.dateindex.0,
exit,
)?;
self.dateindex_to_sell_side_risk_ratio_7d_ema.compute_ema(
starting_indexes.dateindex,
&self.dateindex_to_sell_side_risk_ratio,
7,
exit,
)?;
self.sell_side_risk_ratio_7d_ema
.compute_ema(starting_indexes.dateindex, &self.sell_side_risk_ratio, 7, exit)?;
self.dateindex_to_sell_side_risk_ratio_30d_ema.compute_ema(
self.sell_side_risk_ratio_30d_ema.compute_ema(
starting_indexes.dateindex,
&self.dateindex_to_sell_side_risk_ratio,
&self.sell_side_risk_ratio,
30,
exit,
)?;
// Net realized PnL cumulative 30d delta
self.indexes_to_net_realized_pnl_cumulative_30d_delta
self.net_realized_pnl_cumulative_30d_delta
.compute_all(starting_indexes, exit, |vec| {
vec.compute_change(
starting_indexes.dateindex,
&self.indexes_to_net_realized_pnl.dateindex.cumulative.0,
&self.net_realized_pnl.dateindex.cumulative.0,
30,
exit,
)?;
@@ -718,14 +635,12 @@ impl RealizedMetrics {
})?;
// Relative to realized cap
self.indexes_to_net_realized_pnl_cumulative_30d_delta_rel_to_realized_cap
self.net_realized_pnl_cumulative_30d_delta_rel_to_realized_cap
.compute_all(starting_indexes, exit, |vec| {
vec.compute_percentage(
starting_indexes.dateindex,
&self
.indexes_to_net_realized_pnl_cumulative_30d_delta
.dateindex,
&self.indexes_to_realized_cap.dateindex.0,
&self.net_realized_pnl_cumulative_30d_delta.dateindex,
&self.realized_cap.dateindex.0,
exit,
)?;
Ok(())
@@ -733,13 +648,11 @@ impl RealizedMetrics {
// Relative to market cap
if let Some(dateindex_to_market_cap) = dateindex_to_market_cap {
self.indexes_to_net_realized_pnl_cumulative_30d_delta_rel_to_market_cap
self.net_realized_pnl_cumulative_30d_delta_rel_to_market_cap
.compute_all(starting_indexes, exit, |vec| {
vec.compute_percentage(
starting_indexes.dateindex,
&self
.indexes_to_net_realized_pnl_cumulative_30d_delta
.dateindex,
&self.net_realized_pnl_cumulative_30d_delta.dateindex,
dateindex_to_market_cap,
exit,
)?;
@@ -749,13 +662,13 @@ impl RealizedMetrics {
// Optional: realized_cap_rel_to_own_market_cap
if let (Some(rel_vec), Some(height_to_market_cap)) = (
self.indexes_to_realized_cap_rel_to_own_market_cap.as_mut(),
self.realized_cap_rel_to_own_market_cap.as_mut(),
height_to_market_cap,
) {
rel_vec.compute_all(indexes, starting_indexes, exit, |vec| {
vec.compute_percentage(
starting_indexes.height,
&self.height_to_realized_cap,
&self.realized_cap.height,
height_to_market_cap,
exit,
)?;
@@ -764,11 +677,11 @@ impl RealizedMetrics {
}
// Optional: realized_profit_to_loss_ratio
if let Some(ratio) = self.dateindex_to_realized_profit_to_loss_ratio.as_mut() {
if let Some(ratio) = self.realized_profit_to_loss_ratio.as_mut() {
ratio.compute_divide(
starting_indexes.dateindex,
&self.indexes_to_realized_profit.dateindex.sum.0,
&self.indexes_to_realized_loss.dateindex.sum.0,
&self.realized_profit.dateindex.sum.0,
&self.realized_loss.dateindex.sum.0,
exit,
)?;
}

View File

@@ -1,10 +1,10 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Bitcoin, Dollars, Height, Sats, StoredF32, StoredF64, Version};
use vecdb::{IterableCloneableVec, LazyVecFrom2};
use brk_types::{Dollars, Sats, StoredF32, StoredF64, Version};
use vecdb::IterableCloneableVec;
use crate::internal::{
BinaryDateLast, NegPercentageDollarsF32, NegRatio32, PercentageBtcF64,
LazyBinaryBlockLast, LazyBinaryDateLast, NegPercentageDollarsF32, NegRatio32,
PercentageDollarsF32, PercentageSatsF64, Ratio32,
};
@@ -15,94 +15,49 @@ use super::{ImportConfig, SupplyMetrics, UnrealizedMetrics};
#[derive(Clone, Traversable)]
pub struct RelativeMetrics {
// === Supply Relative to Circulating Supply (lazy from global supply) ===
// KISS: both sources are ComputedVecsDateLast<Sats>
pub indexes_to_supply_rel_to_circulating_supply:
Option<BinaryDateLast<StoredF64, Sats, Sats>>,
pub supply_rel_to_circulating_supply: Option<LazyBinaryDateLast<StoredF64, Sats, Sats>>,
// === Supply in Profit/Loss Relative to Own Supply (lazy) ===
pub height_to_supply_in_profit_rel_to_own_supply:
LazyVecFrom2<Height, StoredF64, Height, Bitcoin, Height, Bitcoin>,
pub height_to_supply_in_loss_rel_to_own_supply:
LazyVecFrom2<Height, StoredF64, Height, Bitcoin, Height, Bitcoin>,
// KISS: both unrealized and supply are now KISS types
pub indexes_to_supply_in_profit_rel_to_own_supply:
BinaryDateLast<StoredF64, Sats, Sats>,
pub indexes_to_supply_in_loss_rel_to_own_supply:
BinaryDateLast<StoredF64, Sats, Sats>,
pub supply_in_profit_rel_to_own_supply: LazyBinaryBlockLast<StoredF64, Sats, Sats>,
pub supply_in_loss_rel_to_own_supply: LazyBinaryBlockLast<StoredF64, Sats, Sats>,
// === Supply in Profit/Loss Relative to Circulating Supply (lazy from global supply) ===
pub height_to_supply_in_profit_rel_to_circulating_supply:
Option<LazyVecFrom2<Height, StoredF64, Height, Bitcoin, Height, Bitcoin>>,
pub height_to_supply_in_loss_rel_to_circulating_supply:
Option<LazyVecFrom2<Height, StoredF64, Height, Bitcoin, Height, Bitcoin>>,
// KISS: both unrealized and global_supply are now KISS types
pub indexes_to_supply_in_profit_rel_to_circulating_supply:
Option<BinaryDateLast<StoredF64, Sats, Sats>>,
pub indexes_to_supply_in_loss_rel_to_circulating_supply:
Option<BinaryDateLast<StoredF64, Sats, Sats>>,
pub supply_in_profit_rel_to_circulating_supply:
Option<LazyBinaryBlockLast<StoredF64, Sats, Sats>>,
pub supply_in_loss_rel_to_circulating_supply:
Option<LazyBinaryBlockLast<StoredF64, Sats, Sats>>,
// === Unrealized vs Market Cap (lazy from global market cap) ===
pub height_to_unrealized_profit_rel_to_market_cap:
Option<LazyVecFrom2<Height, StoredF32, Height, Dollars, Height, Dollars>>,
pub height_to_unrealized_loss_rel_to_market_cap:
Option<LazyVecFrom2<Height, StoredF32, Height, Dollars, Height, Dollars>>,
pub height_to_neg_unrealized_loss_rel_to_market_cap:
Option<LazyVecFrom2<Height, StoredF32, Height, Dollars, Height, Dollars>>,
pub height_to_net_unrealized_pnl_rel_to_market_cap:
Option<LazyVecFrom2<Height, StoredF32, Height, Dollars, Height, Dollars>>,
// KISS: DerivedDateLast + ComputedVecsDateLast
pub indexes_to_unrealized_profit_rel_to_market_cap:
Option<BinaryDateLast<StoredF32, Dollars, Dollars>>,
pub indexes_to_unrealized_loss_rel_to_market_cap:
Option<BinaryDateLast<StoredF32, Dollars, Dollars>>,
pub indexes_to_neg_unrealized_loss_rel_to_market_cap:
Option<BinaryDateLast<StoredF32, Dollars, Dollars>>,
// KISS: both ComputedVecsDateLast
pub indexes_to_net_unrealized_pnl_rel_to_market_cap:
Option<BinaryDateLast<StoredF32, Dollars, Dollars>>,
pub unrealized_profit_rel_to_market_cap:
Option<LazyBinaryBlockLast<StoredF32, Dollars, Dollars>>,
pub unrealized_loss_rel_to_market_cap: Option<LazyBinaryBlockLast<StoredF32, Dollars, Dollars>>,
pub neg_unrealized_loss_rel_to_market_cap:
Option<LazyBinaryBlockLast<StoredF32, Dollars, Dollars>>,
pub net_unrealized_pnl_rel_to_market_cap:
Option<LazyBinaryBlockLast<StoredF32, Dollars, Dollars>>,
// === NUPL (Net Unrealized Profit/Loss) ===
// KISS: both ComputedVecsDateLast
pub indexes_to_nupl: Option<BinaryDateLast<StoredF32, Dollars, Dollars>>,
pub nupl: Option<LazyBinaryBlockLast<StoredF32, Dollars, Dollars>>,
// === Unrealized vs Own Market Cap (lazy) ===
pub height_to_unrealized_profit_rel_to_own_market_cap:
Option<LazyVecFrom2<Height, StoredF32, Height, Dollars, Height, Dollars>>,
pub height_to_unrealized_loss_rel_to_own_market_cap:
Option<LazyVecFrom2<Height, StoredF32, Height, Dollars, Height, Dollars>>,
pub height_to_neg_unrealized_loss_rel_to_own_market_cap:
Option<LazyVecFrom2<Height, StoredF32, Height, Dollars, Height, Dollars>>,
pub height_to_net_unrealized_pnl_rel_to_own_market_cap:
Option<LazyVecFrom2<Height, StoredF32, Height, Dollars, Height, Dollars>>,
// KISS: DerivedDateLast + ComputedVecsDateLast
pub indexes_to_unrealized_profit_rel_to_own_market_cap:
Option<BinaryDateLast<StoredF32, Dollars, Dollars>>,
pub indexes_to_unrealized_loss_rel_to_own_market_cap:
Option<BinaryDateLast<StoredF32, Dollars, Dollars>>,
pub indexes_to_neg_unrealized_loss_rel_to_own_market_cap:
Option<BinaryDateLast<StoredF32, Dollars, Dollars>>,
// KISS: both ComputedVecsDateLast
pub indexes_to_net_unrealized_pnl_rel_to_own_market_cap:
Option<BinaryDateLast<StoredF32, Dollars, Dollars>>,
pub unrealized_profit_rel_to_own_market_cap:
Option<LazyBinaryBlockLast<StoredF32, Dollars, Dollars>>,
pub unrealized_loss_rel_to_own_market_cap:
Option<LazyBinaryBlockLast<StoredF32, Dollars, Dollars>>,
pub neg_unrealized_loss_rel_to_own_market_cap:
Option<LazyBinaryBlockLast<StoredF32, Dollars, Dollars>>,
pub net_unrealized_pnl_rel_to_own_market_cap:
Option<LazyBinaryBlockLast<StoredF32, Dollars, Dollars>>,
// === Unrealized vs Own Total Unrealized PnL (lazy) ===
pub height_to_unrealized_profit_rel_to_own_total_unrealized_pnl:
Option<LazyVecFrom2<Height, StoredF32, Height, Dollars, Height, Dollars>>,
pub height_to_unrealized_loss_rel_to_own_total_unrealized_pnl:
Option<LazyVecFrom2<Height, StoredF32, Height, Dollars, Height, Dollars>>,
pub height_to_neg_unrealized_loss_rel_to_own_total_unrealized_pnl:
Option<LazyVecFrom2<Height, StoredF32, Height, Dollars, Height, Dollars>>,
pub height_to_net_unrealized_pnl_rel_to_own_total_unrealized_pnl:
Option<LazyVecFrom2<Height, StoredF32, Height, Dollars, Height, Dollars>>,
// KISS: DerivedDateLast + DerivedDateLast
pub indexes_to_unrealized_profit_rel_to_own_total_unrealized_pnl:
Option<BinaryDateLast<StoredF32, Dollars, Dollars>>,
pub indexes_to_unrealized_loss_rel_to_own_total_unrealized_pnl:
Option<BinaryDateLast<StoredF32, Dollars, Dollars>>,
pub indexes_to_neg_unrealized_loss_rel_to_own_total_unrealized_pnl:
Option<BinaryDateLast<StoredF32, Dollars, Dollars>>,
pub indexes_to_net_unrealized_pnl_rel_to_own_total_unrealized_pnl:
Option<BinaryDateLast<StoredF32, Dollars, Dollars>>,
pub unrealized_profit_rel_to_own_total_unrealized_pnl:
Option<LazyBinaryBlockLast<StoredF32, Dollars, Dollars>>,
pub unrealized_loss_rel_to_own_total_unrealized_pnl:
Option<LazyBinaryBlockLast<StoredF32, Dollars, Dollars>>,
pub neg_unrealized_loss_rel_to_own_total_unrealized_pnl:
Option<LazyBinaryBlockLast<StoredF32, Dollars, Dollars>>,
pub net_unrealized_pnl_rel_to_own_total_unrealized_pnl:
Option<LazyBinaryBlockLast<StoredF32, Dollars, Dollars>>,
}
impl RelativeMetrics {
@@ -122,300 +77,222 @@ impl RelativeMetrics {
let compute_rel_to_all = cfg.compute_rel_to_all();
// Global sources from "all" cohort
let global_supply_sats = all_supply.map(|s| &s.indexes_to_supply.sats);
let global_supply_sats_dateindex = all_supply.map(|s| &s.indexes_to_supply.sats_dateindex);
let global_supply_btc = all_supply.map(|s| &s.height_to_supply_value.bitcoin);
let global_market_cap = all_supply.and_then(|s| s.indexes_to_supply.dollars.as_ref());
let global_market_cap_height =
all_supply.and_then(|s| s.height_to_supply_value.dollars.as_ref());
let global_supply_sats_height = all_supply.map(|s| &s.supply.sats.height);
let global_supply_sats_difficultyepoch = all_supply.map(|s| &s.supply.sats.difficultyepoch);
let global_supply_sats_dates = all_supply.map(|s| &s.supply.sats.rest.dates);
let global_supply_sats_dateindex = all_supply.map(|s| &s.supply.sats.rest.dateindex);
let global_market_cap = all_supply.and_then(|s| s.supply.dollars.as_ref());
// Own market cap source
let own_market_cap = supply.indexes_to_supply.dollars.as_ref();
let own_market_cap_height = supply.height_to_supply_value.dollars.as_ref();
let own_market_cap = supply.supply.dollars.as_ref();
Ok(Self {
// === Supply Relative to Circulating Supply (lazy from global supply) ===
indexes_to_supply_rel_to_circulating_supply: (compute_rel_to_all
&& global_supply_sats.is_some())
supply_rel_to_circulating_supply: (compute_rel_to_all
&& global_supply_sats_dates.is_some())
.then(|| {
BinaryDateLast::from_both_derived_last::<PercentageSatsF64>(
LazyBinaryDateLast::from_both_derived_last::<PercentageSatsF64>(
&cfg.name("supply_rel_to_circulating_supply"),
cfg.version + v1,
supply.indexes_to_supply.sats_dateindex.boxed_clone(),
&supply.indexes_to_supply.sats,
supply.supply.sats.rest.dateindex.boxed_clone(),
&supply.supply.sats.rest.dates,
global_supply_sats_dateindex.unwrap().boxed_clone(),
global_supply_sats.unwrap(),
global_supply_sats_dates.unwrap(),
)
}),
// === Supply in Profit/Loss Relative to Own Supply (lazy) ===
height_to_supply_in_profit_rel_to_own_supply: LazyVecFrom2::transformed::<
PercentageBtcF64,
>(
&cfg.name("supply_in_profit_rel_to_own_supply"),
cfg.version + v1,
unrealized
.height_to_supply_in_profit_value
.bitcoin
.boxed_clone(),
supply.height_to_supply_value.bitcoin.boxed_clone(),
),
height_to_supply_in_loss_rel_to_own_supply: LazyVecFrom2::transformed::<PercentageBtcF64>(
&cfg.name("supply_in_loss_rel_to_own_supply"),
cfg.version + v1,
unrealized
.height_to_supply_in_loss_value
.bitcoin
.boxed_clone(),
supply.height_to_supply_value.bitcoin.boxed_clone(),
),
indexes_to_supply_in_profit_rel_to_own_supply:
BinaryDateLast::from_both_derived_last::<PercentageSatsF64>(
supply_in_profit_rel_to_own_supply:
LazyBinaryBlockLast::from_height_difficultyepoch_dates::<PercentageSatsF64>(
&cfg.name("supply_in_profit_rel_to_own_supply"),
cfg.version + v1,
unrealized.dateindex_to_supply_in_profit.boxed_clone(),
&unrealized.indexes_to_supply_in_profit.sats,
supply.indexes_to_supply.sats_dateindex.boxed_clone(),
&supply.indexes_to_supply.sats,
unrealized.supply_in_profit.height.boxed_clone(),
supply.supply.sats.height.boxed_clone(),
unrealized.supply_in_profit.difficultyepoch.boxed_clone(),
supply.supply.sats.difficultyepoch.boxed_clone(),
unrealized
.supply_in_profit
.indexes
.sats_dateindex
.boxed_clone(),
&unrealized.supply_in_profit.indexes.sats,
supply.supply.sats.rest.dateindex.boxed_clone(),
&supply.supply.sats.rest.dates,
),
indexes_to_supply_in_loss_rel_to_own_supply:
BinaryDateLast::from_both_derived_last::<PercentageSatsF64>(
supply_in_loss_rel_to_own_supply:
LazyBinaryBlockLast::from_height_difficultyepoch_dates::<PercentageSatsF64>(
&cfg.name("supply_in_loss_rel_to_own_supply"),
cfg.version + v1,
unrealized.dateindex_to_supply_in_loss.boxed_clone(),
&unrealized.indexes_to_supply_in_loss.sats,
supply.indexes_to_supply.sats_dateindex.boxed_clone(),
&supply.indexes_to_supply.sats,
unrealized.supply_in_loss.height.boxed_clone(),
supply.supply.sats.height.boxed_clone(),
unrealized.supply_in_loss.difficultyepoch.boxed_clone(),
supply.supply.sats.difficultyepoch.boxed_clone(),
unrealized
.supply_in_loss
.indexes
.sats_dateindex
.boxed_clone(),
&unrealized.supply_in_loss.indexes.sats,
supply.supply.sats.rest.dateindex.boxed_clone(),
&supply.supply.sats.rest.dates,
),
// === Supply in Profit/Loss Relative to Circulating Supply (lazy from global supply) ===
height_to_supply_in_profit_rel_to_circulating_supply: (compute_rel_to_all
&& global_supply_btc.is_some())
supply_in_profit_rel_to_circulating_supply: (compute_rel_to_all
&& global_supply_sats_height.is_some())
.then(|| {
LazyVecFrom2::transformed::<PercentageBtcF64>(
LazyBinaryBlockLast::from_height_difficultyepoch_dates::<PercentageSatsF64>(
&cfg.name("supply_in_profit_rel_to_circulating_supply"),
cfg.version + v1,
unrealized.supply_in_profit.height.boxed_clone(),
global_supply_sats_height.unwrap().boxed_clone(),
unrealized.supply_in_profit.difficultyepoch.boxed_clone(),
global_supply_sats_difficultyepoch.unwrap().boxed_clone(),
unrealized
.height_to_supply_in_profit_value
.bitcoin
.supply_in_profit
.indexes
.sats_dateindex
.boxed_clone(),
global_supply_btc.unwrap().boxed_clone(),
&unrealized.supply_in_profit.indexes.sats,
global_supply_sats_dateindex.unwrap().boxed_clone(),
global_supply_sats_dates.unwrap(),
)
}),
height_to_supply_in_loss_rel_to_circulating_supply: (compute_rel_to_all
&& global_supply_btc.is_some())
supply_in_loss_rel_to_circulating_supply: (compute_rel_to_all
&& global_supply_sats_height.is_some())
.then(|| {
LazyVecFrom2::transformed::<PercentageBtcF64>(
LazyBinaryBlockLast::from_height_difficultyepoch_dates::<PercentageSatsF64>(
&cfg.name("supply_in_loss_rel_to_circulating_supply"),
cfg.version + v1,
unrealized.supply_in_loss.height.boxed_clone(),
global_supply_sats_height.unwrap().boxed_clone(),
unrealized.supply_in_loss.difficultyepoch.boxed_clone(),
global_supply_sats_difficultyepoch.unwrap().boxed_clone(),
unrealized
.height_to_supply_in_loss_value
.bitcoin
.supply_in_loss
.indexes
.sats_dateindex
.boxed_clone(),
global_supply_btc.unwrap().boxed_clone(),
)
}),
indexes_to_supply_in_profit_rel_to_circulating_supply: (compute_rel_to_all
&& global_supply_sats.is_some())
.then(|| {
BinaryDateLast::from_both_derived_last::<PercentageSatsF64>(
&cfg.name("supply_in_profit_rel_to_circulating_supply"),
cfg.version + v1,
unrealized.dateindex_to_supply_in_profit.boxed_clone(),
&unrealized.indexes_to_supply_in_profit.sats,
&unrealized.supply_in_loss.indexes.sats,
global_supply_sats_dateindex.unwrap().boxed_clone(),
global_supply_sats.unwrap(),
)
}),
indexes_to_supply_in_loss_rel_to_circulating_supply: (compute_rel_to_all
&& global_supply_sats.is_some())
.then(|| {
BinaryDateLast::from_both_derived_last::<PercentageSatsF64>(
&cfg.name("supply_in_loss_rel_to_circulating_supply"),
cfg.version + v1,
unrealized.dateindex_to_supply_in_loss.boxed_clone(),
&unrealized.indexes_to_supply_in_loss.sats,
global_supply_sats_dateindex.unwrap().boxed_clone(),
global_supply_sats.unwrap(),
global_supply_sats_dates.unwrap(),
)
}),
// === Unrealized vs Market Cap (lazy from global market cap) ===
height_to_unrealized_profit_rel_to_market_cap: global_market_cap_height.map(|mc| {
LazyVecFrom2::transformed::<PercentageDollarsF32>(
&cfg.name("unrealized_profit_rel_to_market_cap"),
cfg.version,
unrealized.height_to_unrealized_profit.boxed_clone(),
mc.boxed_clone(),
)
}),
height_to_unrealized_loss_rel_to_market_cap: global_market_cap_height.map(|mc| {
LazyVecFrom2::transformed::<PercentageDollarsF32>(
&cfg.name("unrealized_loss_rel_to_market_cap"),
cfg.version,
unrealized.height_to_unrealized_loss.boxed_clone(),
mc.boxed_clone(),
)
}),
height_to_neg_unrealized_loss_rel_to_market_cap: global_market_cap_height.map(|mc| {
LazyVecFrom2::transformed::<NegPercentageDollarsF32>(
&cfg.name("neg_unrealized_loss_rel_to_market_cap"),
cfg.version,
unrealized.height_to_unrealized_loss.boxed_clone(),
mc.boxed_clone(),
)
}),
height_to_net_unrealized_pnl_rel_to_market_cap: global_market_cap_height.map(|mc| {
LazyVecFrom2::transformed::<PercentageDollarsF32>(
&cfg.name("net_unrealized_pnl_rel_to_market_cap"),
cfg.version + v1,
unrealized.height_to_net_unrealized_pnl.boxed_clone(),
mc.boxed_clone(),
)
}),
// KISS: market_cap is now ComputedVecsDateLast
indexes_to_unrealized_profit_rel_to_market_cap: global_market_cap.map(|mc| {
BinaryDateLast::from_derived_last_and_computed_last::<PercentageDollarsF32>(
&cfg.name("unrealized_profit_rel_to_market_cap"),
cfg.version + v2,
unrealized.dateindex_to_unrealized_profit.boxed_clone(),
&unrealized.indexes_to_unrealized_profit,
mc,
)
}),
indexes_to_unrealized_loss_rel_to_market_cap: global_market_cap.map(|mc| {
BinaryDateLast::from_derived_last_and_computed_last::<PercentageDollarsF32>(
&cfg.name("unrealized_loss_rel_to_market_cap"),
cfg.version + v2,
unrealized.dateindex_to_unrealized_loss.boxed_clone(),
&unrealized.indexes_to_unrealized_loss,
mc,
)
}),
indexes_to_neg_unrealized_loss_rel_to_market_cap: global_market_cap.map(|mc| {
BinaryDateLast::from_derived_last_and_computed_last::<NegPercentageDollarsF32>(
unrealized_profit_rel_to_market_cap:
global_market_cap.map(|mc| {
LazyBinaryBlockLast::from_computed_height_date_and_block_last::<
PercentageDollarsF32,
>(
&cfg.name("unrealized_profit_rel_to_market_cap"),
cfg.version + v2,
&unrealized.unrealized_profit,
mc,
)
}),
unrealized_loss_rel_to_market_cap:
global_market_cap.map(|mc| {
LazyBinaryBlockLast::from_computed_height_date_and_block_last::<
PercentageDollarsF32,
>(
&cfg.name("unrealized_loss_rel_to_market_cap"),
cfg.version + v2,
&unrealized.unrealized_loss,
mc,
)
}),
neg_unrealized_loss_rel_to_market_cap: global_market_cap.map(|mc| {
LazyBinaryBlockLast::from_computed_height_date_and_block_last::<
NegPercentageDollarsF32,
>(
&cfg.name("neg_unrealized_loss_rel_to_market_cap"),
cfg.version + v2,
unrealized.dateindex_to_unrealized_loss.boxed_clone(),
&unrealized.indexes_to_unrealized_loss,
&unrealized.unrealized_loss,
mc,
)
}),
indexes_to_net_unrealized_pnl_rel_to_market_cap: global_market_cap.map(|mc| {
BinaryDateLast::from_computed_both_last::<PercentageDollarsF32>(
net_unrealized_pnl_rel_to_market_cap: global_market_cap.map(|mc| {
LazyBinaryBlockLast::from_binary_block_and_computed_block_last::<
PercentageDollarsF32,
_,
_,
>(
&cfg.name("net_unrealized_pnl_rel_to_market_cap"),
cfg.version + v2,
&unrealized.indexes_to_net_unrealized_pnl,
&unrealized.net_unrealized_pnl,
mc,
)
}),
// NUPL is a proxy for net_unrealized_pnl_rel_to_market_cap
indexes_to_nupl: global_market_cap.map(|mc| {
BinaryDateLast::from_computed_both_last::<PercentageDollarsF32>(
nupl: global_market_cap.map(|mc| {
LazyBinaryBlockLast::from_binary_block_and_computed_block_last::<
PercentageDollarsF32,
_,
_,
>(
&cfg.name("nupl"),
cfg.version + v2,
&unrealized.indexes_to_net_unrealized_pnl,
&unrealized.net_unrealized_pnl,
mc,
)
}),
// === Unrealized vs Own Market Cap (lazy, optional) ===
height_to_unrealized_profit_rel_to_own_market_cap: (extended && compute_rel_to_all)
.then(|| {
own_market_cap_height.map(|mc| {
LazyVecFrom2::transformed::<PercentageDollarsF32>(
&cfg.name("unrealized_profit_rel_to_own_market_cap"),
cfg.version + v1,
unrealized.height_to_unrealized_profit.boxed_clone(),
mc.boxed_clone(),
)
})
})
.flatten(),
height_to_unrealized_loss_rel_to_own_market_cap: (extended && compute_rel_to_all)
.then(|| {
own_market_cap_height.map(|mc| {
LazyVecFrom2::transformed::<PercentageDollarsF32>(
&cfg.name("unrealized_loss_rel_to_own_market_cap"),
cfg.version + v1,
unrealized.height_to_unrealized_loss.boxed_clone(),
mc.boxed_clone(),
)
})
})
.flatten(),
height_to_neg_unrealized_loss_rel_to_own_market_cap: (extended && compute_rel_to_all)
.then(|| {
own_market_cap_height.map(|mc| {
LazyVecFrom2::transformed::<NegPercentageDollarsF32>(
&cfg.name("neg_unrealized_loss_rel_to_own_market_cap"),
cfg.version + v1,
unrealized.height_to_unrealized_loss.boxed_clone(),
mc.boxed_clone(),
)
})
})
.flatten(),
height_to_net_unrealized_pnl_rel_to_own_market_cap: (extended && compute_rel_to_all)
.then(|| {
own_market_cap_height.map(|mc| {
LazyVecFrom2::transformed::<PercentageDollarsF32>(
&cfg.name("net_unrealized_pnl_rel_to_own_market_cap"),
cfg.version + v2,
unrealized.height_to_net_unrealized_pnl.boxed_clone(),
mc.boxed_clone(),
)
})
})
.flatten(),
// KISS: own_market_cap is now ComputedVecsDateLast
indexes_to_unrealized_profit_rel_to_own_market_cap: (extended && compute_rel_to_all)
unrealized_profit_rel_to_own_market_cap: (extended && compute_rel_to_all)
.then(|| {
own_market_cap.map(|mc| {
BinaryDateLast::from_derived_last_and_computed_last::<PercentageDollarsF32>(
LazyBinaryBlockLast::from_computed_height_date_and_block_last::<
PercentageDollarsF32,
>(
&cfg.name("unrealized_profit_rel_to_own_market_cap"),
cfg.version + v2,
unrealized.dateindex_to_unrealized_profit.boxed_clone(),
&unrealized.indexes_to_unrealized_profit,
&unrealized.unrealized_profit,
mc,
)
})
})
.flatten(),
indexes_to_unrealized_loss_rel_to_own_market_cap: (extended && compute_rel_to_all)
unrealized_loss_rel_to_own_market_cap: (extended && compute_rel_to_all)
.then(|| {
own_market_cap.map(|mc| {
BinaryDateLast::from_derived_last_and_computed_last::<PercentageDollarsF32>(
LazyBinaryBlockLast::from_computed_height_date_and_block_last::<
PercentageDollarsF32,
>(
&cfg.name("unrealized_loss_rel_to_own_market_cap"),
cfg.version + v2,
unrealized.dateindex_to_unrealized_loss.boxed_clone(),
&unrealized.indexes_to_unrealized_loss,
&unrealized.unrealized_loss,
mc,
)
})
})
.flatten(),
indexes_to_neg_unrealized_loss_rel_to_own_market_cap: (extended && compute_rel_to_all)
neg_unrealized_loss_rel_to_own_market_cap: (extended && compute_rel_to_all)
.then(|| {
own_market_cap.map(|mc| {
BinaryDateLast::from_derived_last_and_computed_last::<NegPercentageDollarsF32>(
LazyBinaryBlockLast::from_computed_height_date_and_block_last::<
NegPercentageDollarsF32,
>(
&cfg.name("neg_unrealized_loss_rel_to_own_market_cap"),
cfg.version + v2,
unrealized.dateindex_to_unrealized_loss.boxed_clone(),
&unrealized.indexes_to_unrealized_loss,
&unrealized.unrealized_loss,
mc,
)
})
})
.flatten(),
indexes_to_net_unrealized_pnl_rel_to_own_market_cap: (extended && compute_rel_to_all)
net_unrealized_pnl_rel_to_own_market_cap: (extended && compute_rel_to_all)
.then(|| {
own_market_cap.map(|mc| {
BinaryDateLast::from_computed_both_last::<PercentageDollarsF32>(
LazyBinaryBlockLast::from_binary_block_and_computed_block_last::<
PercentageDollarsF32,
_,
_,
>(
&cfg.name("net_unrealized_pnl_rel_to_own_market_cap"),
cfg.version + v2,
&unrealized.indexes_to_net_unrealized_pnl,
&unrealized.net_unrealized_pnl,
mc,
)
})
@@ -423,71 +300,36 @@ impl RelativeMetrics {
.flatten(),
// === Unrealized vs Own Total Unrealized PnL (lazy, optional) ===
height_to_unrealized_profit_rel_to_own_total_unrealized_pnl: extended.then(|| {
LazyVecFrom2::transformed::<Ratio32>(
unrealized_profit_rel_to_own_total_unrealized_pnl: extended.then(|| {
LazyBinaryBlockLast::from_computed_height_date_and_binary_block::<Ratio32, _, _>(
&cfg.name("unrealized_profit_rel_to_own_total_unrealized_pnl"),
cfg.version,
unrealized.height_to_unrealized_profit.boxed_clone(),
unrealized.height_to_total_unrealized_pnl.boxed_clone(),
&unrealized.unrealized_profit,
&unrealized.total_unrealized_pnl,
)
}),
height_to_unrealized_loss_rel_to_own_total_unrealized_pnl: extended.then(|| {
LazyVecFrom2::transformed::<Ratio32>(
unrealized_loss_rel_to_own_total_unrealized_pnl: extended.then(|| {
LazyBinaryBlockLast::from_computed_height_date_and_binary_block::<Ratio32, _, _>(
&cfg.name("unrealized_loss_rel_to_own_total_unrealized_pnl"),
cfg.version,
unrealized.height_to_unrealized_loss.boxed_clone(),
unrealized.height_to_total_unrealized_pnl.boxed_clone(),
&unrealized.unrealized_loss,
&unrealized.total_unrealized_pnl,
)
}),
height_to_neg_unrealized_loss_rel_to_own_total_unrealized_pnl: extended.then(|| {
LazyVecFrom2::transformed::<NegRatio32>(
neg_unrealized_loss_rel_to_own_total_unrealized_pnl: extended.then(|| {
LazyBinaryBlockLast::from_computed_height_date_and_binary_block::<NegRatio32, _, _>(
&cfg.name("neg_unrealized_loss_rel_to_own_total_unrealized_pnl"),
cfg.version,
unrealized.height_to_unrealized_loss.boxed_clone(),
unrealized.height_to_total_unrealized_pnl.boxed_clone(),
&unrealized.unrealized_loss,
&unrealized.total_unrealized_pnl,
)
}),
height_to_net_unrealized_pnl_rel_to_own_total_unrealized_pnl: extended.then(|| {
LazyVecFrom2::transformed::<Ratio32>(
net_unrealized_pnl_rel_to_own_total_unrealized_pnl: extended.then(|| {
LazyBinaryBlockLast::from_both_binary_block::<Ratio32, _, _, _, _>(
&cfg.name("net_unrealized_pnl_rel_to_own_total_unrealized_pnl"),
cfg.version + v1,
unrealized.height_to_net_unrealized_pnl.boxed_clone(),
unrealized.height_to_total_unrealized_pnl.boxed_clone(),
)
}),
indexes_to_unrealized_profit_rel_to_own_total_unrealized_pnl: extended.then(|| {
BinaryDateLast::from_derived_last_and_computed_last::<Ratio32>(
&cfg.name("unrealized_profit_rel_to_own_total_unrealized_pnl"),
cfg.version + v1,
unrealized.dateindex_to_unrealized_profit.boxed_clone(),
&unrealized.indexes_to_unrealized_profit,
&unrealized.indexes_to_total_unrealized_pnl,
)
}),
indexes_to_unrealized_loss_rel_to_own_total_unrealized_pnl: extended.then(|| {
BinaryDateLast::from_derived_last_and_computed_last::<Ratio32>(
&cfg.name("unrealized_loss_rel_to_own_total_unrealized_pnl"),
cfg.version + v1,
unrealized.dateindex_to_unrealized_loss.boxed_clone(),
&unrealized.indexes_to_unrealized_loss,
&unrealized.indexes_to_total_unrealized_pnl,
)
}),
indexes_to_neg_unrealized_loss_rel_to_own_total_unrealized_pnl: extended.then(|| {
BinaryDateLast::from_derived_last_and_computed_last::<NegRatio32>(
&cfg.name("neg_unrealized_loss_rel_to_own_total_unrealized_pnl"),
cfg.version + v1,
unrealized.dateindex_to_unrealized_loss.boxed_clone(),
&unrealized.indexes_to_unrealized_loss,
&unrealized.indexes_to_total_unrealized_pnl,
)
}),
indexes_to_net_unrealized_pnl_rel_to_own_total_unrealized_pnl: extended.then(|| {
BinaryDateLast::from_computed_both_last::<Ratio32>(
&cfg.name("net_unrealized_pnl_rel_to_own_total_unrealized_pnl"),
cfg.version + v1,
&unrealized.indexes_to_net_unrealized_pnl,
&unrealized.indexes_to_total_unrealized_pnl,
&unrealized.net_unrealized_pnl,
&unrealized.total_unrealized_pnl,
)
}),
})

View File

@@ -1,131 +1,89 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Height, Sats, StoredU64, SupplyState, Version};
use brk_types::{Height, Sats, Version};
use crate::ComputeIndexes;
use rayon::prelude::*;
use vecdb::{
AnyStoredVec, AnyVec, EagerVec, Exit, GenericStoredVec, ImportableVec, IterableCloneableVec,
PcoVec, TypedVecIterator,
};
use vecdb::{AnyStoredVec, AnyVec, Exit, GenericStoredVec, IterableCloneableVec};
use crate::{
ComputeIndexes, indexes,
indexes,
internal::{
DerivedComputedBlockLast, HalfClosePriceTimesSats, HalveDollars, HalveSats,
HalveSatsToBitcoin, LazyBlockValue, LazyDerivedBlockValue, LazyValueDateLast, ValueDateLast,
HalfClosePriceTimesSats, HalveDollars, HalveSats, HalveSatsToBitcoin, LazyBlockValue,
LazyValueDateLast, ValueBlockLast,
},
price,
};
use super::ImportConfig;
/// Supply and UTXO count metrics for a cohort.
/// Supply metrics for a cohort.
#[derive(Clone, Traversable)]
pub struct SupplyMetrics {
pub height_to_supply: EagerVec<PcoVec<Height, Sats>>,
pub height_to_supply_value: LazyDerivedBlockValue,
pub indexes_to_supply: ValueDateLast,
pub height_to_utxo_count: EagerVec<PcoVec<Height, StoredU64>>,
pub indexes_to_utxo_count: DerivedComputedBlockLast<StoredU64>,
pub height_to_supply_half_value: LazyBlockValue,
pub indexes_to_supply_half: LazyValueDateLast,
pub supply: ValueBlockLast,
pub supply_half_value: LazyBlockValue,
pub supply_half: LazyValueDateLast,
}
impl SupplyMetrics {
/// Import supply metrics from database.
pub fn forced_import(cfg: &ImportConfig) -> Result<Self> {
let v1 = Version::ONE;
let compute_dollars = cfg.compute_dollars();
let height_to_supply: EagerVec<PcoVec<Height, Sats>> =
EagerVec::forced_import(cfg.db, &cfg.name("supply"), cfg.version)?;
let supply = ValueBlockLast::forced_import(
cfg.db,
&cfg.name("supply"),
cfg.version,
cfg.indexes,
compute_dollars,
)?;
let price_source = cfg
.price
.map(|p| p.usd.chainindexes_to_price_close.height.boxed_clone());
let height_to_supply_value = LazyDerivedBlockValue::from_source(
&cfg.name("supply"),
height_to_supply.boxed_clone(),
cfg.version,
price_source.clone(),
);
let indexes_to_supply = ValueDateLast::forced_import(
cfg.db,
&cfg.name("supply"),
cfg.version + v1,
compute_dollars,
cfg.indexes,
)?;
.map(|p| p.usd.split.close.height.boxed_clone());
// Create lazy supply_half from supply sources
let height_to_supply_half_value = LazyBlockValue::from_sources::<
HalveSats,
HalveSatsToBitcoin,
HalfClosePriceTimesSats,
>(
&cfg.name("supply_half"),
height_to_supply.boxed_clone(),
price_source,
cfg.version,
);
let indexes_to_supply_half =
LazyValueDateLast::from_source::<HalveSats, HalveSatsToBitcoin, HalveDollars>(
let supply_half_value =
LazyBlockValue::from_sources::<HalveSats, HalveSatsToBitcoin, HalfClosePriceTimesSats>(
&cfg.name("supply_half"),
&indexes_to_supply,
supply.sats.height.boxed_clone(),
price_source,
cfg.version,
);
let height_to_utxo_count =
EagerVec::forced_import(cfg.db, &cfg.name("utxo_count"), cfg.version)?;
let supply_half = LazyValueDateLast::from_block_source::<
HalveSats,
HalveSatsToBitcoin,
HalveDollars,
>(&cfg.name("supply_half"), &supply, cfg.version);
Ok(Self {
indexes_to_utxo_count: DerivedComputedBlockLast::forced_import(
cfg.db,
&cfg.name("utxo_count"),
height_to_utxo_count.boxed_clone(),
cfg.version,
cfg.indexes,
)?,
height_to_supply,
height_to_supply_value,
indexes_to_supply,
height_to_utxo_count,
height_to_supply_half_value,
indexes_to_supply_half,
supply,
supply_half_value,
supply_half,
})
}
/// Get minimum length across height-indexed vectors.
pub fn min_len(&self) -> usize {
self.height_to_supply
.len()
.min(self.height_to_utxo_count.len())
self.supply.sats.height.len()
}
/// Push supply state values to height-indexed vectors.
pub fn truncate_push(&mut self, height: Height, state: &SupplyState) -> Result<()> {
self.height_to_supply.truncate_push(height, state.value)?;
self.height_to_utxo_count
.truncate_push(height, StoredU64::from(state.utxo_count))?;
pub fn truncate_push(&mut self, height: Height, supply: Sats) -> Result<()> {
self.supply.sats.height.truncate_push(height, supply)?;
Ok(())
}
/// Write height-indexed vectors to disk.
pub fn write(&mut self) -> Result<()> {
self.height_to_supply.write()?;
self.height_to_utxo_count.write()?;
self.supply.sats.height.write()?;
Ok(())
}
/// Returns a parallel iterator over all vecs for parallel writing.
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
vec![
&mut self.height_to_supply as &mut dyn AnyStoredVec,
&mut self.height_to_utxo_count as &mut dyn AnyStoredVec,
]
.into_par_iter()
vec![&mut self.supply.sats.height as &mut dyn AnyStoredVec].into_par_iter()
}
/// Validate computed versions against base version.
@@ -141,26 +99,18 @@ impl SupplyMetrics {
others: &[&Self],
exit: &Exit,
) -> Result<()> {
self.height_to_supply.compute_sum_of_others(
self.supply.sats.height.compute_sum_of_others(
starting_indexes.height,
&others
.iter()
.map(|v| &v.height_to_supply)
.collect::<Vec<_>>(),
exit,
)?;
self.height_to_utxo_count.compute_sum_of_others(
starting_indexes.height,
&others
.iter()
.map(|v| &v.height_to_utxo_count)
.map(|v| &v.supply.sats.height)
.collect::<Vec<_>>(),
exit,
)?;
Ok(())
}
/// First phase of computed metrics (indexes from height).
/// Compute derived vecs from existing height data.
pub fn compute_rest_part1(
&mut self,
indexes: &indexes::Vecs,
@@ -168,34 +118,7 @@ impl SupplyMetrics {
starting_indexes: &ComputeIndexes,
exit: &Exit,
) -> Result<()> {
self.indexes_to_supply
.compute_all(price, starting_indexes, exit, |v| {
let mut dateindex_to_height_count_iter =
indexes.time.dateindex_to_height_count.into_iter();
let mut height_to_supply_iter = self.height_to_supply.into_iter();
v.compute_transform(
starting_indexes.dateindex,
&indexes.time.dateindex_to_first_height,
|(i, height, ..)| {
let count = dateindex_to_height_count_iter.get_unwrap(i);
if count == StoredU64::default() {
unreachable!()
}
let supply = height_to_supply_iter.get_unwrap(height + (*count - 1));
(i, supply)
},
exit,
)?;
Ok(())
})?;
self.indexes_to_utxo_count.derive_from(
indexes,
starting_indexes,
&self.height_to_utxo_count,
exit,
)?;
Ok(())
self.supply
.compute_rest(indexes, price, starting_indexes, exit)
}
}

View File

@@ -1,18 +1,15 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{DateIndex, Dollars, Height, Sats};
use brk_types::{DateIndex, Dollars, Height};
use rayon::prelude::*;
use vecdb::{
AnyStoredVec, AnyVec, EagerVec, Exit, GenericStoredVec, ImportableVec, IterableCloneableVec,
LazyVecFrom1, LazyVecFrom2, Negate, PcoVec,
};
use vecdb::{AnyStoredVec, AnyVec, Exit, GenericStoredVec, Negate};
use crate::{
ComputeIndexes,
distribution::state::UnrealizedState,
internal::{
ComputedDateLast, DerivedDateLast, DollarsMinus, DollarsPlus, LazyDateLast,
LazyDerivedBlockValue, ValueDerivedDateLast,
ComputedHeightDateLast, DollarsMinus, DollarsPlus, LazyBinaryBlockLast, LazyBlockLast,
ValueBlockDateLast,
},
};
@@ -22,36 +19,19 @@ use super::ImportConfig;
#[derive(Clone, Traversable)]
pub struct UnrealizedMetrics {
// === Supply in Profit/Loss ===
pub height_to_supply_in_profit: EagerVec<PcoVec<Height, Sats>>,
pub indexes_to_supply_in_profit: ValueDerivedDateLast,
pub height_to_supply_in_loss: EagerVec<PcoVec<Height, Sats>>,
pub indexes_to_supply_in_loss: ValueDerivedDateLast,
pub dateindex_to_supply_in_profit: EagerVec<PcoVec<DateIndex, Sats>>,
pub dateindex_to_supply_in_loss: EagerVec<PcoVec<DateIndex, Sats>>,
pub height_to_supply_in_profit_value: LazyDerivedBlockValue,
pub height_to_supply_in_loss_value: LazyDerivedBlockValue,
pub supply_in_profit: ValueBlockDateLast,
pub supply_in_loss: ValueBlockDateLast,
// === Unrealized Profit/Loss ===
pub height_to_unrealized_profit: EagerVec<PcoVec<Height, Dollars>>,
pub indexes_to_unrealized_profit: DerivedDateLast<Dollars>,
pub height_to_unrealized_loss: EagerVec<PcoVec<Height, Dollars>>,
pub indexes_to_unrealized_loss: DerivedDateLast<Dollars>,
pub dateindex_to_unrealized_profit: EagerVec<PcoVec<DateIndex, Dollars>>,
pub dateindex_to_unrealized_loss: EagerVec<PcoVec<DateIndex, Dollars>>,
pub unrealized_profit: ComputedHeightDateLast<Dollars>,
pub unrealized_loss: ComputedHeightDateLast<Dollars>,
// === Negated and Net ===
pub height_to_neg_unrealized_loss: LazyVecFrom1<Height, Dollars, Height, Dollars>,
pub indexes_to_neg_unrealized_loss: LazyDateLast<Dollars>,
// === Negated ===
pub neg_unrealized_loss: LazyBlockLast<Dollars>,
// net = profit - loss (height is lazy, indexes computed)
pub height_to_net_unrealized_pnl:
LazyVecFrom2<Height, Dollars, Height, Dollars, Height, Dollars>,
pub indexes_to_net_unrealized_pnl: ComputedDateLast<Dollars>,
// total = profit + loss (height is lazy, indexes computed)
pub height_to_total_unrealized_pnl:
LazyVecFrom2<Height, Dollars, Height, Dollars, Height, Dollars>,
pub indexes_to_total_unrealized_pnl: ComputedDateLast<Dollars>,
// === Net and Total ===
pub net_unrealized_pnl: LazyBinaryBlockLast<Dollars>,
pub total_unrealized_pnl: LazyBinaryBlockLast<Dollars>,
}
impl UnrealizedMetrics {
@@ -59,154 +39,89 @@ impl UnrealizedMetrics {
pub fn forced_import(cfg: &ImportConfig) -> Result<Self> {
let compute_dollars = cfg.compute_dollars();
let dateindex_to_supply_in_profit =
EagerVec::forced_import(cfg.db, &cfg.name("supply_in_profit"), cfg.version)?;
let dateindex_to_supply_in_loss =
EagerVec::forced_import(cfg.db, &cfg.name("supply_in_loss"), cfg.version)?;
let dateindex_to_unrealized_profit =
EagerVec::forced_import(cfg.db, &cfg.name("unrealized_profit"), cfg.version)?;
let dateindex_to_unrealized_loss =
EagerVec::forced_import(cfg.db, &cfg.name("unrealized_loss"), cfg.version)?;
let height_to_unrealized_loss: EagerVec<PcoVec<Height, Dollars>> =
EagerVec::forced_import(cfg.db, &cfg.name("unrealized_loss"), cfg.version)?;
let height_to_neg_unrealized_loss = LazyVecFrom1::transformed::<Negate>(
&cfg.name("neg_unrealized_loss"),
// === Supply in Profit/Loss ===
let supply_in_profit = ValueBlockDateLast::forced_import(
cfg.db,
&cfg.name("supply_in_profit"),
cfg.version,
height_to_unrealized_loss.boxed_clone(),
);
let indexes_to_unrealized_loss = DerivedDateLast::from_source(
&cfg.name("unrealized_loss"),
cfg.version,
dateindex_to_unrealized_loss.boxed_clone(),
compute_dollars,
cfg.indexes,
);
let indexes_to_neg_unrealized_loss = LazyDateLast::from_derived::<Negate>(
&cfg.name("neg_unrealized_loss"),
cfg.price,
)?;
let supply_in_loss = ValueBlockDateLast::forced_import(
cfg.db,
&cfg.name("supply_in_loss"),
cfg.version,
dateindex_to_unrealized_loss.boxed_clone(),
&indexes_to_unrealized_loss,
);
compute_dollars,
cfg.indexes,
cfg.price,
)?;
// Extract profit sources for lazy net/total vecs
let height_to_unrealized_profit: EagerVec<PcoVec<Height, Dollars>> =
EagerVec::forced_import(cfg.db, &cfg.name("unrealized_profit"), cfg.version)?;
let indexes_to_unrealized_profit = DerivedDateLast::from_source(
// === Unrealized Profit/Loss ===
let unrealized_profit = ComputedHeightDateLast::forced_import(
cfg.db,
&cfg.name("unrealized_profit"),
cfg.version,
dateindex_to_unrealized_profit.boxed_clone(),
cfg.indexes,
);
// Create lazy height vecs from profit/loss sources
let height_to_net_unrealized_pnl = LazyVecFrom2::transformed::<DollarsMinus>(
&cfg.name("net_unrealized_pnl"),
cfg.version,
height_to_unrealized_profit.boxed_clone(),
height_to_unrealized_loss.boxed_clone(),
);
let height_to_total_unrealized_pnl = LazyVecFrom2::transformed::<DollarsPlus>(
&cfg.name("total_unrealized_pnl"),
cfg.version,
height_to_unrealized_profit.boxed_clone(),
height_to_unrealized_loss.boxed_clone(),
);
// indexes_to_net/total remain computed (needed by relative.rs)
let indexes_to_net_unrealized_pnl = ComputedDateLast::forced_import(
cfg.db,
&cfg.name("net_unrealized_pnl"),
cfg.version,
cfg.indexes,
)?;
let indexes_to_total_unrealized_pnl = ComputedDateLast::forced_import(
let unrealized_loss = ComputedHeightDateLast::forced_import(
cfg.db,
&cfg.name("total_unrealized_pnl"),
&cfg.name("unrealized_loss"),
cfg.version,
cfg.indexes,
)?;
let height_to_supply_in_profit: EagerVec<PcoVec<Height, Sats>> =
EagerVec::forced_import(cfg.db, &cfg.name("supply_in_profit"), cfg.version)?;
let height_to_supply_in_loss: EagerVec<PcoVec<Height, Sats>> =
EagerVec::forced_import(cfg.db, &cfg.name("supply_in_loss"), cfg.version)?;
let price_source = cfg
.price
.map(|p| p.usd.chainindexes_to_price_close.height.boxed_clone());
let height_to_supply_in_profit_value = LazyDerivedBlockValue::from_source(
&cfg.name("supply_in_profit"),
height_to_supply_in_profit.boxed_clone(),
// === Negated ===
let neg_unrealized_loss = LazyBlockLast::from_computed_height_date::<Negate>(
&cfg.name("neg_unrealized_loss"),
cfg.version,
price_source.clone(),
&unrealized_loss,
);
let height_to_supply_in_loss_value = LazyDerivedBlockValue::from_source(
&cfg.name("supply_in_loss"),
height_to_supply_in_loss.boxed_clone(),
// === Net and Total ===
let net_unrealized_pnl = LazyBinaryBlockLast::from_computed_height_date_last::<DollarsMinus>(
&cfg.name("net_unrealized_pnl"),
cfg.version,
price_source,
&unrealized_profit,
&unrealized_loss,
);
let total_unrealized_pnl = LazyBinaryBlockLast::from_computed_height_date_last::<DollarsPlus>(
&cfg.name("total_unrealized_pnl"),
cfg.version,
&unrealized_profit,
&unrealized_loss,
);
Ok(Self {
// === Supply in Profit/Loss ===
height_to_supply_in_profit,
indexes_to_supply_in_profit: ValueDerivedDateLast::from_source(
cfg.db,
&cfg.name("supply_in_profit"),
dateindex_to_supply_in_profit.boxed_clone(),
cfg.version,
compute_dollars,
cfg.indexes,
)?,
height_to_supply_in_loss,
indexes_to_supply_in_loss: ValueDerivedDateLast::from_source(
cfg.db,
&cfg.name("supply_in_loss"),
dateindex_to_supply_in_loss.boxed_clone(),
cfg.version,
compute_dollars,
cfg.indexes,
)?,
dateindex_to_supply_in_profit,
dateindex_to_supply_in_loss,
height_to_supply_in_profit_value,
height_to_supply_in_loss_value,
// === Unrealized Profit/Loss ===
height_to_unrealized_profit,
indexes_to_unrealized_profit,
height_to_unrealized_loss,
indexes_to_unrealized_loss,
dateindex_to_unrealized_profit,
dateindex_to_unrealized_loss,
height_to_neg_unrealized_loss,
indexes_to_neg_unrealized_loss,
height_to_net_unrealized_pnl,
indexes_to_net_unrealized_pnl,
height_to_total_unrealized_pnl,
indexes_to_total_unrealized_pnl,
supply_in_profit,
supply_in_loss,
unrealized_profit,
unrealized_loss,
neg_unrealized_loss,
net_unrealized_pnl,
total_unrealized_pnl,
})
}
/// Get minimum length across height-indexed vectors written in block loop.
pub fn min_stateful_height_len(&self) -> usize {
self.height_to_supply_in_profit
self.supply_in_profit
.height
.len()
.min(self.height_to_supply_in_loss.len())
.min(self.height_to_unrealized_profit.len())
.min(self.height_to_unrealized_loss.len())
.min(self.supply_in_loss.height.len())
.min(self.unrealized_profit.height.len())
.min(self.unrealized_loss.height.len())
}
/// Get minimum length across dateindex-indexed vectors written in block loop.
pub fn min_stateful_dateindex_len(&self) -> usize {
self.dateindex_to_supply_in_profit
self.supply_in_profit
.indexes
.sats_dateindex
.len()
.min(self.dateindex_to_supply_in_loss.len())
.min(self.dateindex_to_unrealized_profit.len())
.min(self.dateindex_to_unrealized_loss.len())
.min(self.supply_in_loss.indexes.sats_dateindex.len())
.min(self.unrealized_profit.dateindex.len())
.min(self.unrealized_loss.dateindex.len())
}
/// Push unrealized state values to height-indexed vectors.
@@ -217,23 +132,33 @@ impl UnrealizedMetrics {
height_state: &UnrealizedState,
date_state: Option<&UnrealizedState>,
) -> Result<()> {
self.height_to_supply_in_profit
self.supply_in_profit
.height
.truncate_push(height, height_state.supply_in_profit)?;
self.height_to_supply_in_loss
self.supply_in_loss
.height
.truncate_push(height, height_state.supply_in_loss)?;
self.height_to_unrealized_profit
self.unrealized_profit
.height
.truncate_push(height, height_state.unrealized_profit)?;
self.height_to_unrealized_loss
self.unrealized_loss
.height
.truncate_push(height, height_state.unrealized_loss)?;
if let (Some(dateindex), Some(date_state)) = (dateindex, date_state) {
self.dateindex_to_supply_in_profit
self.supply_in_profit
.indexes
.sats_dateindex
.truncate_push(dateindex, date_state.supply_in_profit)?;
self.dateindex_to_supply_in_loss
self.supply_in_loss
.indexes
.sats_dateindex
.truncate_push(dateindex, date_state.supply_in_loss)?;
self.dateindex_to_unrealized_profit
self.unrealized_profit
.dateindex
.truncate_push(dateindex, date_state.unrealized_profit)?;
self.dateindex_to_unrealized_loss
self.unrealized_loss
.dateindex
.truncate_push(dateindex, date_state.unrealized_loss)?;
}
@@ -242,28 +167,28 @@ impl UnrealizedMetrics {
/// Write height-indexed vectors to disk.
pub fn write(&mut self) -> Result<()> {
self.height_to_supply_in_profit.write()?;
self.height_to_supply_in_loss.write()?;
self.height_to_unrealized_profit.write()?;
self.height_to_unrealized_loss.write()?;
self.dateindex_to_supply_in_profit.write()?;
self.dateindex_to_supply_in_loss.write()?;
self.dateindex_to_unrealized_profit.write()?;
self.dateindex_to_unrealized_loss.write()?;
self.supply_in_profit.height.write()?;
self.supply_in_loss.height.write()?;
self.unrealized_profit.height.write()?;
self.unrealized_loss.height.write()?;
self.supply_in_profit.indexes.sats_dateindex.write()?;
self.supply_in_loss.indexes.sats_dateindex.write()?;
self.unrealized_profit.dateindex.write()?;
self.unrealized_loss.dateindex.write()?;
Ok(())
}
/// Returns a parallel iterator over all vecs for parallel writing.
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
vec![
&mut self.height_to_supply_in_profit as &mut dyn AnyStoredVec,
&mut self.height_to_supply_in_loss as &mut dyn AnyStoredVec,
&mut self.height_to_unrealized_profit as &mut dyn AnyStoredVec,
&mut self.height_to_unrealized_loss as &mut dyn AnyStoredVec,
&mut self.dateindex_to_supply_in_profit as &mut dyn AnyStoredVec,
&mut self.dateindex_to_supply_in_loss as &mut dyn AnyStoredVec,
&mut self.dateindex_to_unrealized_profit as &mut dyn AnyStoredVec,
&mut self.dateindex_to_unrealized_loss as &mut dyn AnyStoredVec,
&mut self.supply_in_profit.height as &mut dyn AnyStoredVec,
&mut self.supply_in_loss.height as &mut dyn AnyStoredVec,
&mut self.unrealized_profit.height as &mut dyn AnyStoredVec,
&mut self.unrealized_loss.height as &mut dyn AnyStoredVec,
&mut self.supply_in_profit.indexes.sats_dateindex as &mut dyn AnyStoredVec,
&mut self.supply_in_loss.indexes.sats_dateindex as &mut dyn AnyStoredVec,
&mut self.unrealized_profit.rest.dateindex as &mut dyn AnyStoredVec,
&mut self.unrealized_loss.rest.dateindex as &mut dyn AnyStoredVec,
]
.into_par_iter()
}
@@ -275,67 +200,73 @@ impl UnrealizedMetrics {
others: &[&Self],
exit: &Exit,
) -> Result<()> {
self.height_to_supply_in_profit.compute_sum_of_others(
self.supply_in_profit.height.compute_sum_of_others(
starting_indexes.height,
&others
.iter()
.map(|v| &v.height_to_supply_in_profit)
.map(|v| &v.supply_in_profit.height)
.collect::<Vec<_>>(),
exit,
)?;
self.height_to_supply_in_loss.compute_sum_of_others(
self.supply_in_loss.height.compute_sum_of_others(
starting_indexes.height,
&others
.iter()
.map(|v| &v.height_to_supply_in_loss)
.map(|v| &v.supply_in_loss.height)
.collect::<Vec<_>>(),
exit,
)?;
self.height_to_unrealized_profit.compute_sum_of_others(
self.unrealized_profit.height.compute_sum_of_others(
starting_indexes.height,
&others
.iter()
.map(|v| &v.height_to_unrealized_profit)
.map(|v| &v.unrealized_profit.height)
.collect::<Vec<_>>(),
exit,
)?;
self.height_to_unrealized_loss.compute_sum_of_others(
self.unrealized_loss.height.compute_sum_of_others(
starting_indexes.height,
&others
.iter()
.map(|v| &v.height_to_unrealized_loss)
.map(|v| &v.unrealized_loss.height)
.collect::<Vec<_>>(),
exit,
)?;
self.dateindex_to_supply_in_profit.compute_sum_of_others(
self.supply_in_profit
.indexes
.sats_dateindex
.compute_sum_of_others(
starting_indexes.dateindex,
&others
.iter()
.map(|v| &v.supply_in_profit.indexes.sats_dateindex)
.collect::<Vec<_>>(),
exit,
)?;
self.supply_in_loss
.indexes
.sats_dateindex
.compute_sum_of_others(
starting_indexes.dateindex,
&others
.iter()
.map(|v| &v.supply_in_loss.indexes.sats_dateindex)
.collect::<Vec<_>>(),
exit,
)?;
self.unrealized_profit.dateindex.compute_sum_of_others(
starting_indexes.dateindex,
&others
.iter()
.map(|v| &v.dateindex_to_supply_in_profit)
.map(|v| &v.unrealized_profit.dateindex)
.collect::<Vec<_>>(),
exit,
)?;
self.dateindex_to_supply_in_loss.compute_sum_of_others(
self.unrealized_loss.dateindex.compute_sum_of_others(
starting_indexes.dateindex,
&others
.iter()
.map(|v| &v.dateindex_to_supply_in_loss)
.collect::<Vec<_>>(),
exit,
)?;
self.dateindex_to_unrealized_profit.compute_sum_of_others(
starting_indexes.dateindex,
&others
.iter()
.map(|v| &v.dateindex_to_unrealized_profit)
.collect::<Vec<_>>(),
exit,
)?;
self.dateindex_to_unrealized_loss.compute_sum_of_others(
starting_indexes.dateindex,
&others
.iter()
.map(|v| &v.dateindex_to_unrealized_loss)
.map(|v| &v.unrealized_loss.dateindex)
.collect::<Vec<_>>(),
exit,
)?;
@@ -349,39 +280,11 @@ impl UnrealizedMetrics {
starting_indexes: &ComputeIndexes,
exit: &Exit,
) -> Result<()> {
// KISS: compute_rest doesn't need source vec - lazy vecs are set up during import
self.indexes_to_supply_in_profit
.compute_rest(price, starting_indexes, exit)?;
self.supply_in_profit
.compute_dollars_from_price(price, starting_indexes, exit)?;
self.indexes_to_supply_in_loss
.compute_rest(price, starting_indexes, exit)?;
// indexes_to_unrealized_profit/loss are Derived - no compute needed (lazy only)
// height_to_net/total are lazy, but indexes still need compute
// total_unrealized_pnl = profit + loss
self.indexes_to_total_unrealized_pnl
.compute_all(starting_indexes, exit, |vec| {
vec.compute_add(
starting_indexes.dateindex,
&self.dateindex_to_unrealized_profit,
&self.dateindex_to_unrealized_loss,
exit,
)?;
Ok(())
})?;
// net_unrealized_pnl = profit - loss
self.indexes_to_net_unrealized_pnl
.compute_all(starting_indexes, exit, |vec| {
vec.compute_subtract(
starting_indexes.dateindex,
&self.dateindex_to_unrealized_profit,
&self.dateindex_to_unrealized_loss,
exit,
)?;
Ok(())
})?;
self.supply_in_loss
.compute_dollars_from_price(price, starting_indexes, exit)?;
Ok(())
}

View File

@@ -4,10 +4,10 @@ use brk_error::Result;
use brk_indexer::Indexer;
use brk_traversable::Traversable;
use brk_types::{
EmptyAddressData, EmptyAddressIndex, Height, LoadedAddressData, LoadedAddressIndex, StoredU64,
EmptyAddressData, EmptyAddressIndex, Height, LoadedAddressData, LoadedAddressIndex,
SupplyState, Version,
};
use log::info;
use tracing::info;
use vecdb::{
AnyVec, BytesVec, Database, Exit, GenericStoredVec, ImportableVec, IterableCloneableVec,
LazyVecFrom1, PAGE_SIZE, Stamp, TypedVecIterator, VecIndex,
@@ -19,14 +19,11 @@ use crate::{
compute::{StartMode, determine_start_mode, process_blocks, recover_state, reset_state},
state::BlockState,
},
indexes, inputs,
internal::ComputedBlockLast,
outputs, price, transactions,
indexes, inputs, outputs, price, transactions,
};
use super::{
AddressCohorts, AddressesDataVecs, AnyAddressIndexesVecs, UTXOCohorts,
address::{AddressTypeToHeightToAddressCount, AddressTypeToIndexesToAddressCount},
AddressCohorts, AddressesDataVecs, AnyAddressIndexesVecs, UTXOCohorts, address::AddrCountVecs,
compute::aggregates,
};
@@ -44,13 +41,8 @@ pub struct Vecs {
pub utxo_cohorts: UTXOCohorts,
pub address_cohorts: AddressCohorts,
pub addresstype_to_height_to_addr_count: AddressTypeToHeightToAddressCount,
pub addresstype_to_height_to_empty_addr_count: AddressTypeToHeightToAddressCount,
pub addresstype_to_indexes_to_addr_count: AddressTypeToIndexesToAddressCount,
pub addresstype_to_indexes_to_empty_addr_count: AddressTypeToIndexesToAddressCount,
pub indexes_to_addr_count: ComputedBlockLast<StoredU64>,
pub indexes_to_empty_addr_count: ComputedBlockLast<StoredU64>,
pub addr_count: AddrCountVecs,
pub empty_addr_count: AddrCountVecs,
pub loadedaddressindex_to_loadedaddressindex:
LazyVecFrom1<LoadedAddressIndex, LoadedAddressIndex, LoadedAddressIndex, LoadedAddressData>,
pub emptyaddressindex_to_emptyaddressindex:
@@ -111,50 +103,20 @@ impl Vecs {
|index, _| Some(index),
);
// Extract address type height vecs before struct literal to use as sources
let addresstype_to_height_to_addr_count =
AddressTypeToHeightToAddressCount::forced_import(&db, "addr_count", version)?;
let addresstype_to_height_to_empty_addr_count =
AddressTypeToHeightToAddressCount::forced_import(&db, "empty_addr_count", version)?;
let this = Self {
chain_state: BytesVec::forced_import_with(
vecdb::ImportOptions::new(&db, "chain", version)
.with_saved_stamped_changes(SAVED_STAMPED_CHANGES),
)?,
indexes_to_addr_count: ComputedBlockLast::forced_import(
&db,
"addr_count",
version,
indexes,
)?,
indexes_to_empty_addr_count: ComputedBlockLast::forced_import(
addr_count: AddrCountVecs::forced_import(&db, "addr_count", version, indexes)?,
empty_addr_count: AddrCountVecs::forced_import(
&db,
"empty_addr_count",
version,
indexes,
)?,
addresstype_to_indexes_to_addr_count:
AddressTypeToIndexesToAddressCount::forced_import(
&db,
"addr_count",
version,
indexes,
&addresstype_to_height_to_addr_count,
)?,
addresstype_to_indexes_to_empty_addr_count:
AddressTypeToIndexesToAddressCount::forced_import(
&db,
"empty_addr_count",
version,
indexes,
&addresstype_to_height_to_empty_addr_count,
)?,
addresstype_to_height_to_addr_count,
addresstype_to_height_to_empty_addr_count,
utxo_cohorts,
address_cohorts,
@@ -200,14 +162,22 @@ impl Vecs {
starting_indexes: &mut ComputeIndexes,
exit: &Exit,
) -> Result<()> {
// 1. Find minimum computed height for recovery
let chain_state_height = Height::from(self.chain_state.len());
// 1. Find minimum height we have data for across stateful vecs
let current_height = Height::from(self.chain_state.len());
let height_based_min = self.min_stateful_height_len();
let dateindex_min = self.min_stateful_dateindex_len();
let stateful_min = adjust_for_dateindex_gap(height_based_min, dateindex_min, indexes)?;
let min_stateful = adjust_for_dateindex_gap(height_based_min, dateindex_min, indexes)?;
// 2. Determine start mode and recover/reset state
let start_mode = determine_start_mode(stateful_min, chain_state_height);
// Clamp to starting_indexes.height to handle reorg (indexer may require earlier start)
let resume_target = current_height.min(starting_indexes.height);
if resume_target < current_height {
info!(
"Reorg detected: rolling back from {} to {}",
current_height, resume_target
);
}
let start_mode = determine_start_mode(min_stateful.min(resume_target), resume_target);
// Try to resume from checkpoint, fall back to fresh start if needed
let recovered_height = match start_mode {
@@ -238,8 +208,8 @@ impl Vecs {
// Fresh start: reset all state
let (starting_height, mut chain_state) = if recovered_height.is_zero() {
self.chain_state.reset()?;
self.addresstype_to_height_to_addr_count.reset()?;
self.addresstype_to_height_to_empty_addr_count.reset()?;
self.addr_count.reset_height()?;
self.empty_addr_count.reset_height()?;
reset_state(
&mut self.any_address_indexes,
&mut self.addresses_data,
@@ -251,8 +221,8 @@ impl Vecs {
(Height::ZERO, vec![])
} else {
// Recover chain_state from stored values
let height_to_timestamp = &blocks.time.height_to_timestamp_fixed;
let height_to_price = price.map(|p| &p.usd.chainindexes_to_price_close.height);
let height_to_timestamp = &blocks.time.timestamp_fixed;
let height_to_price = price.map(|p| &p.usd.split.close.height);
let mut height_to_timestamp_iter = height_to_timestamp.into_iter();
let mut height_to_price_iter = height_to_price.map(|v| v.into_iter());
@@ -279,14 +249,7 @@ impl Vecs {
.validate_computed_versions(base_version)?;
// 3. Get last height from indexer
let last_height = Height::from(
indexer
.vecs
.block
.height_to_blockhash
.len()
.saturating_sub(1),
);
let last_height = Height::from(indexer.vecs.blocks.blockhash.len().saturating_sub(1));
// 4. Process blocks
if starting_height <= last_height {
@@ -324,64 +287,26 @@ impl Vecs {
exit,
)?;
// 6b. Compute address count dateindex vecs (per-addresstype)
self.addresstype_to_indexes_to_addr_count.compute(
indexes,
starting_indexes,
exit,
&self.addresstype_to_height_to_addr_count,
)?;
self.addresstype_to_indexes_to_empty_addr_count.compute(
indexes,
starting_indexes,
exit,
&self.addresstype_to_height_to_empty_addr_count,
)?;
// 6c. Compute global address count dateindex vecs (sum of all address types)
let addr_count_sources: Vec<_> =
self.addresstype_to_height_to_addr_count.values().collect();
self.indexes_to_addr_count
.compute_all(indexes, starting_indexes, exit, |height_vec| {
Ok(height_vec.compute_sum_of_others(
starting_indexes.height,
&addr_count_sources,
exit,
)?)
})?;
let empty_addr_count_sources: Vec<_> = self
.addresstype_to_height_to_empty_addr_count
.values()
.collect();
self.indexes_to_empty_addr_count.compute_all(
indexes,
starting_indexes,
exit,
|height_vec| {
Ok(height_vec.compute_sum_of_others(
starting_indexes.height,
&empty_addr_count_sources,
exit,
)?)
},
)?;
// 6b. Compute address count dateindex vecs (by addresstype + all)
self.addr_count
.compute_rest(indexes, starting_indexes, exit)?;
self.empty_addr_count
.compute_rest(indexes, starting_indexes, exit)?;
// 7. Compute rest part2 (relative metrics)
let supply_metrics = &self.utxo_cohorts.all.metrics.supply;
let height_to_market_cap = supply_metrics
.height_to_supply_value
.supply
.dollars
.as_ref()
.cloned();
.map(|d| d.height.clone());
// KISS: dateindex is no longer Option, just clone directly
let dateindex_to_market_cap = supply_metrics
.indexes_to_supply
.supply
.dollars
.as_ref()
.map(|v| v.dateindex.clone());
.map(|d| d.dateindex.0.clone());
let height_to_market_cap_ref = height_to_market_cap.as_ref();
let dateindex_to_market_cap_ref = dateindex_to_market_cap.as_ref();
@@ -415,12 +340,8 @@ impl Vecs {
.min(Height::from(self.chain_state.len()))
.min(self.any_address_indexes.min_stamped_height())
.min(self.addresses_data.min_stamped_height())
.min(Height::from(
self.addresstype_to_height_to_addr_count.min_len(),
))
.min(Height::from(
self.addresstype_to_height_to_empty_addr_count.min_len(),
))
.min(Height::from(self.addr_count.min_len()))
.min(Height::from(self.empty_addr_count.min_len()))
}
/// Get minimum length across all dateindex-indexed stateful vectors.
@@ -446,25 +367,25 @@ fn adjust_for_dateindex_gap(
return Ok(height_based_min);
}
// Skip if height_to_dateindex doesn't cover height_based_min yet
if height_based_min.to_usize() >= indexes.block.height_to_dateindex.len() {
// Skip if height.dateindex doesn't cover height_based_min yet
if height_based_min.to_usize() >= indexes.height.dateindex.len() {
return Ok(height_based_min);
}
// Get the dateindex at the height we want to resume at
let required_dateindex: usize = indexes
.block
.height_to_dateindex
.height
.dateindex
.read_once(height_based_min)?
.into();
// If dateindex vecs are behind, restart from first height of the missing day
if dateindex_min < required_dateindex
&& dateindex_min < indexes.time.dateindex_to_first_height.len()
&& dateindex_min < indexes.dateindex.first_height.len()
{
Ok(indexes
.time
.dateindex_to_first_height
.dateindex
.first_height
.read_once(dateindex_min.into())?)
} else {
Ok(height_based_min)