mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-24 06:39:58 -07:00
global: snapshot
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user