mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-05-24 08:44:46 -07:00
global: big snapshot
This commit is contained in:
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)?;
|
||||
}
|
||||
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
))
|
||||
|
||||
@@ -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,
|
||||
)?;
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
81
crates/brk_computer/src/distribution/metrics/outputs.rs
Normal file
81
crates/brk_computer/src/distribution/metrics/outputs.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
)?;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user