global: snapshot

This commit is contained in:
nym21
2026-02-04 22:27:44 +01:00
parent 9b409799c8
commit 0433e3b256
12 changed files with 336 additions and 204 deletions

View File

@@ -56,10 +56,10 @@ impl AddressCohorts {
}))
}
/// Apply a function to each aggregate cohort with its source cohorts.
fn for_each_aggregate<F>(&mut self, mut f: F) -> Result<()>
/// Apply a function to each aggregate cohort with its source cohorts (in parallel).
fn for_each_aggregate<F>(&mut self, f: F) -> Result<()>
where
F: FnMut(&mut AddressCohortVecs, Vec<&AddressCohortVecs>) -> Result<()>,
F: Fn(&mut AddressCohortVecs, Vec<&AddressCohortVecs>) -> Result<()> + Sync,
{
let by_amount_range = &self.0.amount_range;
@@ -80,10 +80,9 @@ impl AddressCohorts {
})
.collect();
for (vecs, sources) in pairs {
f(vecs, sources)?;
}
Ok(())
pairs
.into_par_iter()
.try_for_each(|(vecs, sources)| f(vecs, sources))
}
/// Compute overlapping cohorts from component amount_range cohorts.
@@ -105,22 +104,24 @@ impl AddressCohorts {
starting_indexes: &ComputeIndexes,
exit: &Exit,
) -> Result<()> {
// 1. Compute all metrics except net_sentiment
self.par_iter_mut()
.try_for_each(|v| v.compute_rest_part1(indexes, price, starting_indexes, exit))
}
.try_for_each(|v| v.compute_rest_part1(indexes, price, starting_indexes, exit))?;
/// Recompute net_sentiment for aggregate cohorts as weighted average of source cohorts.
pub fn compute_aggregate_net_sentiment(
&mut self,
indexes: &indexes::Vecs,
starting_indexes: &ComputeIndexes,
exit: &Exit,
) -> Result<()> {
// 2. Compute net_sentiment.height for separate cohorts (greed - pain)
self.par_iter_separate_mut()
.try_for_each(|v| v.metrics.compute_net_sentiment_height(starting_indexes, exit))?;
// 3. Compute net_sentiment.height for aggregate cohorts (weighted average)
self.for_each_aggregate(|vecs, sources| {
let metrics: Vec<_> = sources.iter().map(|v| &v.metrics).collect();
vecs.metrics
.compute_net_sentiment_from_others(starting_indexes, &metrics, indexes, exit)
})
.compute_net_sentiment_from_others(starting_indexes, &metrics, exit)
})?;
// 4. Compute net_sentiment dateindex for ALL cohorts
self.par_iter_mut()
.try_for_each(|v| v.metrics.compute_net_sentiment_rest(indexes, starting_indexes, exit))
}
/// Second phase of post-processing: compute relative metrics.

View File

@@ -152,10 +152,10 @@ impl UTXOCohorts {
}))
}
/// Apply a function to each aggregate cohort with its source cohorts.
fn for_each_aggregate<F>(&mut self, mut f: F) -> Result<()>
/// Apply a function to each aggregate cohort with its source cohorts (in parallel).
fn for_each_aggregate<F>(&mut self, f: F) -> Result<()>
where
F: FnMut(&mut UTXOCohortVecs, Vec<&UTXOCohortVecs>) -> Result<()>,
F: Fn(&mut UTXOCohortVecs, Vec<&UTXOCohortVecs>) -> Result<()> + Sync,
{
let by_age_range = &self.0.age_range;
let by_amount_range = &self.0.amount_range;
@@ -215,10 +215,9 @@ impl UTXOCohorts {
}))
.collect();
for (vecs, sources) in pairs {
f(vecs, sources)?;
}
Ok(())
pairs
.into_par_iter()
.try_for_each(|(vecs, sources)| f(vecs, sources))
}
/// Compute overlapping cohorts from component age/amount range cohorts.
@@ -240,26 +239,24 @@ impl UTXOCohorts {
starting_indexes: &ComputeIndexes,
exit: &Exit,
) -> Result<()> {
// 1. Compute all metrics except net_sentiment
self.par_iter_mut()
.try_for_each(|v| v.compute_rest_part1(indexes, price, starting_indexes, exit))
}
.try_for_each(|v| v.compute_rest_part1(indexes, price, starting_indexes, exit))?;
/// Recompute net_sentiment for aggregate cohorts as weighted average of source cohorts.
pub fn compute_aggregate_net_sentiment(
&mut self,
indexes: &indexes::Vecs,
starting_indexes: &ComputeIndexes,
exit: &Exit,
) -> Result<()> {
// 2. Compute net_sentiment.height for separate cohorts (greed - pain)
self.par_iter_separate_mut()
.try_for_each(|v| v.metrics.compute_net_sentiment_height(starting_indexes, exit))?;
// 3. Compute net_sentiment.height for aggregate cohorts (weighted average)
self.for_each_aggregate(|vecs, sources| {
let metrics: Vec<_> = sources.iter().map(|v| &v.metrics).collect();
vecs.metrics.compute_net_sentiment_from_others(
starting_indexes,
&metrics,
indexes,
exit,
)
})
vecs.metrics
.compute_net_sentiment_from_others(starting_indexes, &metrics, exit)
})?;
// 4. Compute net_sentiment dateindex for ALL cohorts
self.par_iter_mut()
.try_for_each(|v| v.metrics.compute_net_sentiment_rest(indexes, starting_indexes, exit))
}
/// Second phase of post-processing: compute relative metrics.

