computer: snapshot

This commit is contained in:
nym21
2026-01-09 22:02:34 +01:00
parent 426d7797a3
commit 3a3f6b8593
43 changed files with 3879 additions and 4097 deletions

View File

@@ -13,25 +13,23 @@ impl Vecs {
starting_indexes: &ComputeIndexes,
exit: &Exit,
) -> Result<()> {
let mut height_to_difficultyepoch_iter =
indexes.height.difficultyepoch.into_iter();
self.difficultyepoch
.compute_all(starting_indexes, exit, |vec| {
let mut height_count_iter = indexes.dateindex.height_count.into_iter();
vec.compute_transform(
starting_indexes.dateindex,
&indexes.dateindex.first_height,
|(di, height, ..)| {
(
di,
height_to_difficultyepoch_iter
.get_unwrap(height + (*height_count_iter.get_unwrap(di) - 1)),
)
},
exit,
)?;
Ok(())
})?;
let mut height_to_difficultyepoch_iter = indexes.height.difficultyepoch.into_iter();
self.epoch.compute_all(starting_indexes, exit, |vec| {
let mut height_count_iter = indexes.dateindex.height_count.into_iter();
vec.compute_transform(
starting_indexes.dateindex,
&indexes.dateindex.first_height,
|(di, height, ..)| {
(
di,
height_to_difficultyepoch_iter
.get_unwrap(height + (*height_count_iter.get_unwrap(di) - 1)),
)
},
exit,
)?;
Ok(())
})?;
self.blocks_before_next_difficulty_adjustment.compute_all(
indexes,

View File

@@ -13,12 +13,7 @@ impl Vecs {
let v2 = Version::TWO;
Ok(Self {
difficultyepoch: ComputedDateLast::forced_import(
db,
"difficultyepoch",
version,
indexes,
)?,
epoch: ComputedDateLast::forced_import(db, "difficultyepoch", version, indexes)?,
blocks_before_next_difficulty_adjustment: ComputedBlockLast::forced_import(
db,
"blocks_before_next_difficulty_adjustment",

View File

@@ -6,7 +6,7 @@ use crate::internal::{ComputedBlockLast, ComputedDateLast};
/// Difficulty epoch metrics and countdown
#[derive(Clone, Traversable)]
pub struct Vecs {
pub difficultyepoch: ComputedDateLast<DifficultyEpoch>,
pub epoch: ComputedDateLast<DifficultyEpoch>,
pub blocks_before_next_difficulty_adjustment: ComputedBlockLast<StoredU32>,
pub days_before_next_difficulty_adjustment: ComputedBlockLast<StoredF32>,
}

View File

@@ -14,23 +14,22 @@ impl Vecs {
exit: &Exit,
) -> Result<()> {
let mut height_to_halvingepoch_iter = indexes.height.halvingepoch.into_iter();
self.halvingepoch
.compute_all(starting_indexes, exit, |vec| {
let mut height_count_iter = indexes.dateindex.height_count.into_iter();
vec.compute_transform(
starting_indexes.dateindex,
&indexes.dateindex.first_height,
|(di, height, ..)| {
(
di,
height_to_halvingepoch_iter
.get_unwrap(height + (*height_count_iter.get_unwrap(di) - 1)),
)
},
exit,
)?;
Ok(())
})?;
self.epoch.compute_all(starting_indexes, exit, |vec| {
let mut height_count_iter = indexes.dateindex.height_count.into_iter();
vec.compute_transform(
starting_indexes.dateindex,
&indexes.dateindex.first_height,
|(di, height, ..)| {
(
di,
height_to_halvingepoch_iter
.get_unwrap(height + (*height_count_iter.get_unwrap(di) - 1)),
)
},
exit,
)?;
Ok(())
})?;
self.blocks_before_next_halving
.compute_all(indexes, starting_indexes, exit, |v| {

View File

@@ -13,7 +13,7 @@ impl Vecs {
let v2 = Version::TWO;
Ok(Self {
halvingepoch: ComputedDateLast::forced_import(db, "halvingepoch", version, indexes)?,
epoch: ComputedDateLast::forced_import(db, "halvingepoch", version, indexes)?,
blocks_before_next_halving: ComputedBlockLast::forced_import(
db,
"blocks_before_next_halving",

View File

@@ -6,7 +6,7 @@ use crate::internal::{ComputedBlockLast, ComputedDateLast};
/// Halving epoch metrics and countdown
#[derive(Clone, Traversable)]
pub struct Vecs {
pub halvingepoch: ComputedDateLast<HalvingEpoch>,
pub epoch: ComputedDateLast<HalvingEpoch>,
pub blocks_before_next_halving: ComputedBlockLast<StoredU32>,
pub days_before_next_halving: ComputedBlockLast<StoredF32>,
}

View File

@@ -18,7 +18,7 @@ impl Vecs {
.all
.metrics
.supply
.supply
.total
.sats
.height;

View File

@@ -32,7 +32,7 @@ impl Vecs {
.all
.metrics
.supply
.supply
.total
.bitcoin
.height;

View File

@@ -23,7 +23,7 @@ impl Vecs {
.all
.metrics
.supply
.supply
.total
.bitcoin
.height;
let realized_price = &distribution

View File

@@ -20,7 +20,7 @@ impl Vecs {
.all
.metrics
.supply
.supply
.total
.sats
.height;

View File

@@ -225,6 +225,7 @@ impl AddressTypeToAddrCountVecs {
#[derive(Clone, Traversable)]
pub struct AddrCountVecs {
pub all: ComputedBlockLast<StoredU64>,
#[traversable(flatten)]
pub by_addresstype: AddressTypeToAddrCountVecs,
}

View File

@@ -8,10 +8,7 @@ use rayon::prelude::*;
use vecdb::{AnyStoredVec, AnyVec, Database, Exit, GenericStoredVec, IterableVec};
use crate::{
ComputeIndexes,
distribution::state::AddressCohortState,
indexes,
internal::ComputedBlockLast,
ComputeIndexes, distribution::state::AddressCohortState, indexes, internal::ComputedBlockLast,
price,
};
@@ -155,7 +152,7 @@ impl DynCohortVecs for AddressCohortVecs {
state.inner.supply.value = self
.metrics
.supply
.supply
.total
.sats
.height
.read_once(prev_height)?;

View File

@@ -7,7 +7,7 @@ use brk_types::{DateIndex, Dollars, Height, Version};
use rayon::prelude::*;
use vecdb::{AnyStoredVec, Database, Exit, IterableVec};
use crate::{ComputeIndexes, indexes, price, distribution::state::UTXOCohortState};
use crate::{ComputeIndexes, distribution::state::UTXOCohortState, indexes, price};
use crate::distribution::metrics::{CohortMetrics, ImportConfig, RealizedMetrics, SupplyMetrics};
@@ -149,7 +149,7 @@ impl DynCohortVecs for UTXOCohortVecs {
state.supply.value = self
.metrics
.supply
.supply
.total
.sats
.height
.read_once(prev_height)?;

View File

@@ -92,7 +92,11 @@ 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.outputs.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());
@@ -124,7 +128,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.value)?;
self.outputs.truncate_push(height, state.supply.utxo_count)?;
self.outputs
.truncate_push(height, state.supply.utxo_count)?;
self.activity.truncate_push(
height,
state.sent,
@@ -309,8 +314,7 @@ impl CohortMetrics {
) -> Result<()> {
self.supply
.compute_rest_part1(indexes, price, starting_indexes, exit)?;
self.outputs
.compute_rest(indexes, starting_indexes, exit)?;
self.outputs.compute_rest(indexes, starting_indexes, exit)?;
self.activity
.compute_rest_part1(indexes, starting_indexes, exit)?;
@@ -345,7 +349,7 @@ impl CohortMetrics {
indexes,
price,
starting_indexes,
&self.supply.supply.bitcoin.height,
&self.supply.total.bitcoin.height,
height_to_market_cap,
dateindex_to_market_cap,
exit,

View File

@@ -77,14 +77,14 @@ impl RelativeMetrics {
let compute_rel_to_all = cfg.compute_rel_to_all();
// Global sources from "all" cohort
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());
let global_supply_sats_height = all_supply.map(|s| &s.total.sats.height);
let global_supply_sats_difficultyepoch = all_supply.map(|s| &s.total.sats.difficultyepoch);
let global_supply_sats_dates = all_supply.map(|s| &s.total.sats.rest.dates);
let global_supply_sats_dateindex = all_supply.map(|s| &s.total.sats.rest.dateindex);
let global_market_cap = all_supply.and_then(|s| s.total.dollars.as_ref());
// Own market cap source
let own_market_cap = supply.supply.dollars.as_ref();
let own_market_cap = supply.total.dollars.as_ref();
Ok(Self {
// === Supply Relative to Circulating Supply (lazy from global supply) ===
@@ -94,8 +94,8 @@ impl RelativeMetrics {
LazyBinaryDateLast::from_both_derived_last::<PercentageSatsF64>(
&cfg.name("supply_rel_to_circulating_supply"),
cfg.version + v1,
supply.supply.sats.rest.dateindex.boxed_clone(),
&supply.supply.sats.rest.dates,
supply.total.sats.rest.dateindex.boxed_clone(),
&supply.total.sats.rest.dates,
global_supply_sats_dateindex.unwrap().boxed_clone(),
global_supply_sats_dates.unwrap(),
)
@@ -107,34 +107,34 @@ impl RelativeMetrics {
&cfg.name("supply_in_profit_rel_to_own_supply"),
cfg.version + v1,
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(),
supply.total.sats.height.boxed_clone(),
unrealized.supply_in_profit.difficultyepoch.sats.boxed_clone(),
supply.total.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,
supply.total.sats.rest.dateindex.boxed_clone(),
&supply.total.sats.rest.dates,
),
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.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(),
supply.total.sats.height.boxed_clone(),
unrealized.supply_in_loss.difficultyepoch.sats.boxed_clone(),
supply.total.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.total.sats.rest.dateindex.boxed_clone(),
&supply.total.sats.rest.dates,
),
// === Supply in Profit/Loss Relative to Circulating Supply (lazy from global supply) ===
@@ -146,7 +146,7 @@ impl RelativeMetrics {
cfg.version + v1,
unrealized.supply_in_profit.height.boxed_clone(),
global_supply_sats_height.unwrap().boxed_clone(),
unrealized.supply_in_profit.difficultyepoch.boxed_clone(),
unrealized.supply_in_profit.difficultyepoch.sats.boxed_clone(),
global_supply_sats_difficultyepoch.unwrap().boxed_clone(),
unrealized
.supply_in_profit
@@ -166,7 +166,7 @@ impl RelativeMetrics {
cfg.version + v1,
unrealized.supply_in_loss.height.boxed_clone(),
global_supply_sats_height.unwrap().boxed_clone(),
unrealized.supply_in_loss.difficultyepoch.boxed_clone(),
unrealized.supply_in_loss.difficultyepoch.sats.boxed_clone(),
global_supply_sats_difficultyepoch.unwrap().boxed_clone(),
unrealized
.supply_in_loss

View File

@@ -4,13 +4,13 @@ use brk_types::{Height, Sats, Version};
use crate::ComputeIndexes;
use rayon::prelude::*;
use vecdb::{AnyStoredVec, AnyVec, Exit, GenericStoredVec, IterableCloneableVec};
use vecdb::{AnyStoredVec, AnyVec, Exit, GenericStoredVec};
use crate::{
indexes,
internal::{
HalfClosePriceTimesSats, HalveDollars, HalveSats, HalveSatsToBitcoin, LazyBlockValue,
LazyValueDateLast, ValueBlockLast,
HalfClosePriceTimesSats, HalveDollars, HalveSats, HalveSatsToBitcoin, LazyBinaryLastBlockValue,
ValueBlockLast,
},
price,
};
@@ -20,9 +20,8 @@ use super::ImportConfig;
/// Supply metrics for a cohort.
#[derive(Clone, Traversable)]
pub struct SupplyMetrics {
pub supply: ValueBlockLast,
pub supply_half_value: LazyBlockValue,
pub supply_half: LazyValueDateLast,
pub total: ValueBlockLast,
pub halved: LazyBinaryLastBlockValue,
}
impl SupplyMetrics {
@@ -38,52 +37,39 @@ impl SupplyMetrics {
compute_dollars,
)?;
let price_source = cfg
.price
.map(|p| p.usd.split.close.height.boxed_clone());
// Create lazy supply_half from supply sources
let supply_half_value =
LazyBlockValue::from_sources::<HalveSats, HalveSatsToBitcoin, HalfClosePriceTimesSats>(
&cfg.name("supply_half"),
supply.sats.height.boxed_clone(),
price_source,
cfg.version,
);
let supply_half = LazyValueDateLast::from_block_source::<
let supply_half = LazyBinaryLastBlockValue::from_block_source::<
HalveSats,
HalveSatsToBitcoin,
HalfClosePriceTimesSats,
HalveDollars,
>(&cfg.name("supply_half"), &supply, cfg.version);
>(&cfg.name("supply_half"), &supply, cfg.price, cfg.version);
Ok(Self {
supply,
supply_half_value,
supply_half,
total: supply,
halved: supply_half,
})
}
/// Get minimum length across height-indexed vectors.
pub fn min_len(&self) -> usize {
self.supply.sats.height.len()
self.total.sats.height.len()
}
/// Push supply state values to height-indexed vectors.
pub fn truncate_push(&mut self, height: Height, supply: Sats) -> Result<()> {
self.supply.sats.height.truncate_push(height, supply)?;
self.total.sats.height.truncate_push(height, supply)?;
Ok(())
}
/// Write height-indexed vectors to disk.
pub fn write(&mut self) -> Result<()> {
self.supply.sats.height.write()?;
self.total.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.supply.sats.height as &mut dyn AnyStoredVec].into_par_iter()
vec![&mut self.total.sats.height as &mut dyn AnyStoredVec].into_par_iter()
}
/// Validate computed versions against base version.
@@ -99,11 +85,11 @@ impl SupplyMetrics {
others: &[&Self],
exit: &Exit,
) -> Result<()> {
self.supply.sats.height.compute_sum_of_others(
self.total.sats.height.compute_sum_of_others(
starting_indexes.height,
&others
.iter()
.map(|v| &v.supply.sats.height)
.map(|v| &v.total.sats.height)
.collect::<Vec<_>>(),
exit,
)?;
@@ -118,7 +104,7 @@ impl SupplyMetrics {
starting_indexes: &ComputeIndexes,
exit: &Exit,
) -> Result<()> {
self.supply
self.total
.compute_rest(indexes, price, starting_indexes, exit)
}
}

View File

@@ -297,13 +297,13 @@ impl Vecs {
let supply_metrics = &self.utxo_cohorts.all.metrics.supply;
let height_to_market_cap = supply_metrics
.supply
.total
.dollars
.as_ref()
.map(|d| d.height.clone());
let dateindex_to_market_cap = supply_metrics
.supply
.total
.dollars
.as_ref()
.map(|d| d.dateindex.0.clone());
@@ -373,16 +373,10 @@ fn adjust_for_dateindex_gap(
}
// Get the dateindex at the height we want to resume at
let required_dateindex: usize = indexes
.height
.dateindex
.read_once(height_based_min)?
.into();
let required_dateindex: usize = indexes.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.dateindex.first_height.len()
{
if dateindex_min < required_dateindex && dateindex_min < indexes.dateindex.first_height.len() {
Ok(indexes
.dateindex
.first_height

View File

@@ -29,7 +29,7 @@ where
S1T: ComputedVecValue,
S2T: ComputedVecValue,
{
#[traversable(wrap = "sum")]
#[traversable(rename = "sum")]
pub height: LazyVecFrom2<Height, T, Height, S1T, Height, S2T>,
#[deref]
#[deref_mut]

View File

@@ -21,7 +21,7 @@ pub struct ComputedBlockSumCum<T>
where
T: ComputedVecValue + PartialOrd + JsonSchema,
{
#[traversable(wrap = "sum")]
#[traversable(rename = "sum")]
pub height: EagerVec<PcoVec<Height, T>>,
#[deref]
#[deref_mut]

View File

@@ -20,7 +20,7 @@ where
S1T: ComputedVecValue,
S2T: ComputedVecValue,
{
#[traversable(wrap = "sum")]
#[traversable(rename = "sum")]
pub height: LazyVecFrom2<Height, T, Height, S1T, Height, S2T>,
#[deref]
#[deref_mut]

View File

@@ -21,7 +21,7 @@ where
S1T: ComputedVecValue,
S2T: ComputedVecValue,
{
#[traversable(wrap = "sum")]
#[traversable(rename = "sum")]
pub height: LazyVecFrom2<Height, T, Height, S1T, Height, S2T>,
#[traversable(rename = "cumulative")]
pub height_cumulative: LazyVecFrom2<Height, T, Height, S1T, Height, S2T>,

View File

@@ -18,7 +18,7 @@ where
T: ComputedVecValue + PartialOrd + JsonSchema,
S1T: ComputedVecValue,
{
#[traversable(wrap = "sum")]
#[traversable(rename = "sum")]
pub height: LazyVecFrom1<Height, T, Height, S1T>,
#[deref]
#[deref_mut]

View File

@@ -11,7 +11,7 @@ use vecdb::{
use crate::internal::{ComputedVecValue, LastVec, LazyLast};
#[derive(Clone, Deref, DerefMut, Traversable)]
#[traversable(wrap = "last")]
#[traversable(transparent)]
pub struct LazyTransformLast<I, T, S1T = T>(pub LazyVecFrom1<I, T, I, S1T>)
where
I: VecIndex,

View File

@@ -0,0 +1,69 @@
//! Lazy binary value wrapper combining height (with price) + difficultyepoch + date last transforms.
use brk_traversable::Traversable;
use brk_types::{Bitcoin, Close, Dollars, Sats, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{BinaryTransform, IterableCloneableVec, UnaryTransform};
use super::{LazyBlockValue, LazyTransformedValueDifficultyEpoch};
use crate::internal::LazyValueDateLast;
use crate::{internal::ValueBlockLast, price};
const VERSION: Version = Version::ZERO;
/// Lazy binary value wrapper with height (using price binary transform) + difficultyepoch + date last transforms.
///
/// Use this when the height-level dollars need a binary transform (e.g., price × sats)
/// rather than a unary transform from existing dollars.
///
/// No merge at this level - denominations (sats, bitcoin, dollars) stay as separate branches.
/// Each inner field has merge which combines indexes within each denomination.
#[derive(Clone, Deref, DerefMut, Traversable)]
pub struct LazyBinaryLastBlockValue {
#[traversable(flatten)]
pub height: LazyBlockValue,
#[traversable(flatten)]
pub difficultyepoch: LazyTransformedValueDifficultyEpoch,
#[deref]
#[deref_mut]
#[traversable(flatten)]
pub dates: LazyValueDateLast,
}
impl LazyBinaryLastBlockValue {
pub fn from_block_source<SatsTransform, BitcoinTransform, HeightDollarsTransform, DateDollarsTransform>(
name: &str,
source: &ValueBlockLast,
price: Option<&price::Vecs>,
version: Version,
) -> Self
where
SatsTransform: UnaryTransform<Sats, Sats>,
BitcoinTransform: UnaryTransform<Sats, Bitcoin>,
HeightDollarsTransform: BinaryTransform<Close<Dollars>, Sats, Dollars>,
DateDollarsTransform: UnaryTransform<Dollars, Dollars>,
{
let v = version + VERSION;
let price_source = price.map(|p| p.usd.split.close.height.boxed_clone());
let height = LazyBlockValue::from_sources::<SatsTransform, BitcoinTransform, HeightDollarsTransform>(
name,
source.sats.height.boxed_clone(),
price_source,
v,
);
let difficultyepoch = LazyTransformedValueDifficultyEpoch::from_block_source::<
SatsTransform,
BitcoinTransform,
HeightDollarsTransform,
>(name, source, price, v);
let dates = LazyValueDateLast::from_block_source::<SatsTransform, BitcoinTransform, DateDollarsTransform>(
name, source, v,
);
Self { height, difficultyepoch, dates }
}
}

View File

@@ -0,0 +1,112 @@
//! Fully lazy value types for DifficultyEpoch indexing.
//!
//! Two variants exist for different source patterns:
//! - `LazyValueDifficultyEpochFromHeight`: For sources without dollars (computes from price × sats)
//! - `LazyTransformedValueDifficultyEpoch`: For transformed views (e.g., halved supply)
use brk_traversable::Traversable;
use brk_types::{Bitcoin, Close, DifficultyEpoch, Dollars, Height, Sats, Version};
use vecdb::{BinaryTransform, IterableBoxedVec, IterableCloneableVec, LazyVecFrom1, LazyVecFrom2, UnaryTransform};
use crate::internal::{ClosePriceTimesSats, LazyLast, SatsToBitcoin};
use crate::price;
const VERSION: Version = Version::ZERO;
/// Lazy value type at difficultyepoch level - computed from height sats + price.
///
/// Use this when the source only has height-indexed sats (e.g., ValueBlockDateLast).
/// Dollars are computed via price × sats binary transform.
#[derive(Clone, Traversable)]
pub struct LazyValueDifficultyEpochFromHeight {
pub sats: LazyLast<DifficultyEpoch, Sats, Height, DifficultyEpoch>,
pub bitcoin: LazyVecFrom1<DifficultyEpoch, Bitcoin, DifficultyEpoch, Sats>,
pub dollars: Option<LazyVecFrom2<DifficultyEpoch, Dollars, DifficultyEpoch, Close<Dollars>, DifficultyEpoch, Sats>>,
}
impl LazyValueDifficultyEpochFromHeight {
/// Create from height sats source and difficultyepoch identity.
/// Bitcoin is derived from sats. Dollars are computed from price × sats.
pub fn from_height_source(
name: &str,
height_sats: IterableBoxedVec<Height, Sats>,
difficultyepoch_identity: IterableBoxedVec<DifficultyEpoch, DifficultyEpoch>,
price: Option<&price::Vecs>,
version: Version,
) -> Self {
let v = version + VERSION;
let sats = LazyLast::from_source(name, v, height_sats, difficultyepoch_identity);
let bitcoin = LazyVecFrom1::transformed::<SatsToBitcoin>(
&format!("{name}_btc"),
v,
sats.boxed_clone(),
);
let dollars = price.map(|p| {
LazyVecFrom2::transformed::<ClosePriceTimesSats>(
&format!("{name}_usd"),
v,
p.usd.split.close.difficultyepoch.boxed_clone(),
sats.boxed_clone(),
)
});
Self { sats, bitcoin, dollars }
}
}
/// Lazy value type at difficultyepoch level - transformed from existing difficultyepoch sources.
///
/// Use this when creating transformed views (e.g., halved supply) from sources that
/// already have difficultyepoch aggregations. Applies transforms to the existing aggregations.
#[derive(Clone, Traversable)]
pub struct LazyTransformedValueDifficultyEpoch {
pub sats: LazyVecFrom1<DifficultyEpoch, Sats, DifficultyEpoch, Sats>,
pub bitcoin: LazyVecFrom1<DifficultyEpoch, Bitcoin, DifficultyEpoch, Sats>,
pub dollars: Option<LazyVecFrom2<DifficultyEpoch, Dollars, DifficultyEpoch, Close<Dollars>, DifficultyEpoch, Sats>>,
}
impl LazyTransformedValueDifficultyEpoch {
/// Create transformed difficultyepoch values from a ValueBlockLast source.
/// SatsTransform is applied to the source's difficultyepoch sats.
/// BitcoinTransform converts source sats to bitcoin (should combine sats transform + conversion).
/// Dollars are computed from price × transformed sats.
pub fn from_block_source<SatsTransform, BitcoinTransform, DollarsTransform>(
name: &str,
source: &super::ValueBlockLast,
price: Option<&price::Vecs>,
version: Version,
) -> Self
where
SatsTransform: UnaryTransform<Sats, Sats>,
BitcoinTransform: UnaryTransform<Sats, Bitcoin>,
DollarsTransform: BinaryTransform<Close<Dollars>, Sats, Dollars>,
{
let v = version + VERSION;
let sats = LazyVecFrom1::transformed::<SatsTransform>(
name,
v,
source.sats.rest.difficultyepoch.boxed_clone(),
);
let bitcoin = LazyVecFrom1::transformed::<BitcoinTransform>(
&format!("{name}_btc"),
v,
source.sats.rest.difficultyepoch.boxed_clone(),
);
let dollars = price.map(|p| {
LazyVecFrom2::transformed::<DollarsTransform>(
&format!("{name}_usd"),
v,
p.usd.split.close.difficultyepoch.boxed_clone(),
source.sats.rest.difficultyepoch.boxed_clone(),
)
});
Self { sats, bitcoin, dollars }
}
}

View File

@@ -3,8 +3,10 @@ mod full;
mod height;
mod last;
mod lazy;
mod lazy_binary_last;
mod lazy_computed_sum_cum;
mod lazy_derived;
mod lazy_difficultyepoch;
mod lazy_height;
mod lazy_last;
mod lazy_sum_cum;
@@ -16,8 +18,10 @@ pub use full::*;
pub use height::*;
pub use last::*;
pub use lazy::*;
pub use lazy_binary_last::*;
pub use lazy_computed_sum_cum::*;
pub use lazy_derived::*;
pub use lazy_difficultyepoch::*;
pub use lazy_height::*;
pub use lazy_last::*;
pub use lazy_sum_cum::*;

View File

@@ -5,26 +5,26 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{DifficultyEpoch, Height, Sats, Version};
use brk_types::{Height, Sats, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{Database, EagerVec, Exit, ImportableVec, IterableCloneableVec, PcoVec};
use crate::{ComputeIndexes, indexes, price};
use super::super::block::LazyDerivedBlockValue;
use super::super::block::{LazyDerivedBlockValue, LazyValueDifficultyEpochFromHeight};
use super::ValueDateLast;
use crate::internal::LazyLast;
/// Value type where both height and dateindex are stored independently.
/// Dateindex values cannot be derived from height (e.g., unrealized P&L).
#[derive(Clone, Deref, DerefMut, Traversable)]
#[traversable(merge)]
pub struct ValueBlockDateLast {
#[traversable(wrap = "sats")]
#[traversable(rename = "sats")]
pub height: EagerVec<PcoVec<Height, Sats>>,
#[traversable(flatten)]
pub height_value: LazyDerivedBlockValue,
pub difficultyepoch: LazyLast<DifficultyEpoch, Sats, Height, DifficultyEpoch>,
#[traversable(flatten)]
pub difficultyepoch: LazyValueDifficultyEpochFromHeight,
#[deref]
#[deref_mut]
#[traversable(flatten)]
@@ -51,11 +51,12 @@ impl ValueBlockDateLast {
let height_value =
LazyDerivedBlockValue::from_source(name, height.boxed_clone(), v, price_source);
let difficultyepoch = LazyLast::from_source(
let difficultyepoch = LazyValueDifficultyEpochFromHeight::from_height_source(
name,
v,
height.boxed_clone(),
indexes.difficultyepoch.identity.boxed_clone(),
price,
v,
);
let indexes = ValueDateLast::forced_import(db, name, v, compute_dollars, indexes)?;

View File

@@ -14,9 +14,8 @@ use super::ValueDerivedTxFull;
const VERSION: Version = Version::ZERO;
#[derive(Clone, Deref, DerefMut, Traversable)]
#[traversable(merge)]
pub struct ValueTxFull {
#[traversable(wrap = "sats")]
#[traversable(rename = "txindex")]
pub base: EagerVec<PcoVec<TxIndex, Sats>>,
#[deref]
#[deref_mut]

View File

@@ -153,7 +153,7 @@ impl Vecs {
let supply_vecs: Vec<_> = amount_range
.iter()
.map(|c| &c.metrics.supply.supply.sats.dateindex.0)
.map(|c| &c.metrics.supply.total.sats.dateindex.0)
.collect();
let count_vecs: Vec<_> = amount_range
.iter()

View File

@@ -29,7 +29,7 @@ impl Vecs {
.all
.metrics
.supply
.supply
.total
.dollars
.as_ref()
.zip(transactions.volume.sent_sum.dollars.as_ref())

View File

@@ -13,6 +13,8 @@ impl Vecs {
Self(LazyLastBlockValue::from_block_source::<
SatsIdentity,
DollarsIdentity,
>("circulating_supply", &supply_metrics.supply, version))
>(
"circulating_supply", &supply_metrics.total, version)
)
}
}

View File

@@ -13,7 +13,7 @@ impl Vecs {
exit: &Exit,
) -> Result<()> {
// inflation = daily_subsidy / circulating_supply * 365 * 100
let circulating_supply = &distribution.utxo_cohorts.all.metrics.supply.supply.sats;
let circulating_supply = &distribution.utxo_cohorts.all.metrics.supply.total.sats;
self.compute_all(starting_indexes, exit, |v| {
v.compute_transform2(

View File

@@ -2,13 +2,16 @@ use brk_types::Version;
use vecdb::IterableCloneableVec;
use super::Vecs;
use crate::{distribution, internal::{DollarsIdentity, LazyBlockLast}};
use crate::{
distribution,
internal::{DollarsIdentity, LazyBlockLast},
};
impl Vecs {
pub fn import(version: Version, distribution: &distribution::Vecs) -> Option<Self> {
let supply_metrics = &distribution.utxo_cohorts.all.metrics.supply;
supply_metrics.supply.dollars.as_ref().map(|d| {
supply_metrics.total.dollars.as_ref().map(|d| {
Self(LazyBlockLast::from_computed::<DollarsIdentity>(
"market_cap",
version,

View File

@@ -13,7 +13,7 @@ impl Vecs {
exit: &Exit,
) -> Result<()> {
// velocity = annualized_volume / circulating_supply
let circulating_supply = &distribution.utxo_cohorts.all.metrics.supply.supply;
let circulating_supply = &distribution.utxo_cohorts.all.metrics.supply.total;
// BTC velocity
self.btc.compute_all(starting_indexes, exit, |v| {