computer: snapshot

This commit is contained in:
nym21
2025-12-28 20:24:38 +01:00
parent f5790d5c8a
commit 236b4097c5
15 changed files with 632 additions and 914 deletions

View File

@@ -18,7 +18,7 @@ use crate::{
};
use super::{
super::metrics::{CohortMetrics, ImportConfig},
super::metrics::{CohortMetrics, ImportConfig, SupplyMetrics},
traits::{CohortVecs, DynCohortVecs},
};
@@ -47,6 +47,9 @@ pub struct AddressCohortVecs {
impl AddressCohortVecs {
/// Import address cohort from database.
///
/// `all_supply` is the supply metrics from the "all" cohort, used as global
/// sources for `*_rel_to_market_cap` ratios. Pass `None` if not available.
pub fn forced_import(
db: &Database,
filter: Filter,
@@ -54,6 +57,7 @@ impl AddressCohortVecs {
indexes: &indexes::Vecs,
price: Option<&price::Vecs>,
states_path: Option<&Path>,
all_supply: Option<&SupplyMetrics>,
) -> Result<Self> {
let compute_dollars = price.is_some();
let full_name = filter.to_full_name(CohortContext::Address);
@@ -73,7 +77,7 @@ impl AddressCohortVecs {
state: states_path
.map(|path| AddressCohortState::new(path, &full_name, compute_dollars)),
metrics: CohortMetrics::forced_import(&cfg)?,
metrics: CohortMetrics::forced_import(&cfg, all_supply)?,
height_to_addr_count: EagerVec::forced_import(
db,
@@ -288,7 +292,6 @@ impl CohortVecs for AddressCohortVecs {
price: Option<&price::Vecs>,
starting_indexes: &Indexes,
height_to_supply: &impl IterableVec<Height, Bitcoin>,
dateindex_to_supply: &impl IterableVec<DateIndex, Bitcoin>,
height_to_market_cap: Option<&impl IterableVec<Height, Dollars>>,
dateindex_to_market_cap: Option<&impl IterableVec<DateIndex, Dollars>>,
exit: &Exit,
@@ -298,7 +301,6 @@ impl CohortVecs for AddressCohortVecs {
price,
starting_indexes,
height_to_supply,
dateindex_to_supply,
height_to_market_cap,
dateindex_to_market_cap,
exit,

View File

@@ -13,7 +13,7 @@ use vecdb::{AnyStoredVec, Database, Exit, IterableVec};
use crate::{Indexes, indexes, price, stateful::DynCohortVecs};
use super::{AddressCohortVecs, CohortVecs};
use super::{super::metrics::SupplyMetrics, AddressCohortVecs, CohortVecs};
const VERSION: Version = Version::new(0);
@@ -23,19 +23,23 @@ pub struct AddressCohorts(AddressGroups<AddressCohortVecs>);
impl AddressCohorts {
/// Import all Address cohorts from database.
///
/// `all_supply` is the supply metrics from the UTXO "all" cohort, used as global
/// sources for `*_rel_to_market_cap` ratios.
pub fn forced_import(
db: &Database,
version: Version,
indexes: &indexes::Vecs,
price: Option<&price::Vecs>,
states_path: &Path,
all_supply: Option<&SupplyMetrics>,
) -> Result<Self> {
let v = version + VERSION + Version::ZERO;
// Helper to create a cohort - only amount_range cohorts have state
let create = |filter: Filter, has_state: bool| -> Result<AddressCohortVecs> {
let states_path = if has_state { Some(states_path) } else { None };
AddressCohortVecs::forced_import(db, filter, v, indexes, price, states_path)
AddressCohortVecs::forced_import(db, filter, v, indexes, price, states_path, all_supply)
};
let full = |f: Filter| create(f, true);
@@ -183,20 +187,18 @@ impl AddressCohorts {
/// Second phase of post-processing: compute relative metrics.
#[allow(clippy::too_many_arguments)]
pub fn compute_rest_part2<S, D, HM, DM>(
pub fn compute_rest_part2<S, HM, DM>(
&mut self,
indexes: &indexes::Vecs,
price: Option<&price::Vecs>,
starting_indexes: &Indexes,
height_to_supply: &S,
dateindex_to_supply: &D,
height_to_market_cap: Option<&HM>,
dateindex_to_market_cap: Option<&DM>,
exit: &Exit,
) -> Result<()>
where
S: IterableVec<Height, Bitcoin> + Sync,
D: IterableVec<DateIndex, Bitcoin> + Sync,
HM: IterableVec<Height, Dollars> + Sync,
DM: IterableVec<DateIndex, Dollars> + Sync,
{
@@ -206,7 +208,6 @@ impl AddressCohorts {
price,
starting_indexes,
height_to_supply,
dateindex_to_supply,
height_to_market_cap,
dateindex_to_market_cap,
exit,

View File

@@ -61,7 +61,6 @@ pub trait CohortVecs: DynCohortVecs {
price: Option<&price::Vecs>,
starting_indexes: &Indexes,
height_to_supply: &impl IterableVec<Height, Bitcoin>,
dateindex_to_supply: &impl IterableVec<DateIndex, Bitcoin>,
height_to_market_cap: Option<&impl IterableVec<Height, Dollars>>,
dateindex_to_market_cap: Option<&impl IterableVec<DateIndex, Dollars>>,
exit: &Exit,

View File

@@ -12,7 +12,7 @@ use crate::{
stateful::{CohortVecs, DynCohortVecs, states::UTXOCohortState},
};
use super::super::metrics::{CohortMetrics, ImportConfig};
use super::super::metrics::{CohortMetrics, ImportConfig, SupplyMetrics};
/// UTXO cohort with metrics and optional runtime state.
#[derive(Clone, Traversable)]
@@ -31,6 +31,10 @@ pub struct UTXOCohortVecs {
impl UTXOCohortVecs {
/// Import UTXO cohort from database.
///
/// `all_supply` is the supply metrics from the "all" cohort, used as global
/// sources for `*_rel_to_market_cap` ratios. Pass `None` for the "all" cohort itself.
#[allow(clippy::too_many_arguments)]
pub fn forced_import(
db: &Database,
filter: Filter,
@@ -39,6 +43,7 @@ impl UTXOCohortVecs {
price: Option<&price::Vecs>,
states_path: &Path,
state_level: StateLevel,
all_supply: Option<&SupplyMetrics>,
) -> Result<Self> {
let compute_dollars = price.is_some();
let full_name = filter.to_full_name(CohortContext::Utxo);
@@ -65,7 +70,7 @@ impl UTXOCohortVecs {
None
},
metrics: CohortMetrics::forced_import(&cfg)?,
metrics: CohortMetrics::forced_import(&cfg, all_supply)?,
})
}
@@ -233,7 +238,6 @@ impl CohortVecs for UTXOCohortVecs {
price: Option<&price::Vecs>,
starting_indexes: &Indexes,
height_to_supply: &impl IterableVec<Height, Bitcoin>,
dateindex_to_supply: &impl IterableVec<DateIndex, Bitcoin>,
height_to_market_cap: Option<&impl IterableVec<Height, Dollars>>,
dateindex_to_market_cap: Option<&impl IterableVec<DateIndex, Dollars>>,
exit: &Exit,
@@ -243,7 +247,6 @@ impl CohortVecs for UTXOCohortVecs {
price,
starting_indexes,
height_to_supply,
dateindex_to_supply,
height_to_market_cap,
dateindex_to_market_cap,
exit,

View File

@@ -46,212 +46,256 @@ impl UTXOCohorts {
) -> Result<Self> {
let v = version + VERSION + Version::ZERO;
let create = |filter: Filter, state_level: StateLevel| -> Result<UTXOCohortVecs> {
UTXOCohortVecs::forced_import(db, filter, v, indexes, price, states_path, state_level)
};
// Create "all" cohort first - it doesn't need global sources (it IS the global source)
let all = UTXOCohortVecs::forced_import(
db,
Filter::All,
version + VERSION + Version::ONE,
indexes,
price,
states_path,
StateLevel::PriceOnly,
None,
)?;
let full = |f: Filter| create(f, StateLevel::Full);
let none = |f: Filter| create(f, StateLevel::None);
// Get reference to all's supply for other cohorts to use as global source
let all_supply = Some(&all.metrics.supply);
Ok(Self(UTXOGroups {
all: UTXOCohortVecs::forced_import(
// Create all cohorts first (while borrowing all_supply), then assemble struct
let term = ByTerm {
short: UTXOCohortVecs::forced_import(
db,
Filter::All,
version + VERSION + Version::ONE,
Filter::Term(Term::Sth),
v,
indexes,
price,
states_path,
StateLevel::PriceOnly,
all_supply,
)?,
long: UTXOCohortVecs::forced_import(
db,
Filter::Term(Term::Lth),
v,
indexes,
price,
states_path,
StateLevel::PriceOnly,
all_supply,
)?,
};
term: ByTerm {
short: create(Filter::Term(Term::Sth), StateLevel::PriceOnly)?,
long: create(Filter::Term(Term::Lth), StateLevel::PriceOnly)?,
},
let full = |f: Filter| {
UTXOCohortVecs::forced_import(
db,
f,
v,
indexes,
price,
states_path,
StateLevel::Full,
all_supply,
)
};
let none = |f: Filter| {
UTXOCohortVecs::forced_import(
db,
f,
v,
indexes,
price,
states_path,
StateLevel::None,
all_supply,
)
};
epoch: ByEpoch {
_0: full(Filter::Epoch(HalvingEpoch::new(0)))?,
_1: full(Filter::Epoch(HalvingEpoch::new(1)))?,
_2: full(Filter::Epoch(HalvingEpoch::new(2)))?,
_3: full(Filter::Epoch(HalvingEpoch::new(3)))?,
_4: full(Filter::Epoch(HalvingEpoch::new(4)))?,
},
let epoch = ByEpoch {
_0: full(Filter::Epoch(HalvingEpoch::new(0)))?,
_1: full(Filter::Epoch(HalvingEpoch::new(1)))?,
_2: full(Filter::Epoch(HalvingEpoch::new(2)))?,
_3: full(Filter::Epoch(HalvingEpoch::new(3)))?,
_4: full(Filter::Epoch(HalvingEpoch::new(4)))?,
};
year: ByYear {
_2009: full(Filter::Year(Year::new(2009)))?,
_2010: full(Filter::Year(Year::new(2010)))?,
_2011: full(Filter::Year(Year::new(2011)))?,
_2012: full(Filter::Year(Year::new(2012)))?,
_2013: full(Filter::Year(Year::new(2013)))?,
_2014: full(Filter::Year(Year::new(2014)))?,
_2015: full(Filter::Year(Year::new(2015)))?,
_2016: full(Filter::Year(Year::new(2016)))?,
_2017: full(Filter::Year(Year::new(2017)))?,
_2018: full(Filter::Year(Year::new(2018)))?,
_2019: full(Filter::Year(Year::new(2019)))?,
_2020: full(Filter::Year(Year::new(2020)))?,
_2021: full(Filter::Year(Year::new(2021)))?,
_2022: full(Filter::Year(Year::new(2022)))?,
_2023: full(Filter::Year(Year::new(2023)))?,
_2024: full(Filter::Year(Year::new(2024)))?,
_2025: full(Filter::Year(Year::new(2025)))?,
_2026: full(Filter::Year(Year::new(2026)))?,
},
let year = ByYear {
_2009: full(Filter::Year(Year::new(2009)))?,
_2010: full(Filter::Year(Year::new(2010)))?,
_2011: full(Filter::Year(Year::new(2011)))?,
_2012: full(Filter::Year(Year::new(2012)))?,
_2013: full(Filter::Year(Year::new(2013)))?,
_2014: full(Filter::Year(Year::new(2014)))?,
_2015: full(Filter::Year(Year::new(2015)))?,
_2016: full(Filter::Year(Year::new(2016)))?,
_2017: full(Filter::Year(Year::new(2017)))?,
_2018: full(Filter::Year(Year::new(2018)))?,
_2019: full(Filter::Year(Year::new(2019)))?,
_2020: full(Filter::Year(Year::new(2020)))?,
_2021: full(Filter::Year(Year::new(2021)))?,
_2022: full(Filter::Year(Year::new(2022)))?,
_2023: full(Filter::Year(Year::new(2023)))?,
_2024: full(Filter::Year(Year::new(2024)))?,
_2025: full(Filter::Year(Year::new(2025)))?,
_2026: full(Filter::Year(Year::new(2026)))?,
};
type_: BySpendableType {
p2pk65: full(Filter::Type(OutputType::P2PK65))?,
p2pk33: full(Filter::Type(OutputType::P2PK33))?,
p2pkh: full(Filter::Type(OutputType::P2PKH))?,
p2sh: full(Filter::Type(OutputType::P2SH))?,
p2wpkh: full(Filter::Type(OutputType::P2WPKH))?,
p2wsh: full(Filter::Type(OutputType::P2WSH))?,
p2tr: full(Filter::Type(OutputType::P2TR))?,
p2a: full(Filter::Type(OutputType::P2A))?,
p2ms: full(Filter::Type(OutputType::P2MS))?,
empty: full(Filter::Type(OutputType::Empty))?,
unknown: full(Filter::Type(OutputType::Unknown))?,
},
let type_ = BySpendableType {
p2pk65: full(Filter::Type(OutputType::P2PK65))?,
p2pk33: full(Filter::Type(OutputType::P2PK33))?,
p2pkh: full(Filter::Type(OutputType::P2PKH))?,
p2sh: full(Filter::Type(OutputType::P2SH))?,
p2wpkh: full(Filter::Type(OutputType::P2WPKH))?,
p2wsh: full(Filter::Type(OutputType::P2WSH))?,
p2tr: full(Filter::Type(OutputType::P2TR))?,
p2a: full(Filter::Type(OutputType::P2A))?,
p2ms: full(Filter::Type(OutputType::P2MS))?,
empty: full(Filter::Type(OutputType::Empty))?,
unknown: full(Filter::Type(OutputType::Unknown))?,
};
max_age: ByMaxAge {
_1w: none(Filter::Time(TimeFilter::LowerThan(DAYS_1W)))?,
_1m: none(Filter::Time(TimeFilter::LowerThan(DAYS_1M)))?,
_2m: none(Filter::Time(TimeFilter::LowerThan(DAYS_2M)))?,
_3m: none(Filter::Time(TimeFilter::LowerThan(DAYS_3M)))?,
_4m: none(Filter::Time(TimeFilter::LowerThan(DAYS_4M)))?,
_5m: none(Filter::Time(TimeFilter::LowerThan(DAYS_5M)))?,
_6m: none(Filter::Time(TimeFilter::LowerThan(DAYS_6M)))?,
_1y: none(Filter::Time(TimeFilter::LowerThan(DAYS_1Y)))?,
_2y: none(Filter::Time(TimeFilter::LowerThan(DAYS_2Y)))?,
_3y: none(Filter::Time(TimeFilter::LowerThan(DAYS_3Y)))?,
_4y: none(Filter::Time(TimeFilter::LowerThan(DAYS_4Y)))?,
_5y: none(Filter::Time(TimeFilter::LowerThan(DAYS_5Y)))?,
_6y: none(Filter::Time(TimeFilter::LowerThan(DAYS_6Y)))?,
_7y: none(Filter::Time(TimeFilter::LowerThan(DAYS_7Y)))?,
_8y: none(Filter::Time(TimeFilter::LowerThan(DAYS_8Y)))?,
_10y: none(Filter::Time(TimeFilter::LowerThan(DAYS_10Y)))?,
_12y: none(Filter::Time(TimeFilter::LowerThan(DAYS_12Y)))?,
_15y: none(Filter::Time(TimeFilter::LowerThan(DAYS_15Y)))?,
},
let max_age = ByMaxAge {
_1w: none(Filter::Time(TimeFilter::LowerThan(DAYS_1W)))?,
_1m: none(Filter::Time(TimeFilter::LowerThan(DAYS_1M)))?,
_2m: none(Filter::Time(TimeFilter::LowerThan(DAYS_2M)))?,
_3m: none(Filter::Time(TimeFilter::LowerThan(DAYS_3M)))?,
_4m: none(Filter::Time(TimeFilter::LowerThan(DAYS_4M)))?,
_5m: none(Filter::Time(TimeFilter::LowerThan(DAYS_5M)))?,
_6m: none(Filter::Time(TimeFilter::LowerThan(DAYS_6M)))?,
_1y: none(Filter::Time(TimeFilter::LowerThan(DAYS_1Y)))?,
_2y: none(Filter::Time(TimeFilter::LowerThan(DAYS_2Y)))?,
_3y: none(Filter::Time(TimeFilter::LowerThan(DAYS_3Y)))?,
_4y: none(Filter::Time(TimeFilter::LowerThan(DAYS_4Y)))?,
_5y: none(Filter::Time(TimeFilter::LowerThan(DAYS_5Y)))?,
_6y: none(Filter::Time(TimeFilter::LowerThan(DAYS_6Y)))?,
_7y: none(Filter::Time(TimeFilter::LowerThan(DAYS_7Y)))?,
_8y: none(Filter::Time(TimeFilter::LowerThan(DAYS_8Y)))?,
_10y: none(Filter::Time(TimeFilter::LowerThan(DAYS_10Y)))?,
_12y: none(Filter::Time(TimeFilter::LowerThan(DAYS_12Y)))?,
_15y: none(Filter::Time(TimeFilter::LowerThan(DAYS_15Y)))?,
};
min_age: ByMinAge {
_1d: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_1D)))?,
_1w: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_1W)))?,
_1m: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_1M)))?,
_2m: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_2M)))?,
_3m: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_3M)))?,
_4m: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_4M)))?,
_5m: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_5M)))?,
_6m: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_6M)))?,
_1y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_1Y)))?,
_2y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_2Y)))?,
_3y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_3Y)))?,
_4y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_4Y)))?,
_5y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_5Y)))?,
_6y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_6Y)))?,
_7y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_7Y)))?,
_8y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_8Y)))?,
_10y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_10Y)))?,
_12y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_12Y)))?,
},
let min_age = ByMinAge {
_1d: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_1D)))?,
_1w: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_1W)))?,
_1m: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_1M)))?,
_2m: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_2M)))?,
_3m: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_3M)))?,
_4m: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_4M)))?,
_5m: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_5M)))?,
_6m: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_6M)))?,
_1y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_1Y)))?,
_2y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_2Y)))?,
_3y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_3Y)))?,
_4y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_4Y)))?,
_5y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_5Y)))?,
_6y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_6Y)))?,
_7y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_7Y)))?,
_8y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_8Y)))?,
_10y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_10Y)))?,
_12y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_12Y)))?,
};
age_range: ByAgeRange {
up_to_1d: full(Filter::Time(TimeFilter::Range(0..DAYS_1D)))?,
_1d_to_1w: full(Filter::Time(TimeFilter::Range(DAYS_1D..DAYS_1W)))?,
_1w_to_1m: full(Filter::Time(TimeFilter::Range(DAYS_1W..DAYS_1M)))?,
_1m_to_2m: full(Filter::Time(TimeFilter::Range(DAYS_1M..DAYS_2M)))?,
_2m_to_3m: full(Filter::Time(TimeFilter::Range(DAYS_2M..DAYS_3M)))?,
_3m_to_4m: full(Filter::Time(TimeFilter::Range(DAYS_3M..DAYS_4M)))?,
_4m_to_5m: full(Filter::Time(TimeFilter::Range(DAYS_4M..DAYS_5M)))?,
_5m_to_6m: full(Filter::Time(TimeFilter::Range(DAYS_5M..DAYS_6M)))?,
_6m_to_1y: full(Filter::Time(TimeFilter::Range(DAYS_6M..DAYS_1Y)))?,
_1y_to_2y: full(Filter::Time(TimeFilter::Range(DAYS_1Y..DAYS_2Y)))?,
_2y_to_3y: full(Filter::Time(TimeFilter::Range(DAYS_2Y..DAYS_3Y)))?,
_3y_to_4y: full(Filter::Time(TimeFilter::Range(DAYS_3Y..DAYS_4Y)))?,
_4y_to_5y: full(Filter::Time(TimeFilter::Range(DAYS_4Y..DAYS_5Y)))?,
_5y_to_6y: full(Filter::Time(TimeFilter::Range(DAYS_5Y..DAYS_6Y)))?,
_6y_to_7y: full(Filter::Time(TimeFilter::Range(DAYS_6Y..DAYS_7Y)))?,
_7y_to_8y: full(Filter::Time(TimeFilter::Range(DAYS_7Y..DAYS_8Y)))?,
_8y_to_10y: full(Filter::Time(TimeFilter::Range(DAYS_8Y..DAYS_10Y)))?,
_10y_to_12y: full(Filter::Time(TimeFilter::Range(DAYS_10Y..DAYS_12Y)))?,
_12y_to_15y: full(Filter::Time(TimeFilter::Range(DAYS_12Y..DAYS_15Y)))?,
from_15y: full(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_15Y)))?,
},
let age_range = ByAgeRange {
up_to_1d: full(Filter::Time(TimeFilter::Range(0..DAYS_1D)))?,
_1d_to_1w: full(Filter::Time(TimeFilter::Range(DAYS_1D..DAYS_1W)))?,
_1w_to_1m: full(Filter::Time(TimeFilter::Range(DAYS_1W..DAYS_1M)))?,
_1m_to_2m: full(Filter::Time(TimeFilter::Range(DAYS_1M..DAYS_2M)))?,
_2m_to_3m: full(Filter::Time(TimeFilter::Range(DAYS_2M..DAYS_3M)))?,
_3m_to_4m: full(Filter::Time(TimeFilter::Range(DAYS_3M..DAYS_4M)))?,
_4m_to_5m: full(Filter::Time(TimeFilter::Range(DAYS_4M..DAYS_5M)))?,
_5m_to_6m: full(Filter::Time(TimeFilter::Range(DAYS_5M..DAYS_6M)))?,
_6m_to_1y: full(Filter::Time(TimeFilter::Range(DAYS_6M..DAYS_1Y)))?,
_1y_to_2y: full(Filter::Time(TimeFilter::Range(DAYS_1Y..DAYS_2Y)))?,
_2y_to_3y: full(Filter::Time(TimeFilter::Range(DAYS_2Y..DAYS_3Y)))?,
_3y_to_4y: full(Filter::Time(TimeFilter::Range(DAYS_3Y..DAYS_4Y)))?,
_4y_to_5y: full(Filter::Time(TimeFilter::Range(DAYS_4Y..DAYS_5Y)))?,
_5y_to_6y: full(Filter::Time(TimeFilter::Range(DAYS_5Y..DAYS_6Y)))?,
_6y_to_7y: full(Filter::Time(TimeFilter::Range(DAYS_6Y..DAYS_7Y)))?,
_7y_to_8y: full(Filter::Time(TimeFilter::Range(DAYS_7Y..DAYS_8Y)))?,
_8y_to_10y: full(Filter::Time(TimeFilter::Range(DAYS_8Y..DAYS_10Y)))?,
_10y_to_12y: full(Filter::Time(TimeFilter::Range(DAYS_10Y..DAYS_12Y)))?,
_12y_to_15y: full(Filter::Time(TimeFilter::Range(DAYS_12Y..DAYS_15Y)))?,
from_15y: full(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_15Y)))?,
};
amount_range: ByAmountRange {
_0sats: full(Filter::Amount(AmountFilter::LowerThan(Sats::_1)))?,
_1sat_to_10sats: full(Filter::Amount(AmountFilter::Range(Sats::_1..Sats::_10)))?,
_10sats_to_100sats: full(Filter::Amount(AmountFilter::Range(
Sats::_10..Sats::_100,
)))?,
_100sats_to_1k_sats: full(Filter::Amount(AmountFilter::Range(
Sats::_100..Sats::_1K,
)))?,
_1k_sats_to_10k_sats: full(Filter::Amount(AmountFilter::Range(
Sats::_1K..Sats::_10K,
)))?,
_10k_sats_to_100k_sats: full(Filter::Amount(AmountFilter::Range(
Sats::_10K..Sats::_100K,
)))?,
_100k_sats_to_1m_sats: full(Filter::Amount(AmountFilter::Range(
Sats::_100K..Sats::_1M,
)))?,
_1m_sats_to_10m_sats: full(Filter::Amount(AmountFilter::Range(
Sats::_1M..Sats::_10M,
)))?,
_10m_sats_to_1btc: full(Filter::Amount(AmountFilter::Range(
Sats::_10M..Sats::_1BTC,
)))?,
_1btc_to_10btc: full(Filter::Amount(AmountFilter::Range(
Sats::_1BTC..Sats::_10BTC,
)))?,
_10btc_to_100btc: full(Filter::Amount(AmountFilter::Range(
Sats::_10BTC..Sats::_100BTC,
)))?,
_100btc_to_1k_btc: full(Filter::Amount(AmountFilter::Range(
Sats::_100BTC..Sats::_1K_BTC,
)))?,
_1k_btc_to_10k_btc: full(Filter::Amount(AmountFilter::Range(
Sats::_1K_BTC..Sats::_10K_BTC,
)))?,
_10k_btc_to_100k_btc: full(Filter::Amount(AmountFilter::Range(
Sats::_10K_BTC..Sats::_100K_BTC,
)))?,
_100k_btc_or_more: full(Filter::Amount(AmountFilter::GreaterOrEqual(
Sats::_100K_BTC,
)))?,
},
let amount_range = ByAmountRange {
_0sats: full(Filter::Amount(AmountFilter::LowerThan(Sats::_1)))?,
_1sat_to_10sats: full(Filter::Amount(AmountFilter::Range(Sats::_1..Sats::_10)))?,
_10sats_to_100sats: full(Filter::Amount(AmountFilter::Range(Sats::_10..Sats::_100)))?,
_100sats_to_1k_sats: full(Filter::Amount(AmountFilter::Range(Sats::_100..Sats::_1K)))?,
_1k_sats_to_10k_sats: full(Filter::Amount(AmountFilter::Range(Sats::_1K..Sats::_10K)))?,
_10k_sats_to_100k_sats: full(Filter::Amount(AmountFilter::Range(
Sats::_10K..Sats::_100K,
)))?,
_100k_sats_to_1m_sats: full(Filter::Amount(AmountFilter::Range(
Sats::_100K..Sats::_1M,
)))?,
_1m_sats_to_10m_sats: full(Filter::Amount(AmountFilter::Range(Sats::_1M..Sats::_10M)))?,
_10m_sats_to_1btc: full(Filter::Amount(AmountFilter::Range(Sats::_10M..Sats::_1BTC)))?,
_1btc_to_10btc: full(Filter::Amount(AmountFilter::Range(
Sats::_1BTC..Sats::_10BTC,
)))?,
_10btc_to_100btc: full(Filter::Amount(AmountFilter::Range(
Sats::_10BTC..Sats::_100BTC,
)))?,
_100btc_to_1k_btc: full(Filter::Amount(AmountFilter::Range(
Sats::_100BTC..Sats::_1K_BTC,
)))?,
_1k_btc_to_10k_btc: full(Filter::Amount(AmountFilter::Range(
Sats::_1K_BTC..Sats::_10K_BTC,
)))?,
_10k_btc_to_100k_btc: full(Filter::Amount(AmountFilter::Range(
Sats::_10K_BTC..Sats::_100K_BTC,
)))?,
_100k_btc_or_more: full(Filter::Amount(AmountFilter::GreaterOrEqual(
Sats::_100K_BTC,
)))?,
};
lt_amount: ByLowerThanAmount {
_10sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_10)))?,
_100sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_100)))?,
_1k_sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_1K)))?,
_10k_sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_10K)))?,
_100k_sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_100K)))?,
_1m_sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_1M)))?,
_10m_sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_10M)))?,
_1btc: none(Filter::Amount(AmountFilter::LowerThan(Sats::_1BTC)))?,
_10btc: none(Filter::Amount(AmountFilter::LowerThan(Sats::_10BTC)))?,
_100btc: none(Filter::Amount(AmountFilter::LowerThan(Sats::_100BTC)))?,
_1k_btc: none(Filter::Amount(AmountFilter::LowerThan(Sats::_1K_BTC)))?,
_10k_btc: none(Filter::Amount(AmountFilter::LowerThan(Sats::_10K_BTC)))?,
_100k_btc: none(Filter::Amount(AmountFilter::LowerThan(Sats::_100K_BTC)))?,
},
let lt_amount = ByLowerThanAmount {
_10sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_10)))?,
_100sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_100)))?,
_1k_sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_1K)))?,
_10k_sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_10K)))?,
_100k_sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_100K)))?,
_1m_sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_1M)))?,
_10m_sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_10M)))?,
_1btc: none(Filter::Amount(AmountFilter::LowerThan(Sats::_1BTC)))?,
_10btc: none(Filter::Amount(AmountFilter::LowerThan(Sats::_10BTC)))?,
_100btc: none(Filter::Amount(AmountFilter::LowerThan(Sats::_100BTC)))?,
_1k_btc: none(Filter::Amount(AmountFilter::LowerThan(Sats::_1K_BTC)))?,
_10k_btc: none(Filter::Amount(AmountFilter::LowerThan(Sats::_10K_BTC)))?,
_100k_btc: none(Filter::Amount(AmountFilter::LowerThan(Sats::_100K_BTC)))?,
};
ge_amount: ByGreatEqualAmount {
_1sat: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_1)))?,
_10sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_10)))?,
_100sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_100)))?,
_1k_sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_1K)))?,
_10k_sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_10K)))?,
_100k_sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_100K)))?,
_1m_sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_1M)))?,
_10m_sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_10M)))?,
_1btc: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_1BTC)))?,
_10btc: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_10BTC)))?,
_100btc: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_100BTC)))?,
_1k_btc: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_1K_BTC)))?,
_10k_btc: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_10K_BTC)))?,
},
let ge_amount = ByGreatEqualAmount {
_1sat: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_1)))?,
_10sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_10)))?,
_100sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_100)))?,
_1k_sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_1K)))?,
_10k_sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_10K)))?,
_100k_sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_100K)))?,
_1m_sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_1M)))?,
_10m_sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_10M)))?,
_1btc: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_1BTC)))?,
_10btc: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_10BTC)))?,
_100btc: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_100BTC)))?,
_1k_btc: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_1K_BTC)))?,
_10k_btc: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_10K_BTC)))?,
};
Ok(Self(UTXOGroups {
all,
term,
epoch,
year,
type_,
max_age,
min_age,
age_range,
amount_range,
lt_amount,
ge_amount,
}))
}
@@ -335,20 +379,18 @@ impl UTXOCohorts {
/// Second phase of post-processing: compute relative metrics.
#[allow(clippy::too_many_arguments)]
pub fn compute_rest_part2<S, D, HM, DM>(
pub fn compute_rest_part2<S, HM, DM>(
&mut self,
indexes: &indexes::Vecs,
price: Option<&price::Vecs>,
starting_indexes: &Indexes,
height_to_supply: &S,
dateindex_to_supply: &D,
height_to_market_cap: Option<&HM>,
dateindex_to_market_cap: Option<&DM>,
exit: &Exit,
) -> Result<()>
where
S: IterableVec<Height, Bitcoin> + Sync,
D: IterableVec<DateIndex, Bitcoin> + Sync,
HM: IterableVec<Height, Dollars> + Sync,
DM: IterableVec<DateIndex, Dollars> + Sync,
{
@@ -358,7 +400,6 @@ impl UTXOCohorts {
price,
starting_indexes,
height_to_supply,
dateindex_to_supply,
height_to_market_cap,
dateindex_to_market_cap,
exit,

View File

@@ -49,21 +49,19 @@ pub fn compute_rest_part1(
///
/// Computes supply ratios, market cap ratios, etc. using total references.
#[allow(clippy::too_many_arguments)]
pub fn compute_rest_part2<S, D, HM, DM>(
pub fn compute_rest_part2<S, HM, DM>(
utxo_cohorts: &mut UTXOCohorts,
address_cohorts: &mut AddressCohorts,
indexes: &indexes::Vecs,
price: Option<&price::Vecs>,
starting_indexes: &Indexes,
height_to_supply: &S,
dateindex_to_supply: &D,
height_to_market_cap: Option<&HM>,
dateindex_to_market_cap: Option<&DM>,
exit: &Exit,
) -> Result<()>
where
S: IterableVec<Height, Bitcoin> + Sync,
D: IterableVec<DateIndex, Bitcoin> + Sync,
HM: IterableVec<Height, Dollars> + Sync,
DM: IterableVec<DateIndex, Dollars> + Sync,
{
@@ -74,7 +72,6 @@ where
price,
starting_indexes,
height_to_supply,
dateindex_to_supply,
height_to_market_cap,
dateindex_to_market_cap,
exit,
@@ -85,7 +82,6 @@ where
price,
starting_indexes,
height_to_supply,
dateindex_to_supply,
height_to_market_cap,
dateindex_to_market_cap,
exit,

View File

@@ -55,14 +55,6 @@ pub fn process_blocks(
return Ok(());
}
info!(
"Processing blocks {} to {} (compute_dollars: {}, price_data: {})...",
ctx.starting_height,
ctx.last_height,
ctx.compute_dollars,
ctx.price.is_some()
);
// References to vectors using correct field paths
// From indexer.vecs:
let height_to_first_txindex = &indexer.vecs.tx.height_to_first_txindex;

View File

@@ -51,7 +51,10 @@ impl<'a> ComputeContext<'a> {
/// Get price at height (None if no price data or height out of range).
pub fn price_at(&self, height: Height) -> Option<Dollars> {
self.height_to_price.as_ref()?.get(height.to_usize()).copied()
self.height_to_price
.as_ref()?
.get(height.to_usize())
.copied()
}
/// Get timestamp at height.

View File

@@ -31,8 +31,6 @@ pub fn process_address_updates(
empty_updates: AddressTypeToTypeIndexMap<EmptyAddressDataWithSource>,
loaded_updates: AddressTypeToTypeIndexMap<LoadedAddressDataWithSource>,
) -> Result<()> {
info!("Processing address updates...");
let empty_result = process_empty_addresses(addresses_data, empty_updates)?;
let loaded_result = process_loaded_addresses(addresses_data, loaded_updates)?;
let all_updates = empty_result.merge(loaded_result);

View File

@@ -50,7 +50,11 @@ pub struct CohortMetrics {
impl CohortMetrics {
/// Import all metrics from database.
pub fn forced_import(cfg: &ImportConfig) -> Result<Self> {
///
/// `all_supply` is the supply metrics from the "all" cohort, used as global
/// sources for `*_rel_to_market_cap` and `*_rel_to_circulating_supply` ratios.
/// Pass `None` for the "all" cohort itself.
pub fn forced_import(cfg: &ImportConfig, all_supply: Option<&SupplyMetrics>) -> Result<Self> {
let compute_dollars = cfg.compute_dollars();
let supply = SupplyMetrics::forced_import(cfg)?;
@@ -61,7 +65,7 @@ impl CohortMetrics {
let relative = unrealized
.as_ref()
.map(|u| RelativeMetrics::forced_import(cfg, u, &supply))
.map(|u| RelativeMetrics::forced_import(cfg, u, &supply, all_supply))
.transpose()?;
Ok(Self {
@@ -290,7 +294,6 @@ impl CohortMetrics {
price: Option<&price::Vecs>,
starting_indexes: &Indexes,
height_to_supply: &impl IterableVec<Height, Bitcoin>,
dateindex_to_supply: &impl IterableVec<DateIndex, Bitcoin>,
height_to_market_cap: Option<&impl IterableVec<Height, Dollars>>,
dateindex_to_market_cap: Option<&impl IterableVec<DateIndex, Dollars>>,
exit: &Exit,
@@ -307,20 +310,6 @@ impl CohortMetrics {
)?;
}
if let Some(relative) = self.relative.as_mut() {
relative.compute_rest_part2(
indexes,
starting_indexes,
height_to_supply,
dateindex_to_supply,
height_to_market_cap,
dateindex_to_market_cap,
&self.supply,
self.unrealized.as_ref(),
exit,
)?;
}
Ok(())
}
}

View File

@@ -1,67 +1,70 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Bitcoin, DateIndex, Dollars, Height, Sats, StoredF32, StoredF64, Version};
use vecdb::{
EagerVec, Exit, ImportableVec, IterableCloneableVec, IterableVec, LazyVecFrom1, LazyVecFrom2,
Negate, PcoVec,
};
use brk_types::{Bitcoin, Dollars, Height, Sats, StoredF32, StoredF64, Version};
use vecdb::{IterableCloneableVec, LazyVecFrom2};
use crate::{
Indexes,
grouped::{
ComputedVecsFromDateIndex, ComputedVecsFromHeight, LazyVecsFrom2FromDateIndex,
LazyVecsFromDateIndex, NegPercentageDollarsF32, NegRatio32, PercentageDollarsF32,
PercentageSatsF64, Ratio32, Source, VecBuilderOptions,
},
indexes,
use crate::grouped::{
LazyVecsFrom2FromDateIndex, NegPercentageDollarsF32, NegRatio32, PercentageBtcF64,
PercentageDollarsF32, PercentageSatsF64, Ratio32,
};
use super::{ImportConfig, SupplyMetrics, UnrealizedMetrics};
/// Relative metrics comparing cohort values to global values.
/// All `rel_to_` vecs are lazy - computed on-demand from their sources.
#[derive(Clone, Traversable)]
pub struct RelativeMetrics {
// === Supply Relative to Circulating Supply ===
pub indexes_to_supply_rel_to_circulating_supply: Option<ComputedVecsFromHeight<StoredF64>>,
// === Supply Relative to Circulating Supply (lazy from global supply) ===
pub indexes_to_supply_rel_to_circulating_supply:
Option<LazyVecsFrom2FromDateIndex<StoredF64, Sats, Sats>>,
// === Supply in Profit/Loss Relative to Own Supply ===
pub height_to_supply_in_profit_rel_to_own_supply: EagerVec<PcoVec<Height, StoredF64>>,
pub height_to_supply_in_loss_rel_to_own_supply: EagerVec<PcoVec<Height, StoredF64>>,
// === 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>,
pub indexes_to_supply_in_profit_rel_to_own_supply:
LazyVecsFrom2FromDateIndex<StoredF64, Sats, Sats>,
pub indexes_to_supply_in_loss_rel_to_own_supply:
LazyVecsFrom2FromDateIndex<StoredF64, Sats, Sats>,
// === Supply in Profit/Loss Relative to Circulating Supply ===
// === Supply in Profit/Loss Relative to Circulating Supply (lazy from global supply) ===
pub height_to_supply_in_profit_rel_to_circulating_supply:
Option<EagerVec<PcoVec<Height, StoredF64>>>,
Option<LazyVecFrom2<Height, StoredF64, Height, Bitcoin, Height, Bitcoin>>,
pub height_to_supply_in_loss_rel_to_circulating_supply:
Option<EagerVec<PcoVec<Height, StoredF64>>>,
Option<LazyVecFrom2<Height, StoredF64, Height, Bitcoin, Height, Bitcoin>>,
pub indexes_to_supply_in_profit_rel_to_circulating_supply:
Option<ComputedVecsFromDateIndex<StoredF64>>,
Option<LazyVecsFrom2FromDateIndex<StoredF64, Sats, Sats>>,
pub indexes_to_supply_in_loss_rel_to_circulating_supply:
Option<ComputedVecsFromDateIndex<StoredF64>>,
Option<LazyVecsFrom2FromDateIndex<StoredF64, Sats, Sats>>,
// === Unrealized vs Market Cap ===
pub height_to_unrealized_profit_rel_to_market_cap: EagerVec<PcoVec<Height, StoredF32>>,
pub height_to_unrealized_loss_rel_to_market_cap: EagerVec<PcoVec<Height, StoredF32>>,
// === 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:
LazyVecFrom1<Height, StoredF32, Height, StoredF32>,
pub height_to_net_unrealized_pnl_rel_to_market_cap: EagerVec<PcoVec<Height, StoredF32>>,
pub indexes_to_unrealized_profit_rel_to_market_cap: ComputedVecsFromDateIndex<StoredF32>,
pub indexes_to_unrealized_loss_rel_to_market_cap: ComputedVecsFromDateIndex<StoredF32>,
pub indexes_to_neg_unrealized_loss_rel_to_market_cap: LazyVecsFromDateIndex<StoredF32>,
pub indexes_to_net_unrealized_pnl_rel_to_market_cap: ComputedVecsFromDateIndex<StoredF32>,
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>>,
pub indexes_to_unrealized_profit_rel_to_market_cap:
Option<LazyVecsFrom2FromDateIndex<StoredF32, Dollars, Dollars>>,
pub indexes_to_unrealized_loss_rel_to_market_cap:
Option<LazyVecsFrom2FromDateIndex<StoredF32, Dollars, Dollars>>,
pub indexes_to_neg_unrealized_loss_rel_to_market_cap:
Option<LazyVecsFrom2FromDateIndex<StoredF32, Dollars, Dollars>>,
pub indexes_to_net_unrealized_pnl_rel_to_market_cap:
Option<LazyVecsFrom2FromDateIndex<StoredF32, Dollars, Dollars>>,
// === Unrealized vs Own Market Cap (optional) ===
// === Unrealized vs Own Market Cap (lazy) ===
pub height_to_unrealized_profit_rel_to_own_market_cap:
Option<EagerVec<PcoVec<Height, StoredF32>>>,
Option<LazyVecFrom2<Height, StoredF32, Height, Dollars, Height, Dollars>>,
pub height_to_unrealized_loss_rel_to_own_market_cap:
Option<EagerVec<PcoVec<Height, StoredF32>>>,
Option<LazyVecFrom2<Height, StoredF32, Height, Dollars, Height, Dollars>>,
pub height_to_neg_unrealized_loss_rel_to_own_market_cap:
Option<LazyVecFrom1<Height, StoredF32, Height, StoredF32>>,
Option<LazyVecFrom2<Height, StoredF32, Height, Dollars, Height, Dollars>>,
pub height_to_net_unrealized_pnl_rel_to_own_market_cap:
Option<EagerVec<PcoVec<Height, StoredF32>>>,
Option<LazyVecFrom2<Height, StoredF32, Height, Dollars, Height, Dollars>>,
pub indexes_to_unrealized_profit_rel_to_own_market_cap:
Option<LazyVecsFrom2FromDateIndex<StoredF32, Dollars, Dollars>>,
pub indexes_to_unrealized_loss_rel_to_own_market_cap:
@@ -71,7 +74,7 @@ pub struct RelativeMetrics {
pub indexes_to_net_unrealized_pnl_rel_to_own_market_cap:
Option<LazyVecsFrom2FromDateIndex<StoredF32, Dollars, Dollars>>,
// === Unrealized vs Own Total Unrealized PnL (optional) ===
// === 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:
@@ -92,176 +95,66 @@ pub struct RelativeMetrics {
impl RelativeMetrics {
/// Import relative metrics from database.
///
/// All `rel_to_` metrics are lazy - computed on-demand from their sources.
/// `all_supply` provides global sources for `*_rel_to_market_cap` and `*_rel_to_circulating_supply`.
pub fn forced_import(
cfg: &ImportConfig,
unrealized: &UnrealizedMetrics,
supply: &SupplyMetrics,
all_supply: Option<&SupplyMetrics>,
) -> Result<Self> {
let v0 = Version::ZERO;
let v1 = Version::ONE;
let v2 = Version::new(2);
let extended = cfg.extended();
let compute_rel_to_all = cfg.compute_rel_to_all();
let last = VecBuilderOptions::default().add_last();
// Create sources for lazy neg vecs
let height_to_unrealized_loss_rel_to_market_cap: EagerVec<PcoVec<Height, StoredF32>> =
EagerVec::forced_import(
cfg.db,
&cfg.name("unrealized_loss_rel_to_market_cap"),
cfg.version + v0,
)?;
// Global sources from "all" cohort
let global_supply_sats = all_supply.map(|s| &s.indexes_to_supply.sats);
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 height_to_neg_unrealized_loss_rel_to_market_cap = LazyVecFrom1::transformed::<Negate>(
&cfg.name("neg_unrealized_loss_rel_to_market_cap"),
cfg.version + v0,
height_to_unrealized_loss_rel_to_market_cap.boxed_clone(),
);
let indexes_to_unrealized_loss_rel_to_market_cap =
ComputedVecsFromDateIndex::forced_import(
cfg.db,
&cfg.name("unrealized_loss_rel_to_market_cap"),
Source::Compute,
cfg.version + v1,
cfg.indexes,
last,
)?;
let indexes_to_neg_unrealized_loss_rel_to_market_cap =
LazyVecsFromDateIndex::from_computed::<Negate>(
&cfg.name("neg_unrealized_loss_rel_to_market_cap"),
cfg.version + v1,
indexes_to_unrealized_loss_rel_to_market_cap
.dateindex
.as_ref()
.map(|v| v.boxed_clone()),
&indexes_to_unrealized_loss_rel_to_market_cap,
);
// Optional: own market cap vecs
let height_to_unrealized_loss_rel_to_own_market_cap: Option<
EagerVec<PcoVec<Height, StoredF32>>,
> = (extended && compute_rel_to_all)
.then(|| {
EagerVec::forced_import(
cfg.db,
&cfg.name("unrealized_loss_rel_to_own_market_cap"),
cfg.version + v1,
)
})
.transpose()?;
let height_to_neg_unrealized_loss_rel_to_own_market_cap =
height_to_unrealized_loss_rel_to_own_market_cap
.as_ref()
.map(|source| {
LazyVecFrom1::transformed::<Negate>(
&cfg.name("neg_unrealized_loss_rel_to_own_market_cap"),
cfg.version + v1,
source.boxed_clone(),
)
});
// Optional: own total unrealized pnl vecs (lazy from unrealized sources)
let height_to_unrealized_profit_rel_to_own_total_unrealized_pnl = extended.then(|| {
LazyVecFrom2::transformed::<Ratio32>(
&cfg.name("unrealized_profit_rel_to_own_total_unrealized_pnl"),
cfg.version + v0,
unrealized.height_to_unrealized_profit.boxed_clone(),
unrealized.height_to_total_unrealized_pnl.boxed_clone(),
)
});
let height_to_unrealized_loss_rel_to_own_total_unrealized_pnl = extended.then(|| {
LazyVecFrom2::transformed::<Ratio32>(
&cfg.name("unrealized_loss_rel_to_own_total_unrealized_pnl"),
cfg.version + v0,
unrealized.height_to_unrealized_loss.boxed_clone(),
unrealized.height_to_total_unrealized_pnl.boxed_clone(),
)
});
let height_to_neg_unrealized_loss_rel_to_own_total_unrealized_pnl = extended.then(|| {
LazyVecFrom2::transformed::<NegRatio32>(
&cfg.name("neg_unrealized_loss_rel_to_own_total_unrealized_pnl"),
cfg.version + v0,
unrealized.height_to_unrealized_loss.boxed_clone(),
unrealized.height_to_total_unrealized_pnl.boxed_clone(),
)
});
let height_to_net_unrealized_pnl_rel_to_own_total_unrealized_pnl = extended.then(|| {
LazyVecFrom2::transformed::<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(),
)
});
let indexes_to_unrealized_profit_rel_to_own_total_unrealized_pnl = extended.then(|| {
LazyVecsFrom2FromDateIndex::from_computed::<Ratio32>(
&cfg.name("unrealized_profit_rel_to_own_total_unrealized_pnl"),
cfg.version + v1,
&unrealized.indexes_to_unrealized_profit,
&unrealized.indexes_to_total_unrealized_pnl,
)
});
let indexes_to_unrealized_loss_rel_to_own_total_unrealized_pnl = extended.then(|| {
LazyVecsFrom2FromDateIndex::from_computed::<Ratio32>(
&cfg.name("unrealized_loss_rel_to_own_total_unrealized_pnl"),
cfg.version + v1,
&unrealized.indexes_to_unrealized_loss,
&unrealized.indexes_to_total_unrealized_pnl,
)
});
let indexes_to_neg_unrealized_loss_rel_to_own_total_unrealized_pnl = extended.then(|| {
LazyVecsFrom2FromDateIndex::from_computed::<NegRatio32>(
&cfg.name("neg_unrealized_loss_rel_to_own_total_unrealized_pnl"),
cfg.version + v1,
&unrealized.indexes_to_unrealized_loss,
&unrealized.indexes_to_total_unrealized_pnl,
)
});
let indexes_to_net_unrealized_pnl_rel_to_own_total_unrealized_pnl = extended.then(|| {
LazyVecsFrom2FromDateIndex::from_computed::<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,
)
});
// 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();
Ok(Self {
// === Supply Relative to Circulating Supply ===
indexes_to_supply_rel_to_circulating_supply: compute_rel_to_all
.then(|| {
ComputedVecsFromHeight::forced_import(
cfg.db,
&cfg.name("supply_rel_to_circulating_supply"),
Source::Compute,
cfg.version + v1,
cfg.indexes,
last,
)
})
.transpose()?,
// === 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())
.then(|| {
LazyVecsFrom2FromDateIndex::from_computed::<PercentageSatsF64>(
&cfg.name("supply_rel_to_circulating_supply"),
cfg.version + v1,
&supply.indexes_to_supply.sats,
global_supply_sats.unwrap(),
)
}),
// === Supply in Profit/Loss Relative to Own Supply ===
height_to_supply_in_profit_rel_to_own_supply: EagerVec::forced_import(
cfg.db,
// === 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,
)?,
height_to_supply_in_loss_rel_to_own_supply: EagerVec::forced_import(
cfg.db,
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:
LazyVecsFrom2FromDateIndex::from_computed::<PercentageSatsF64>(
&cfg.name("supply_in_profit_rel_to_own_supply"),
@@ -278,412 +171,283 @@ impl RelativeMetrics {
&supply.indexes_to_supply.sats,
),
// === Supply in Profit/Loss Relative to Circulating Supply ===
height_to_supply_in_profit_rel_to_circulating_supply: compute_rel_to_all
.then(|| {
EagerVec::forced_import(
cfg.db,
&cfg.name("supply_in_profit_rel_to_circulating_supply"),
cfg.version + v1,
)
})
.transpose()?,
height_to_supply_in_loss_rel_to_circulating_supply: compute_rel_to_all
.then(|| {
EagerVec::forced_import(
cfg.db,
&cfg.name("supply_in_loss_rel_to_circulating_supply"),
cfg.version + v1,
)
})
.transpose()?,
indexes_to_supply_in_profit_rel_to_circulating_supply: compute_rel_to_all
.then(|| {
ComputedVecsFromDateIndex::forced_import(
cfg.db,
&cfg.name("supply_in_profit_rel_to_circulating_supply"),
Source::Compute,
cfg.version + v1,
cfg.indexes,
last,
)
})
.transpose()?,
indexes_to_supply_in_loss_rel_to_circulating_supply: compute_rel_to_all
.then(|| {
ComputedVecsFromDateIndex::forced_import(
cfg.db,
&cfg.name("supply_in_loss_rel_to_circulating_supply"),
Source::Compute,
cfg.version + v1,
cfg.indexes,
last,
)
})
.transpose()?,
// === 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())
.then(|| {
LazyVecFrom2::transformed::<PercentageBtcF64>(
&cfg.name("supply_in_profit_rel_to_circulating_supply"),
cfg.version + v1,
unrealized
.height_to_supply_in_profit_value
.bitcoin
.boxed_clone(),
global_supply_btc.unwrap().boxed_clone(),
)
}),
height_to_supply_in_loss_rel_to_circulating_supply: (compute_rel_to_all
&& global_supply_btc.is_some())
.then(|| {
LazyVecFrom2::transformed::<PercentageBtcF64>(
&cfg.name("supply_in_loss_rel_to_circulating_supply"),
cfg.version + v1,
unrealized
.height_to_supply_in_loss_value
.bitcoin
.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(|| {
LazyVecsFrom2FromDateIndex::from_computed::<PercentageSatsF64>(
&cfg.name("supply_in_profit_rel_to_circulating_supply"),
cfg.version + v1,
&unrealized.indexes_to_supply_in_profit.sats,
global_supply_sats.unwrap(),
)
}),
indexes_to_supply_in_loss_rel_to_circulating_supply: (compute_rel_to_all
&& global_supply_sats.is_some())
.then(|| {
LazyVecsFrom2FromDateIndex::from_computed::<PercentageSatsF64>(
&cfg.name("supply_in_loss_rel_to_circulating_supply"),
cfg.version + v1,
&unrealized.indexes_to_supply_in_loss.sats,
global_supply_sats.unwrap(),
)
}),
// === Unrealized vs Market Cap ===
height_to_unrealized_profit_rel_to_market_cap: EagerVec::forced_import(
cfg.db,
&cfg.name("unrealized_profit_rel_to_market_cap"),
cfg.version + v0,
)?,
height_to_unrealized_loss_rel_to_market_cap,
height_to_neg_unrealized_loss_rel_to_market_cap,
height_to_net_unrealized_pnl_rel_to_market_cap: EagerVec::forced_import(
cfg.db,
&cfg.name("net_unrealized_pnl_rel_to_market_cap"),
cfg.version + v1,
)?,
indexes_to_unrealized_profit_rel_to_market_cap:
ComputedVecsFromDateIndex::forced_import(
cfg.db,
// === 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"),
Source::Compute,
cfg.version + v1,
cfg.indexes,
last,
)?,
indexes_to_unrealized_loss_rel_to_market_cap,
indexes_to_neg_unrealized_loss_rel_to_market_cap,
indexes_to_net_unrealized_pnl_rel_to_market_cap:
ComputedVecsFromDateIndex::forced_import(
cfg.db,
cfg.version + v0,
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 + v0,
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 + v0,
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"),
Source::Compute,
cfg.version + v1,
cfg.indexes,
last,
)?,
unrealized.height_to_net_unrealized_pnl.boxed_clone(),
mc.boxed_clone(),
)
}),
indexes_to_unrealized_profit_rel_to_market_cap: global_market_cap.map(|mc| {
LazyVecsFrom2FromDateIndex::from_computed::<PercentageDollarsF32>(
&cfg.name("unrealized_profit_rel_to_market_cap"),
cfg.version + v2,
&unrealized.indexes_to_unrealized_profit,
mc,
)
}),
indexes_to_unrealized_loss_rel_to_market_cap: global_market_cap.map(|mc| {
LazyVecsFrom2FromDateIndex::from_computed::<PercentageDollarsF32>(
&cfg.name("unrealized_loss_rel_to_market_cap"),
cfg.version + v2,
&unrealized.indexes_to_unrealized_loss,
mc,
)
}),
indexes_to_neg_unrealized_loss_rel_to_market_cap: global_market_cap.map(|mc| {
LazyVecsFrom2FromDateIndex::from_computed::<NegPercentageDollarsF32>(
&cfg.name("neg_unrealized_loss_rel_to_market_cap"),
cfg.version + v2,
&unrealized.indexes_to_unrealized_loss,
mc,
)
}),
indexes_to_net_unrealized_pnl_rel_to_market_cap: global_market_cap.map(|mc| {
LazyVecsFrom2FromDateIndex::from_computed::<PercentageDollarsF32>(
&cfg.name("net_unrealized_pnl_rel_to_market_cap"),
cfg.version + v2,
&unrealized.indexes_to_net_unrealized_pnl,
mc,
)
}),
// === Unrealized vs Own Market Cap (optional) ===
// === Unrealized vs Own Market Cap (lazy, optional) ===
height_to_unrealized_profit_rel_to_own_market_cap: (extended && compute_rel_to_all)
.then(|| {
EagerVec::forced_import(
cfg.db,
&cfg.name("unrealized_profit_rel_to_own_market_cap"),
cfg.version + v1,
)
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(),
)
})
})
.transpose()?,
height_to_unrealized_loss_rel_to_own_market_cap,
height_to_neg_unrealized_loss_rel_to_own_market_cap,
.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(|| {
EagerVec::forced_import(
cfg.db,
&cfg.name("net_unrealized_pnl_rel_to_own_market_cap"),
cfg.version + v2,
)
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(),
)
})
})
.transpose()?,
.flatten(),
indexes_to_unrealized_profit_rel_to_own_market_cap: (extended && compute_rel_to_all)
.then(|| {
supply
.indexes_to_supply
.dollars
.as_ref()
.map(|supply_dollars| {
LazyVecsFrom2FromDateIndex::from_computed::<PercentageDollarsF32>(
&cfg.name("unrealized_profit_rel_to_own_market_cap"),
cfg.version + v2,
&unrealized.indexes_to_unrealized_profit,
supply_dollars,
)
})
own_market_cap.map(|mc| {
LazyVecsFrom2FromDateIndex::from_computed::<PercentageDollarsF32>(
&cfg.name("unrealized_profit_rel_to_own_market_cap"),
cfg.version + v2,
&unrealized.indexes_to_unrealized_profit,
mc,
)
})
})
.flatten(),
indexes_to_unrealized_loss_rel_to_own_market_cap: (extended && compute_rel_to_all)
.then(|| {
supply
.indexes_to_supply
.dollars
.as_ref()
.map(|supply_dollars| {
LazyVecsFrom2FromDateIndex::from_computed::<PercentageDollarsF32>(
&cfg.name("unrealized_loss_rel_to_own_market_cap"),
cfg.version + v2,
&unrealized.indexes_to_unrealized_loss,
supply_dollars,
)
})
own_market_cap.map(|mc| {
LazyVecsFrom2FromDateIndex::from_computed::<PercentageDollarsF32>(
&cfg.name("unrealized_loss_rel_to_own_market_cap"),
cfg.version + v2,
&unrealized.indexes_to_unrealized_loss,
mc,
)
})
})
.flatten(),
indexes_to_neg_unrealized_loss_rel_to_own_market_cap: (extended && compute_rel_to_all)
.then(|| {
supply
.indexes_to_supply
.dollars
.as_ref()
.map(|supply_dollars| {
LazyVecsFrom2FromDateIndex::from_computed::<NegPercentageDollarsF32>(
&cfg.name("neg_unrealized_loss_rel_to_own_market_cap"),
cfg.version + v2,
&unrealized.indexes_to_unrealized_loss,
supply_dollars,
)
})
own_market_cap.map(|mc| {
LazyVecsFrom2FromDateIndex::from_computed::<NegPercentageDollarsF32>(
&cfg.name("neg_unrealized_loss_rel_to_own_market_cap"),
cfg.version + v2,
&unrealized.indexes_to_unrealized_loss,
mc,
)
})
})
.flatten(),
indexes_to_net_unrealized_pnl_rel_to_own_market_cap: (extended && compute_rel_to_all)
.then(|| {
supply
.indexes_to_supply
.dollars
.as_ref()
.map(|supply_dollars| {
LazyVecsFrom2FromDateIndex::from_computed::<PercentageDollarsF32>(
&cfg.name("net_unrealized_pnl_rel_to_own_market_cap"),
cfg.version + v2,
&unrealized.indexes_to_net_unrealized_pnl,
supply_dollars,
)
})
own_market_cap.map(|mc| {
LazyVecsFrom2FromDateIndex::from_computed::<PercentageDollarsF32>(
&cfg.name("net_unrealized_pnl_rel_to_own_market_cap"),
cfg.version + v2,
&unrealized.indexes_to_net_unrealized_pnl,
mc,
)
})
})
.flatten(),
// === Unrealized vs Own Total Unrealized PnL (optional) ===
height_to_unrealized_profit_rel_to_own_total_unrealized_pnl,
height_to_unrealized_loss_rel_to_own_total_unrealized_pnl,
height_to_neg_unrealized_loss_rel_to_own_total_unrealized_pnl,
height_to_net_unrealized_pnl_rel_to_own_total_unrealized_pnl,
indexes_to_unrealized_profit_rel_to_own_total_unrealized_pnl,
indexes_to_unrealized_loss_rel_to_own_total_unrealized_pnl,
indexes_to_neg_unrealized_loss_rel_to_own_total_unrealized_pnl,
indexes_to_net_unrealized_pnl_rel_to_own_total_unrealized_pnl,
// === Unrealized vs Own Total Unrealized PnL (lazy, optional) ===
height_to_unrealized_profit_rel_to_own_total_unrealized_pnl: extended.then(|| {
LazyVecFrom2::transformed::<Ratio32>(
&cfg.name("unrealized_profit_rel_to_own_total_unrealized_pnl"),
cfg.version + v0,
unrealized.height_to_unrealized_profit.boxed_clone(),
unrealized.height_to_total_unrealized_pnl.boxed_clone(),
)
}),
height_to_unrealized_loss_rel_to_own_total_unrealized_pnl: extended.then(|| {
LazyVecFrom2::transformed::<Ratio32>(
&cfg.name("unrealized_loss_rel_to_own_total_unrealized_pnl"),
cfg.version + v0,
unrealized.height_to_unrealized_loss.boxed_clone(),
unrealized.height_to_total_unrealized_pnl.boxed_clone(),
)
}),
height_to_neg_unrealized_loss_rel_to_own_total_unrealized_pnl: extended.then(|| {
LazyVecFrom2::transformed::<NegRatio32>(
&cfg.name("neg_unrealized_loss_rel_to_own_total_unrealized_pnl"),
cfg.version + v0,
unrealized.height_to_unrealized_loss.boxed_clone(),
unrealized.height_to_total_unrealized_pnl.boxed_clone(),
)
}),
height_to_net_unrealized_pnl_rel_to_own_total_unrealized_pnl: extended.then(|| {
LazyVecFrom2::transformed::<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(|| {
LazyVecsFrom2FromDateIndex::from_computed::<Ratio32>(
&cfg.name("unrealized_profit_rel_to_own_total_unrealized_pnl"),
cfg.version + v1,
&unrealized.indexes_to_unrealized_profit,
&unrealized.indexes_to_total_unrealized_pnl,
)
}),
indexes_to_unrealized_loss_rel_to_own_total_unrealized_pnl: extended.then(|| {
LazyVecsFrom2FromDateIndex::from_computed::<Ratio32>(
&cfg.name("unrealized_loss_rel_to_own_total_unrealized_pnl"),
cfg.version + v1,
&unrealized.indexes_to_unrealized_loss,
&unrealized.indexes_to_total_unrealized_pnl,
)
}),
indexes_to_neg_unrealized_loss_rel_to_own_total_unrealized_pnl: extended.then(|| {
LazyVecsFrom2FromDateIndex::from_computed::<NegRatio32>(
&cfg.name("neg_unrealized_loss_rel_to_own_total_unrealized_pnl"),
cfg.version + v1,
&unrealized.indexes_to_unrealized_loss,
&unrealized.indexes_to_total_unrealized_pnl,
)
}),
indexes_to_net_unrealized_pnl_rel_to_own_total_unrealized_pnl: extended.then(|| {
LazyVecsFrom2FromDateIndex::from_computed::<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,
)
}),
})
}
/// Second phase of computed metrics (ratios, relative values).
///
/// This computes percentage ratios comparing cohort metrics to global metrics:
/// - Supply relative to circulating supply
/// - Supply in profit/loss relative to own supply and circulating supply
/// - Unrealized profit/loss relative to market cap, total unrealized
///
/// See `stateful/common/compute.rs` lines 800-1200 for the full original implementation.
#[allow(clippy::too_many_arguments)]
pub fn compute_rest_part2(
&mut self,
indexes: &indexes::Vecs,
starting_indexes: &Indexes,
height_to_supply: &impl IterableVec<Height, Bitcoin>,
dateindex_to_supply: &impl IterableVec<DateIndex, Bitcoin>,
height_to_market_cap: Option<&impl IterableVec<Height, Dollars>>,
dateindex_to_market_cap: Option<&impl IterableVec<DateIndex, Dollars>>,
supply: &SupplyMetrics,
unrealized: Option<&super::UnrealizedMetrics>,
exit: &Exit,
) -> Result<()> {
// === Supply Relative to Circulating Supply ===
if let Some(v) = self.indexes_to_supply_rel_to_circulating_supply.as_mut() {
v.compute_all(indexes, starting_indexes, exit, |v| {
v.compute_percentage(
starting_indexes.height,
&supply.height_to_supply_value.bitcoin,
height_to_supply,
exit,
)?;
Ok(())
})?;
}
// === Supply in Profit/Loss Relative to Own Supply ===
// Note: indexes_to_* versions are now lazy (LazyVecsFrom2FromDateIndex)
if let Some(unrealized) = unrealized {
self.height_to_supply_in_profit_rel_to_own_supply
.compute_percentage(
starting_indexes.height,
&unrealized.height_to_supply_in_profit_value.bitcoin,
&supply.height_to_supply_value.bitcoin,
exit,
)?;
self.height_to_supply_in_loss_rel_to_own_supply
.compute_percentage(
starting_indexes.height,
&unrealized.height_to_supply_in_loss_value.bitcoin,
&supply.height_to_supply_value.bitcoin,
exit,
)?;
}
// === Supply in Profit/Loss Relative to Circulating Supply ===
if let (Some(unrealized), Some(v)) = (
unrealized,
self.height_to_supply_in_profit_rel_to_circulating_supply
.as_mut(),
) {
v.compute_percentage(
starting_indexes.height,
&unrealized.height_to_supply_in_profit_value.bitcoin,
height_to_supply,
exit,
)?;
}
if let (Some(unrealized), Some(v)) = (
unrealized,
self.height_to_supply_in_loss_rel_to_circulating_supply
.as_mut(),
) {
v.compute_percentage(
starting_indexes.height,
&unrealized.height_to_supply_in_loss_value.bitcoin,
height_to_supply,
exit,
)?;
}
// === Unrealized vs Market Cap ===
if let (Some(unrealized), Some(height_to_mc)) = (unrealized, height_to_market_cap) {
self.height_to_unrealized_profit_rel_to_market_cap
.compute_percentage(
starting_indexes.height,
&unrealized.height_to_unrealized_profit,
height_to_mc,
exit,
)?;
self.height_to_unrealized_loss_rel_to_market_cap
.compute_percentage(
starting_indexes.height,
&unrealized.height_to_unrealized_loss,
height_to_mc,
exit,
)?;
self.height_to_net_unrealized_pnl_rel_to_market_cap
.compute_percentage(
starting_indexes.height,
&unrealized.height_to_net_unrealized_pnl,
height_to_mc,
exit,
)?;
}
if let Some(dateindex_to_mc) = dateindex_to_market_cap
&& let Some(unrealized) = unrealized
{
self.indexes_to_unrealized_profit_rel_to_market_cap
.compute_all(starting_indexes, exit, |v| {
v.compute_percentage(
starting_indexes.dateindex,
&unrealized.dateindex_to_unrealized_profit,
dateindex_to_mc,
exit,
)?;
Ok(())
})?;
self.indexes_to_unrealized_loss_rel_to_market_cap
.compute_all(starting_indexes, exit, |v| {
v.compute_percentage(
starting_indexes.dateindex,
&unrealized.dateindex_to_unrealized_loss,
dateindex_to_mc,
exit,
)?;
Ok(())
})?;
}
if let Some(dateindex_to_mc) = dateindex_to_market_cap
&& let Some(unrealized) = unrealized
&& let Some(dateindex_vec) = unrealized.indexes_to_net_unrealized_pnl.dateindex.as_ref()
{
self.indexes_to_net_unrealized_pnl_rel_to_market_cap
.compute_all(starting_indexes, exit, |v| {
v.compute_percentage(
starting_indexes.dateindex,
dateindex_vec,
dateindex_to_mc,
exit,
)?;
Ok(())
})?;
}
// === Supply in Profit/Loss Relative to Circulating Supply (indexes) ===
if let Some(v) = self
.indexes_to_supply_in_profit_rel_to_circulating_supply
.as_mut()
&& let Some(unrealized) = unrealized
&& let Some(dateindex_vec) = unrealized
.indexes_to_supply_in_profit
.bitcoin
.dateindex
.as_ref()
{
v.compute_all(starting_indexes, exit, |vec| {
vec.compute_percentage(
starting_indexes.dateindex,
dateindex_vec,
dateindex_to_supply,
exit,
)?;
Ok(())
})?;
}
if let Some(v) = self
.indexes_to_supply_in_loss_rel_to_circulating_supply
.as_mut()
&& let Some(unrealized) = unrealized
&& let Some(dateindex_vec) = unrealized
.indexes_to_supply_in_loss
.bitcoin
.dateindex
.as_ref()
{
v.compute_all(starting_indexes, exit, |vec| {
vec.compute_percentage(
starting_indexes.dateindex,
dateindex_vec,
dateindex_to_supply,
exit,
)?;
Ok(())
})?;
}
// === Unrealized vs Own Market Cap ===
// own_market_cap = supply_value.dollars
// Note: indexes_to_* versions are now lazy (LazyVecsFrom2FromDateIndex)
if let Some(unrealized) = unrealized {
if let Some(v) = self
.height_to_unrealized_profit_rel_to_own_market_cap
.as_mut()
&& let Some(supply_dollars) = supply.height_to_supply_value.dollars.as_ref()
{
v.compute_percentage(
starting_indexes.height,
&unrealized.height_to_unrealized_profit,
supply_dollars,
exit,
)?;
}
if let Some(v) = self
.height_to_unrealized_loss_rel_to_own_market_cap
.as_mut()
&& let Some(supply_dollars) = supply.height_to_supply_value.dollars.as_ref()
{
v.compute_percentage(
starting_indexes.height,
&unrealized.height_to_unrealized_loss,
supply_dollars,
exit,
)?;
}
if let Some(v) = self
.height_to_net_unrealized_pnl_rel_to_own_market_cap
.as_mut()
&& let Some(supply_dollars) = supply.height_to_supply_value.dollars.as_ref()
{
v.compute_percentage(
starting_indexes.height,
&unrealized.height_to_net_unrealized_pnl,
supply_dollars,
exit,
)?;
}
}
Ok(())
}
}

View File

@@ -7,7 +7,6 @@ mod range_map;
mod states;
mod vecs;
use states::*;
pub use range_map::RangeMap;
pub use vecs::Vecs;

View File

@@ -89,6 +89,16 @@ impl Vecs {
let utxo_cohorts = UTXOCohorts::forced_import(&db, version, indexes, price, &states_path)?;
// Create address cohorts with reference to utxo "all" cohort's supply for global ratios
let address_cohorts = AddressCohorts::forced_import(
&db,
version,
indexes,
price,
&states_path,
Some(&utxo_cohorts.all.metrics.supply),
)?;
// Create address data BytesVecs first so we can also use them for identity mappings
let loadedaddressindex_to_loadedaddressdata = BytesVec::forced_import_with(
vecdb::ImportOptions::new(&db, "loadedaddressdata", v0)
@@ -212,14 +222,7 @@ impl Vecs {
)?,
utxo_cohorts,
address_cohorts: AddressCohorts::forced_import(
&db,
version,
indexes,
price,
&states_path,
)?,
address_cohorts,
any_address_indexes: AnyAddressIndexesVecs::forced_import(&db, v0)?,
addresses_data: AddressesDataVecs {
@@ -347,10 +350,6 @@ impl Vecs {
})
.collect();
info!(
"State recovery: resumed from checkpoint at height {}",
recovered_height
);
(recovered_height, chain_state)
};
@@ -453,16 +452,6 @@ impl Vecs {
.bitcoin
.clone();
let dateindex_to_supply = self
.utxo_cohorts
.all
.metrics
.supply
.indexes_to_supply
.bitcoin
.dateindex
.clone();
let height_to_market_cap = self.height_to_market_cap.clone();
let dateindex_to_market_cap = self
@@ -470,7 +459,6 @@ impl Vecs {
.as_ref()
.map(|v| v.dateindex.u().clone());
let dateindex_to_supply_ref = dateindex_to_supply.u();
let height_to_market_cap_ref = height_to_market_cap.as_ref();
let dateindex_to_market_cap_ref = dateindex_to_market_cap.as_ref();
@@ -481,7 +469,6 @@ impl Vecs {
price,
starting_indexes,
height_to_supply,
dateindex_to_supply_ref,
height_to_market_cap_ref,
dateindex_to_market_cap_ref,
exit,