View File

@@ -42,10 +42,6 @@ pub fn compute_rest_part1(
utxo_cohorts.compute_rest_part1(indexes, price, starting_indexes, exit)?;
address_cohorts.compute_rest_part1(indexes, price, starting_indexes, exit)?;
// Recompute net_sentiment for aggregate cohorts as weighted average
utxo_cohorts.compute_aggregate_net_sentiment(indexes, starting_indexes, exit)?;
address_cohorts.compute_aggregate_net_sentiment(indexes, starting_indexes, exit)?;
Ok(())
}

View File

@@ -296,16 +296,17 @@ impl CohortMetrics {
Ok(())
}
/// Compute net_sentiment as capital-weighted average of component cohorts.
/// Compute net_sentiment.height as capital-weighted average of component cohorts.
///
/// For aggregate cohorts, the simple greed-pain formula produces values outside
/// the range of components due to asymmetric weighting. This recomputes net_sentiment
/// the range of components due to asymmetric weighting. This computes net_sentiment
/// as a proper weighted average using realized_cap as weight.
///
/// Only computes height; dateindex derivation is done separately via compute_net_sentiment_rest.
pub fn compute_net_sentiment_from_others(
&mut self,
starting_indexes: &ComputeIndexes,
others: &[&Self],
indexes: &indexes::Vecs,
exit: &Exit,
) -> Result<()> {
let Some(unrealized) = self.unrealized.as_mut() else {
@@ -325,14 +326,10 @@ impl CohortMetrics {
return Ok(());
}
unrealized
Ok(unrealized
.net_sentiment
.height
.compute_weighted_average_of_others(starting_indexes.height, &weights, &values, exit)?;
unrealized
.net_sentiment
.compute_rest(indexes, starting_indexes, exit)
.compute_weighted_average_of_others(starting_indexes.height, &weights, &values, exit)?)
}
/// First phase of computed metrics (indexes from height).
@@ -389,4 +386,31 @@ impl CohortMetrics {
Ok(())
}
/// Compute net_sentiment.height for separate cohorts (greed - pain).
/// Called only for separate cohorts; aggregates compute via weighted average in compute_from_stateful.
pub fn compute_net_sentiment_height(
&mut self,
starting_indexes: &ComputeIndexes,
exit: &Exit,
) -> Result<()> {
if let Some(unrealized) = self.unrealized.as_mut() {
unrealized.compute_net_sentiment_height(starting_indexes, exit)?;
}
Ok(())
}
/// Compute net_sentiment dateindex derivation from height.
/// Called for ALL cohorts after height is computed.
pub fn compute_net_sentiment_rest(
&mut self,
indexes: &indexes::Vecs,
starting_indexes: &ComputeIndexes,
exit: &Exit,
) -> Result<()> {
if let Some(unrealized) = self.unrealized.as_mut() {
unrealized.compute_net_sentiment_rest(indexes, starting_indexes, exit)?;
}
Ok(())
}
}

View File

@@ -598,17 +598,32 @@ impl UnrealizedMetrics {
)?)
})?;
// Net sentiment: greed - pain
self.net_sentiment
.compute_all(indexes, starting_indexes, exit, |vec| {
Ok(vec.compute_subtract(
starting_indexes.height,
&self.greed_index.height,
&self.pain_index.height,
exit,
)?)
})?;
// Net sentiment height (greed - pain) computed separately for separate cohorts only
// Aggregate cohorts compute it via weighted average in compute_from_stateful
// Dateindex derivation for ALL cohorts happens in compute_net_sentiment_rest
Ok(())
}
/// Compute net_sentiment.height for separate cohorts (greed - pain).
/// Aggregate cohorts skip this - their height is computed via weighted average in compute_from_stateful.
pub fn compute_net_sentiment_height(&mut self, starting_indexes: &ComputeIndexes, exit: &Exit) -> Result<()> {
Ok(self.net_sentiment.height.compute_subtract(
starting_indexes.height,
&self.greed_index.height,
&self.pain_index.height,
exit,
)?)
}
/// Compute net_sentiment dateindex derivation from height.
/// Called for ALL cohorts after height is computed (either via greed-pain or weighted avg).
pub fn compute_net_sentiment_rest(
&mut self,
indexes: &indexes::Vecs,
starting_indexes: &ComputeIndexes,
exit: &Exit,
) -> Result<()> {
self.net_sentiment.compute_rest(indexes, starting_indexes, exit)
}
}