global: snapshot

This commit is contained in:
nym21
2026-03-05 16:11:25 +01:00
parent 6f2a87be4f
commit eedb8d22c1
61 changed files with 2035 additions and 2757 deletions
+2 -2
View File
@@ -7,7 +7,7 @@ use std::collections::BTreeMap;
use brk_cohort::{
AGE_RANGE_NAMES, AMOUNT_RANGE_NAMES, EPOCH_NAMES, GE_AMOUNT_NAMES, LT_AMOUNT_NAMES,
MAX_AGE_NAMES, MIN_AGE_NAMES, SPENDABLE_TYPE_NAMES, TERM_NAMES, YEAR_NAMES,
MAX_AGE_NAMES, MIN_AGE_NAMES, SPENDABLE_TYPE_NAMES, TERM_NAMES, CLASS_NAMES,
};
use brk_types::{Index, PoolSlug, pools};
use serde::Serialize;
@@ -55,7 +55,7 @@ impl CohortConstants {
vec![
("TERM_NAMES", to_value(&TERM_NAMES)),
("EPOCH_NAMES", to_value(&EPOCH_NAMES)),
("YEAR_NAMES", to_value(&YEAR_NAMES)),
("CLASS_NAMES", to_value(&CLASS_NAMES)),
("SPENDABLE_TYPE_NAMES", to_value(&SPENDABLE_TYPE_NAMES)),
("AGE_RANGE_NAMES", to_value(&AGE_RANGE_NAMES)),
("MAX_AGE_NAMES", to_value(&MAX_AGE_NAMES)),
File diff suppressed because it is too large Load Diff
@@ -5,8 +5,8 @@ use serde::Serialize;
use super::{CohortName, Filter};
/// Year values
pub const YEAR_VALUES: ByYear<Year> = ByYear {
/// Class values
pub const CLASS_VALUES: ByClass<Year> = ByClass {
_2009: Year::new(2009),
_2010: Year::new(2010),
_2011: Year::new(2011),
@@ -27,52 +27,52 @@ pub const YEAR_VALUES: ByYear<Year> = ByYear {
_2026: Year::new(2026),
};
/// Year filters
pub const YEAR_FILTERS: ByYear<Filter> = ByYear {
_2009: Filter::Year(YEAR_VALUES._2009),
_2010: Filter::Year(YEAR_VALUES._2010),
_2011: Filter::Year(YEAR_VALUES._2011),
_2012: Filter::Year(YEAR_VALUES._2012),
_2013: Filter::Year(YEAR_VALUES._2013),
_2014: Filter::Year(YEAR_VALUES._2014),
_2015: Filter::Year(YEAR_VALUES._2015),
_2016: Filter::Year(YEAR_VALUES._2016),
_2017: Filter::Year(YEAR_VALUES._2017),
_2018: Filter::Year(YEAR_VALUES._2018),
_2019: Filter::Year(YEAR_VALUES._2019),
_2020: Filter::Year(YEAR_VALUES._2020),
_2021: Filter::Year(YEAR_VALUES._2021),
_2022: Filter::Year(YEAR_VALUES._2022),
_2023: Filter::Year(YEAR_VALUES._2023),
_2024: Filter::Year(YEAR_VALUES._2024),
_2025: Filter::Year(YEAR_VALUES._2025),
_2026: Filter::Year(YEAR_VALUES._2026),
/// Class filters
pub const CLASS_FILTERS: ByClass<Filter> = ByClass {
_2009: Filter::Class(CLASS_VALUES._2009),
_2010: Filter::Class(CLASS_VALUES._2010),
_2011: Filter::Class(CLASS_VALUES._2011),
_2012: Filter::Class(CLASS_VALUES._2012),
_2013: Filter::Class(CLASS_VALUES._2013),
_2014: Filter::Class(CLASS_VALUES._2014),
_2015: Filter::Class(CLASS_VALUES._2015),
_2016: Filter::Class(CLASS_VALUES._2016),
_2017: Filter::Class(CLASS_VALUES._2017),
_2018: Filter::Class(CLASS_VALUES._2018),
_2019: Filter::Class(CLASS_VALUES._2019),
_2020: Filter::Class(CLASS_VALUES._2020),
_2021: Filter::Class(CLASS_VALUES._2021),
_2022: Filter::Class(CLASS_VALUES._2022),
_2023: Filter::Class(CLASS_VALUES._2023),
_2024: Filter::Class(CLASS_VALUES._2024),
_2025: Filter::Class(CLASS_VALUES._2025),
_2026: Filter::Class(CLASS_VALUES._2026),
};
/// Year names
pub const YEAR_NAMES: ByYear<CohortName> = ByYear {
_2009: CohortName::new("year_2009", "2009", "Year 2009"),
_2010: CohortName::new("year_2010", "2010", "Year 2010"),
_2011: CohortName::new("year_2011", "2011", "Year 2011"),
_2012: CohortName::new("year_2012", "2012", "Year 2012"),
_2013: CohortName::new("year_2013", "2013", "Year 2013"),
_2014: CohortName::new("year_2014", "2014", "Year 2014"),
_2015: CohortName::new("year_2015", "2015", "Year 2015"),
_2016: CohortName::new("year_2016", "2016", "Year 2016"),
_2017: CohortName::new("year_2017", "2017", "Year 2017"),
_2018: CohortName::new("year_2018", "2018", "Year 2018"),
_2019: CohortName::new("year_2019", "2019", "Year 2019"),
_2020: CohortName::new("year_2020", "2020", "Year 2020"),
_2021: CohortName::new("year_2021", "2021", "Year 2021"),
_2022: CohortName::new("year_2022", "2022", "Year 2022"),
_2023: CohortName::new("year_2023", "2023", "Year 2023"),
_2024: CohortName::new("year_2024", "2024", "Year 2024"),
_2025: CohortName::new("year_2025", "2025", "Year 2025"),
_2026: CohortName::new("year_2026", "2026", "Year 2026"),
/// Class names
pub const CLASS_NAMES: ByClass<CohortName> = ByClass {
_2009: CohortName::new("class_2009", "2009", "Class 2009"),
_2010: CohortName::new("class_2010", "2010", "Class 2010"),
_2011: CohortName::new("class_2011", "2011", "Class 2011"),
_2012: CohortName::new("class_2012", "2012", "Class 2012"),
_2013: CohortName::new("class_2013", "2013", "Class 2013"),
_2014: CohortName::new("class_2014", "2014", "Class 2014"),
_2015: CohortName::new("class_2015", "2015", "Class 2015"),
_2016: CohortName::new("class_2016", "2016", "Class 2016"),
_2017: CohortName::new("class_2017", "2017", "Class 2017"),
_2018: CohortName::new("class_2018", "2018", "Class 2018"),
_2019: CohortName::new("class_2019", "2019", "Class 2019"),
_2020: CohortName::new("class_2020", "2020", "Class 2020"),
_2021: CohortName::new("class_2021", "2021", "Class 2021"),
_2022: CohortName::new("class_2022", "2022", "Class 2022"),
_2023: CohortName::new("class_2023", "2023", "Class 2023"),
_2024: CohortName::new("class_2024", "2024", "Class 2024"),
_2025: CohortName::new("class_2025", "2025", "Class 2025"),
_2026: CohortName::new("class_2026", "2026", "Class 2026"),
};
#[derive(Default, Clone, Traversable, Serialize)]
pub struct ByYear<T> {
pub struct ByClass<T> {
pub _2009: T,
pub _2010: T,
pub _2011: T,
@@ -93,19 +93,19 @@ pub struct ByYear<T> {
pub _2026: T,
}
impl ByYear<CohortName> {
impl ByClass<CohortName> {
pub const fn names() -> &'static Self {
&YEAR_NAMES
&CLASS_NAMES
}
}
impl<T> ByYear<T> {
impl<T> ByClass<T> {
pub fn new<F>(mut create: F) -> Self
where
F: FnMut(Filter, &'static str) -> T,
{
let f = YEAR_FILTERS;
let n = YEAR_NAMES;
let f = CLASS_FILTERS;
let n = CLASS_NAMES;
Self {
_2009: create(f._2009, n._2009.id),
_2010: create(f._2010, n._2010.id),
@@ -132,8 +132,8 @@ impl<T> ByYear<T> {
where
F: FnMut(Filter, &'static str) -> Result<T, E>,
{
let f = YEAR_FILTERS;
let n = YEAR_NAMES;
let f = CLASS_FILTERS;
let n = CLASS_NAMES;
Ok(Self {
_2009: create(f._2009, n._2009.id)?,
_2010: create(f._2010, n._2010.id)?,
+2 -2
View File
@@ -24,14 +24,14 @@ impl CohortContext {
/// Build full name for a filter, adding prefix only for Time/Amount filters.
///
/// Prefix rules:
/// - No prefix: `All`, `Term`, `Epoch`, `Year`, `Type`
/// - No prefix: `All`, `Term`, `Epoch`, `Class`, `Type`
/// - Context prefix: `Time`, `Amount`
pub fn full_name(&self, filter: &Filter, name: &str) -> String {
match filter {
Filter::All
| Filter::Term(_)
| Filter::Epoch(_)
| Filter::Year(_)
| Filter::Class(_)
| Filter::Type(_) => name.to_string(),
Filter::Time(_) | Filter::Amount(_) => self.prefixed(name),
}
+1 -1
View File
@@ -9,7 +9,7 @@ pub enum Filter {
Time(TimeFilter),
Amount(AmountFilter),
Epoch(HalvingEpoch),
Year(Year),
Class(Year),
Type(OutputType),
}
+2 -2
View File
@@ -15,7 +15,7 @@ mod by_spendable_type;
mod by_term;
mod by_type;
mod by_unspendable_type;
mod by_year;
mod by_class;
mod cohort_context;
mod cohort_name;
mod filter;
@@ -41,7 +41,7 @@ pub use by_spendable_type::*;
pub use by_term::*;
pub use by_type::*;
pub use by_unspendable_type::*;
pub use by_year::*;
pub use by_class::*;
pub use cohort_context::*;
pub use cohort_name::*;
pub use filter::*;
+9 -9
View File
@@ -3,7 +3,7 @@ use rayon::prelude::*;
use crate::{
ByAgeRange, ByAmountRange, ByEpoch, ByGreatEqualAmount, ByLowerThanAmount, ByMaxAge, ByMinAge,
BySpendableType, ByTerm, ByYear, Filter,
ByClass, BySpendableType, ByTerm, Filter,
};
#[derive(Default, Clone, Traversable)]
@@ -11,7 +11,7 @@ pub struct UTXOGroups<T> {
pub all: T,
pub age_range: ByAgeRange<T>,
pub epoch: ByEpoch<T>,
pub year: ByYear<T>,
pub class: ByClass<T>,
pub min_age: ByMinAge<T>,
pub ge_amount: ByGreatEqualAmount<T>,
pub amount_range: ByAmountRange<T>,
@@ -30,7 +30,7 @@ impl<T> UTXOGroups<T> {
all: create(Filter::All, ""),
age_range: ByAgeRange::new(&mut create),
epoch: ByEpoch::new(&mut create),
year: ByYear::new(&mut create),
class: ByClass::new(&mut create),
min_age: ByMinAge::new(&mut create),
ge_amount: ByGreatEqualAmount::new(&mut create),
amount_range: ByAmountRange::new(&mut create),
@@ -50,7 +50,7 @@ impl<T> UTXOGroups<T> {
.chain(self.ge_amount.iter())
.chain(self.age_range.iter())
.chain(self.epoch.iter())
.chain(self.year.iter())
.chain(self.class.iter())
.chain(self.amount_range.iter())
.chain(self.lt_amount.iter())
.chain(self.type_.iter())
@@ -65,7 +65,7 @@ impl<T> UTXOGroups<T> {
.chain(self.ge_amount.iter_mut())
.chain(self.age_range.iter_mut())
.chain(self.epoch.iter_mut())
.chain(self.year.iter_mut())
.chain(self.class.iter_mut())
.chain(self.amount_range.iter_mut())
.chain(self.lt_amount.iter_mut())
.chain(self.type_.iter_mut())
@@ -83,7 +83,7 @@ impl<T> UTXOGroups<T> {
.chain(self.ge_amount.par_iter_mut())
.chain(self.age_range.par_iter_mut())
.chain(self.epoch.par_iter_mut())
.chain(self.year.par_iter_mut())
.chain(self.class.par_iter_mut())
.chain(self.amount_range.par_iter_mut())
.chain(self.lt_amount.par_iter_mut())
.chain(self.type_.par_iter_mut())
@@ -93,7 +93,7 @@ impl<T> UTXOGroups<T> {
self.age_range
.iter()
.chain(self.epoch.iter())
.chain(self.year.iter())
.chain(self.class.iter())
.chain(self.amount_range.iter())
.chain(self.type_.iter())
}
@@ -102,7 +102,7 @@ impl<T> UTXOGroups<T> {
self.age_range
.iter_mut()
.chain(self.epoch.iter_mut())
.chain(self.year.iter_mut())
.chain(self.class.iter_mut())
.chain(self.amount_range.iter_mut())
.chain(self.type_.iter_mut())
}
@@ -114,7 +114,7 @@ impl<T> UTXOGroups<T> {
self.age_range
.par_iter_mut()
.chain(self.epoch.par_iter_mut())
.chain(self.year.par_iter_mut())
.chain(self.class.par_iter_mut())
.chain(self.amount_range.par_iter_mut())
.chain(self.type_.par_iter_mut())
}
+1
View File
@@ -1,3 +1,4 @@
*.md
!README.md
/*.py
/*.json
@@ -111,13 +111,8 @@ impl AddressCohorts {
self.par_iter_mut()
.try_for_each(|v| v.compute_rest_part1(blocks, prices, starting_indexes, exit))?;
// 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)
// 3. Compute net_sentiment.height for aggregate cohorts (weighted average).
// Separate cohorts already computed net_sentiment in step 2 (inside compute_rest_part1).
self.for_each_aggregate(|vecs, sources| {
let metrics: Vec<_> = sources.iter().map(|v| &v.metrics).collect();
vecs.metrics
@@ -189,12 +189,14 @@ impl DynCohortVecs for AddressCohortVecs {
&mut self,
height: Height,
height_price: Cents,
is_day_boundary: bool,
) -> Result<()> {
if let Some(state) = self.state.as_mut() {
self.metrics.compute_then_truncate_push_unrealized_states(
height,
height_price,
&mut state.inner,
is_day_boundary,
)?;
}
Ok(())
@@ -209,18 +211,15 @@ impl DynCohortVecs for AddressCohortVecs {
) -> Result<()> {
self.metrics
.compute_rest_part1(blocks, prices, starting_indexes, exit)?;
// Separate cohorts (with state) compute net_sentiment = greed - pain directly.
// Aggregate cohorts get it via weighted average in groups.rs.
if self.state.is_some() {
self.metrics
.compute_net_sentiment_height(starting_indexes, exit)?;
}
Ok(())
}
fn compute_net_sentiment_height(
&mut self,
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
self.metrics
.compute_net_sentiment_height(starting_indexes, exit)
}
fn write_state(&mut self, height: Height, cleanup: bool) -> Result<()> {
if let Some(state) = self.state.as_mut() {
state.inner.write(height, cleanup)?;
@@ -28,6 +28,7 @@ pub trait DynCohortVecs: Send + Sync {
&mut self,
height: Height,
height_price: Cents,
is_day_boundary: bool,
) -> Result<()>;
/// First phase of post-processing computations.
@@ -39,13 +40,6 @@ pub trait DynCohortVecs: Send + Sync {
exit: &Exit,
) -> Result<()>;
/// Compute net_sentiment.height for separate cohorts (greed - pain).
fn compute_net_sentiment_height(
&mut self,
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()>;
/// Write state checkpoint to disk.
fn write_state(&mut self, height: Height, cleanup: bool) -> Result<()>;
@@ -2,7 +2,7 @@ use std::path::Path;
use brk_cohort::{
ByAgeRange, ByAmountRange, ByEpoch, ByGreatEqualAmount, ByLowerThanAmount, ByMaxAge, ByMinAge,
BySpendableType, ByYear, CohortContext, Filter, Term,
ByClass, BySpendableType, CohortContext, Filter, Term,
};
use brk_error::Result;
use brk_traversable::Traversable;
@@ -17,7 +17,7 @@ use crate::distribution::metrics::{
ExtendedAdjustedCohortMetrics, ExtendedCohortMetrics, ImportConfig, SupplyMetrics,
};
use super::vecs::UTXOCohortVecs;
use super::{percentiles::PercentileCache, vecs::UTXOCohortVecs};
use crate::distribution::state::UTXOCohortState;
@@ -27,7 +27,7 @@ const VERSION: Version = Version::new(0);
///
/// Each group uses a concrete metrics type matching its required features:
/// - age_range: extended realized + extended cost basis
/// - epoch/year/amount/type: basic metrics with relative
/// - epoch/class/amount/type: basic metrics with relative
/// - all: extended + adjusted (no rel_to_all)
/// - sth: extended + adjusted
/// - lth: extended
@@ -45,23 +45,22 @@ pub struct UTXOCohorts<M: StorageMode = Rw> {
pub amount_range: ByAmountRange<UTXOCohortVecs<BasicCohortMetrics<M>>>,
pub lt_amount: ByLowerThanAmount<UTXOCohortVecs<BasicCohortMetrics<M>>>,
pub epoch: ByEpoch<UTXOCohortVecs<BasicCohortMetrics<M>>>,
pub year: ByYear<UTXOCohortVecs<BasicCohortMetrics<M>>>,
pub class: ByClass<UTXOCohortVecs<BasicCohortMetrics<M>>>,
pub type_: BySpendableType<UTXOCohortVecs<BasicCohortMetrics<M>>>,
#[traversable(skip)]
pub(super) percentile_cache: PercentileCache,
/// Cached partition_point positions for tick_tock boundary searches.
/// Avoids O(log n) binary search per boundary per block; scans forward
/// from last known position (typically O(1) per boundary).
#[traversable(skip)]
pub(super) tick_tock_cached_positions: [usize; 20],
}
macro_rules! collect_separate {
($self:expr, $method:ident, $trait_ref:ty) => {{
let mut v: Vec<$trait_ref> = Vec::with_capacity(UTXOCohorts::SEPARATE_COHORT_CAPACITY);
v.extend($self.age_range.$method().map(|x| x as $trait_ref));
v.extend($self.epoch.$method().map(|x| x as $trait_ref));
v.extend($self.year.$method().map(|x| x as $trait_ref));
v.extend($self.amount_range.$method().map(|x| x as $trait_ref));
v.extend($self.type_.$method().map(|x| x as $trait_ref));
v
}};
}
impl UTXOCohorts<Rw> {
/// ~71 separate cohorts (21 age + 5 epoch + 18 class + 15 amount + 12 type)
const SEPARATE_COHORT_CAPACITY: usize = 80;
/// Import all UTXO cohorts from database.
pub(crate) fn forced_import(
db: &Database,
@@ -123,7 +122,7 @@ impl UTXOCohorts<Rw> {
let amount_range = ByAmountRange::try_new(&basic_separate)?;
let epoch = ByEpoch::try_new(&basic_separate)?;
let year = ByYear::try_new(&basic_separate)?;
let class = ByClass::try_new(&basic_separate)?;
let type_ = BySpendableType::try_new(&basic_separate)?;
// Phase 3: Import "all" cohort with pre-imported supply.
@@ -223,7 +222,7 @@ impl UTXOCohorts<Rw> {
sth,
lth,
epoch,
year,
class,
type_,
max_age,
min_age,
@@ -231,26 +230,39 @@ impl UTXOCohorts<Rw> {
amount_range,
lt_amount,
ge_amount,
percentile_cache: PercentileCache::default(),
tick_tock_cached_positions: [0; 20],
})
}
/// ~71 separate cohorts (21 age + 5 epoch + 18 year + 15 amount + 12 type)
const SEPARATE_COHORT_CAPACITY: usize = 80;
pub(crate) fn par_iter_separate_mut(
&mut self,
) -> impl ParallelIterator<Item = &mut dyn DynCohortVecs> {
collect_separate!(self, iter_mut, &mut dyn DynCohortVecs).into_par_iter()
let Self {
age_range, epoch, class, amount_range, type_, ..
} = self;
age_range
.par_iter_mut()
.map(|x| x as &mut dyn DynCohortVecs)
.chain(epoch.par_iter_mut().map(|x| x as &mut dyn DynCohortVecs))
.chain(class.par_iter_mut().map(|x| x as &mut dyn DynCohortVecs))
.chain(
amount_range
.par_iter_mut()
.map(|x| x as &mut dyn DynCohortVecs),
)
.chain(type_.par_iter_mut().map(|x| x as &mut dyn DynCohortVecs))
}
/// Immutable iterator over all separate (stateful) cohorts.
pub(crate) fn iter_separate(&self) -> impl Iterator<Item = &dyn DynCohortVecs> {
collect_separate!(self, iter, &dyn DynCohortVecs).into_iter()
}
/// Mutable iterator over all separate cohorts (non-parallel).
pub(crate) fn iter_separate_mut(&mut self) -> impl Iterator<Item = &mut dyn DynCohortVecs> {
collect_separate!(self, iter_mut, &mut dyn DynCohortVecs).into_iter()
self.age_range
.iter()
.map(|x| x as &dyn DynCohortVecs)
.chain(self.epoch.iter().map(|x| x as &dyn DynCohortVecs))
.chain(self.class.iter().map(|x| x as &dyn DynCohortVecs))
.chain(self.amount_range.iter().map(|x| x as &dyn DynCohortVecs))
.chain(self.type_.iter().map(|x| x as &dyn DynCohortVecs))
}
pub(crate) fn compute_overlapping_vecs(
@@ -258,90 +270,53 @@ impl UTXOCohorts<Rw> {
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
let age_range = &self.age_range;
let amount_range = &self.amount_range;
let Self {
all, sth, lth, age_range, max_age, min_age,
ge_amount, amount_range, lt_amount,
..
} = self;
// all: aggregate of all age_range
// Note: realized.extended rolling sums are computed from base in compute_rest_part2.
// Note: cost_basis.extended percentiles are computed in truncate_push_aggregate_percentiles.
{
let sources_dyn: Vec<&dyn CohortMetricsBase> = age_range
.iter()
.map(|v| &v.metrics as &dyn CohortMetricsBase)
.collect();
self.all
.metrics
.compute_base_from_others(starting_indexes, &sources_dyn, exit)?;
}
let ar = &*age_range;
let amr = &*amount_range;
let si = starting_indexes;
// sth: aggregate of matching age_range
{
let sth_filter = self.sth.metrics.filter().clone();
let sources_dyn: Vec<&dyn CohortMetricsBase> = age_range
.iter()
.filter(|v| sth_filter.includes(v.metrics.filter()))
.map(|v| &v.metrics as &dyn CohortMetricsBase)
.collect();
self.sth
.metrics
.compute_base_from_others(starting_indexes, &sources_dyn, exit)?;
}
let tasks: Vec<Box<dyn FnOnce() -> Result<()> + Send + '_>> = vec![
Box::new(|| {
let sources = filter_sources_from(ar.iter(), None);
all.metrics.compute_base_from_others(si, &sources, exit)
}),
Box::new(|| {
let sources = filter_sources_from(ar.iter(), Some(sth.metrics.filter()));
sth.metrics.compute_base_from_others(si, &sources, exit)
}),
Box::new(|| {
let sources = filter_sources_from(ar.iter(), Some(lth.metrics.filter()));
lth.metrics.compute_base_from_others(si, &sources, exit)
}),
Box::new(|| {
min_age.par_iter_mut().try_for_each(|vecs| {
let sources = filter_sources_from(ar.iter(), Some(vecs.metrics.filter()));
vecs.metrics.compute_base_from_others(si, &sources, exit)
})
}),
Box::new(|| {
max_age.par_iter_mut().try_for_each(|vecs| {
let sources = filter_sources_from(ar.iter(), Some(vecs.metrics.filter()));
vecs.metrics.compute_base_from_others(si, &sources, exit)
})
}),
Box::new(|| {
ge_amount.par_iter_mut().chain(lt_amount.par_iter_mut()).try_for_each(|vecs| {
let sources = filter_sources_from(amr.iter(), Some(vecs.metrics.filter()));
vecs.metrics.compute_base_from_others(si, &sources, exit)
})
}),
];
// lth: aggregate of matching age_range
{
let lth_filter = self.lth.metrics.filter().clone();
let sources_dyn: Vec<&dyn CohortMetricsBase> = age_range
.iter()
.filter(|v| lth_filter.includes(v.metrics.filter()))
.map(|v| &v.metrics as &dyn CohortMetricsBase)
.collect();
self.lth
.metrics
.compute_base_from_others(starting_indexes, &sources_dyn, exit)?;
}
// min_age: base from matching age_range
self.min_age
.par_iter_mut()
.try_for_each(|vecs| -> Result<()> {
let filter = vecs.metrics.filter().clone();
let sources_dyn: Vec<&dyn CohortMetricsBase> = age_range
.iter()
.filter(|v| filter.includes(v.metrics.filter()))
.map(|v| &v.metrics as &dyn CohortMetricsBase)
.collect();
vecs.metrics
.compute_base_from_others(starting_indexes, &sources_dyn, exit)
})?;
// max_age: base + peak_regret from matching age_range
self.max_age
.par_iter_mut()
.try_for_each(|vecs| -> Result<()> {
let filter = vecs.metrics.filter().clone();
let sources_dyn: Vec<&dyn CohortMetricsBase> = age_range
.iter()
.filter(|v| filter.includes(v.metrics.filter()))
.map(|v| &v.metrics as &dyn CohortMetricsBase)
.collect();
vecs.metrics
.compute_base_from_others(starting_indexes, &sources_dyn, exit)
})?;
// ge_amount, lt_amount: base only from matching amount_range
self.ge_amount
.par_iter_mut()
.chain(self.lt_amount.par_iter_mut())
.try_for_each(|vecs| {
let filter = vecs.metrics.filter().clone();
let sources_dyn: Vec<&dyn CohortMetricsBase> = amount_range
.iter()
.filter(|v| filter.includes(v.metrics.filter()))
.map(|v| &v.metrics as &dyn CohortMetricsBase)
.collect();
vecs.metrics
.compute_base_from_others(starting_indexes, &sources_dyn, exit)
})?;
tasks
.into_par_iter()
.map(|f| f())
.collect::<Result<Vec<_>>>()?;
Ok(())
}
@@ -373,7 +348,7 @@ impl UTXOCohorts<Rw> {
.map(|x| x as &mut dyn DynCohortVecs),
);
all.extend(self.epoch.iter_mut().map(|x| x as &mut dyn DynCohortVecs));
all.extend(self.year.iter_mut().map(|x| x as &mut dyn DynCohortVecs));
all.extend(self.class.iter_mut().map(|x| x as &mut dyn DynCohortVecs));
all.extend(
self.amount_range
.iter_mut()
@@ -389,100 +364,56 @@ impl UTXOCohorts<Rw> {
.try_for_each(|v| v.compute_rest_part1(blocks, prices, starting_indexes, exit))?;
}
// 2. Compute net_sentiment.height for separate cohorts (greed - pain)
self.par_iter_separate_mut()
.try_for_each(|v| v.compute_net_sentiment_height(starting_indexes, exit))?;
// 3. Compute net_sentiment.height for aggregate cohorts (weighted average)
// 2. Compute net_sentiment.height for aggregate cohorts (weighted average).
// Separate cohorts already computed net_sentiment in step 1 (inside compute_rest_part1).
{
let age_range = &self.age_range;
let amount_range = &self.amount_range;
let Self {
all, sth, lth, age_range, max_age, min_age,
ge_amount, amount_range, lt_amount,
..
} = self;
// all
{
let sources: Vec<_> = age_range
.iter()
.map(|v| &v.metrics as &dyn CohortMetricsBase)
.collect();
self.all.metrics.compute_net_sentiment_from_others_dyn(
starting_indexes,
&sources,
exit,
)?;
}
let ar = &*age_range;
let amr = &*amount_range;
let si = starting_indexes;
// sth
{
let filter = self.sth.metrics.filter().clone();
let sources: Vec<_> = age_range
.iter()
.filter(|v| filter.includes(v.metrics.filter()))
.map(|v| &v.metrics as &dyn CohortMetricsBase)
.collect();
self.sth.metrics.compute_net_sentiment_from_others_dyn(
starting_indexes,
&sources,
exit,
)?;
}
let tasks: Vec<Box<dyn FnOnce() -> Result<()> + Send + '_>> = vec![
Box::new(|| {
let sources = filter_sources_from(ar.iter(), None);
all.metrics.compute_net_sentiment_from_others_dyn(si, &sources, exit)
}),
Box::new(|| {
let sources = filter_sources_from(ar.iter(), Some(sth.metrics.filter()));
sth.metrics.compute_net_sentiment_from_others_dyn(si, &sources, exit)
}),
Box::new(|| {
let sources = filter_sources_from(ar.iter(), Some(lth.metrics.filter()));
lth.metrics.compute_net_sentiment_from_others_dyn(si, &sources, exit)
}),
Box::new(|| {
min_age.par_iter_mut().try_for_each(|vecs| {
let sources = filter_sources_from(ar.iter(), Some(vecs.metrics.filter()));
vecs.metrics.compute_net_sentiment_from_others_dyn(si, &sources, exit)
})
}),
Box::new(|| {
max_age.par_iter_mut().try_for_each(|vecs| {
let sources = filter_sources_from(ar.iter(), Some(vecs.metrics.filter()));
vecs.metrics.compute_net_sentiment_from_others_dyn(si, &sources, exit)
})
}),
Box::new(|| {
ge_amount.par_iter_mut().chain(lt_amount.par_iter_mut()).try_for_each(|vecs| {
let sources = filter_sources_from(amr.iter(), Some(vecs.metrics.filter()));
vecs.metrics.compute_net_sentiment_from_others_dyn(si, &sources, exit)
})
}),
];
// lth
{
let filter = self.lth.metrics.filter().clone();
let sources: Vec<_> = age_range
.iter()
.filter(|v| filter.includes(v.metrics.filter()))
.map(|v| &v.metrics as &dyn CohortMetricsBase)
.collect();
self.lth.metrics.compute_net_sentiment_from_others_dyn(
starting_indexes,
&sources,
exit,
)?;
}
// min_age, max_age from age_range
for vecs in self.min_age.iter_mut() {
let filter = vecs.metrics.filter().clone();
let sources: Vec<_> = age_range
.iter()
.filter(|v| filter.includes(v.metrics.filter()))
.map(|v| &v.metrics as &dyn CohortMetricsBase)
.collect();
vecs.metrics.compute_net_sentiment_from_others_dyn(
starting_indexes,
&sources,
exit,
)?;
}
for vecs in self.max_age.iter_mut() {
let filter = vecs.metrics.filter().clone();
let sources: Vec<_> = age_range
.iter()
.filter(|v| filter.includes(v.metrics.filter()))
.map(|v| &v.metrics as &dyn CohortMetricsBase)
.collect();
vecs.metrics.compute_net_sentiment_from_others_dyn(
starting_indexes,
&sources,
exit,
)?;
}
// ge_amount, lt_amount from amount_range
for vecs in self.ge_amount.iter_mut().chain(self.lt_amount.iter_mut()) {
let filter = vecs.metrics.filter().clone();
let sources: Vec<_> = amount_range
.iter()
.filter(|v| filter.includes(v.metrics.filter()))
.map(|v| &v.metrics as &dyn CohortMetricsBase)
.collect();
vecs.metrics.compute_net_sentiment_from_others_dyn(
starting_indexes,
&sources,
exit,
)?;
}
tasks
.into_par_iter()
.map(|f| f())
.collect::<Result<Vec<_>>>()?;
}
Ok(())
@@ -532,116 +463,37 @@ impl UTXOCohorts<Rw> {
// Clone all_supply_sats for non-all cohorts.
let all_supply_sats = self.all.metrics.supply.total.sats.height.read_only_clone();
self.sth.metrics.compute_rest_part2(
blocks,
prices,
starting_indexes,
height_to_market_cap,
&up_to_1h_value_created,
&up_to_1h_value_destroyed,
&all_supply_sats,
exit,
)?;
self.lth.metrics.compute_rest_part2(
blocks,
prices,
starting_indexes,
height_to_market_cap,
&all_supply_sats,
exit,
)?;
self.age_range.par_iter_mut().try_for_each(|v| {
v.metrics.compute_rest_part2(
blocks,
prices,
starting_indexes,
height_to_market_cap,
&all_supply_sats,
exit,
)
})?;
self.max_age.par_iter_mut().try_for_each(|v| {
v.metrics.compute_rest_part2(
blocks,
prices,
starting_indexes,
height_to_market_cap,
&up_to_1h_value_created,
&up_to_1h_value_destroyed,
&all_supply_sats,
exit,
)
})?;
self.min_age.par_iter_mut().try_for_each(|v| {
v.metrics.compute_rest_part2(
blocks,
prices,
starting_indexes,
height_to_market_cap,
&all_supply_sats,
exit,
)
})?;
self.ge_amount.par_iter_mut().try_for_each(|v| {
v.metrics.compute_rest_part2(
blocks,
prices,
starting_indexes,
height_to_market_cap,
&all_supply_sats,
exit,
)
})?;
self.epoch.par_iter_mut().try_for_each(|v| {
v.metrics.compute_rest_part2(
blocks,
prices,
starting_indexes,
height_to_market_cap,
&all_supply_sats,
exit,
)
})?;
self.year.par_iter_mut().try_for_each(|v| {
v.metrics.compute_rest_part2(
blocks,
prices,
starting_indexes,
height_to_market_cap,
&all_supply_sats,
exit,
)
})?;
self.amount_range.par_iter_mut().try_for_each(|v| {
v.metrics.compute_rest_part2(
blocks,
prices,
starting_indexes,
height_to_market_cap,
&all_supply_sats,
exit,
)
})?;
self.lt_amount.par_iter_mut().try_for_each(|v| {
v.metrics.compute_rest_part2(
blocks,
prices,
starting_indexes,
height_to_market_cap,
&all_supply_sats,
exit,
)
})?;
self.type_.par_iter_mut().try_for_each(|v| {
v.metrics.compute_rest_part2(
blocks,
prices,
starting_indexes,
height_to_market_cap,
&all_supply_sats,
exit,
)
})?;
// Destructure to allow parallel mutable access to independent fields.
let Self {
sth, lth, age_range, max_age, min_age,
ge_amount, amount_range, lt_amount, epoch, class, type_, ..
} = self;
// All remaining groups run in parallel. Each closure owns an exclusive &mut
// to its field and shares read-only references to common data.
let vc = &up_to_1h_value_created;
let vd = &up_to_1h_value_destroyed;
let ss = &all_supply_sats;
let tasks: Vec<Box<dyn FnOnce() -> Result<()> + Send + '_>> = vec![
Box::new(|| sth.metrics.compute_rest_part2(blocks, prices, starting_indexes, height_to_market_cap, vc, vd, ss, exit)),
Box::new(|| lth.metrics.compute_rest_part2(blocks, prices, starting_indexes, height_to_market_cap, ss, exit)),
Box::new(|| age_range.par_iter_mut().try_for_each(|v| v.metrics.compute_rest_part2(blocks, prices, starting_indexes, height_to_market_cap, ss, exit))),
Box::new(|| max_age.par_iter_mut().try_for_each(|v| v.metrics.compute_rest_part2(blocks, prices, starting_indexes, height_to_market_cap, vc, vd, ss, exit))),
Box::new(|| min_age.par_iter_mut().try_for_each(|v| v.metrics.compute_rest_part2(blocks, prices, starting_indexes, height_to_market_cap, ss, exit))),
Box::new(|| ge_amount.par_iter_mut().try_for_each(|v| v.metrics.compute_rest_part2(blocks, prices, starting_indexes, height_to_market_cap, ss, exit))),
Box::new(|| epoch.par_iter_mut().try_for_each(|v| v.metrics.compute_rest_part2(blocks, prices, starting_indexes, height_to_market_cap, ss, exit))),
Box::new(|| class.par_iter_mut().try_for_each(|v| v.metrics.compute_rest_part2(blocks, prices, starting_indexes, height_to_market_cap, ss, exit))),
Box::new(|| amount_range.par_iter_mut().try_for_each(|v| v.metrics.compute_rest_part2(blocks, prices, starting_indexes, height_to_market_cap, ss, exit))),
Box::new(|| lt_amount.par_iter_mut().try_for_each(|v| v.metrics.compute_rest_part2(blocks, prices, starting_indexes, height_to_market_cap, ss, exit))),
Box::new(|| type_.par_iter_mut().try_for_each(|v| v.metrics.compute_rest_part2(blocks, prices, starting_indexes, height_to_market_cap, ss, exit))),
];
tasks
.into_par_iter()
.map(|f| f())
.collect::<Result<Vec<_>>>()?;
Ok(())
}
@@ -668,7 +520,7 @@ impl UTXOCohorts<Rw> {
for v in self.epoch.iter_mut() {
vecs.extend(v.metrics.collect_all_vecs_mut());
}
for v in self.year.iter_mut() {
for v in self.class.iter_mut() {
vecs.extend(v.metrics.collect_all_vecs_mut());
}
for v in self.amount_range.iter_mut() {
@@ -744,3 +596,20 @@ impl UTXOCohorts<Rw> {
Ok(())
}
}
/// Filter source cohorts by an optional filter, returning dyn CohortMetricsBase refs.
/// If filter is None, returns all sources (used for "all" aggregate).
fn filter_sources_from<'a, M: CohortMetricsBase + 'a>(
sources: impl Iterator<Item = &'a UTXOCohortVecs<M>>,
filter: Option<&Filter>,
) -> Vec<&'a dyn CohortMetricsBase> {
match filter {
Some(f) => sources
.filter(|v| f.includes(v.metrics.filter()))
.map(|v| &v.metrics as &dyn CohortMetricsBase)
.collect(),
None => sources
.map(|v| &v.metrics as &dyn CohortMetricsBase)
.collect(),
}
}
@@ -2,45 +2,69 @@ use std::{cmp::Reverse, collections::BinaryHeap, fs, path::Path};
use brk_cohort::{Filtered, TERM_NAMES};
use brk_error::Result;
use brk_types::{
BasisPoints16, Cents, CentsCompact, CostBasisDistribution, Date, Height, Sats,
};
use vecdb::WritableVec;
use brk_types::{Cents, CentsCompact, CostBasisDistribution, Date, Height, Sats};
use crate::internal::{PERCENTILES, PERCENTILES_LEN, compute_spot_percentile_rank};
use crate::internal::{PERCENTILES, PERCENTILES_LEN};
use crate::distribution::metrics::{CohortMetricsBase, CostBasisExtended};
use super::groups::UTXOCohorts;
/// Significant digits for cost basis prices (after rounding to dollars).
const COST_BASIS_PRICE_DIGITS: i32 = 5;
#[derive(Clone, Default)]
pub(super) struct CachedPercentiles {
sat_result: [Cents; PERCENTILES_LEN],
usd_result: [Cents; PERCENTILES_LEN],
}
impl CachedPercentiles {
fn push(&self, height: Height, ext: &mut CostBasisExtended) -> Result<()> {
ext.push_arrays(height, &self.sat_result, &self.usd_result)
}
}
/// Cached percentile results for all/sth/lth.
/// Avoids re-merging 21 BTreeMaps on every block.
#[derive(Clone, Default)]
pub(super) struct PercentileCache {
all: CachedPercentiles,
sth: CachedPercentiles,
lth: CachedPercentiles,
initialized: bool,
}
impl UTXOCohorts {
/// Compute and push percentiles for aggregate cohorts (all, sth, lth).
///
/// Single K-way merge pass over all age_range cohorts computes percentiles
/// for all 3 targets simultaneously, since each cohort belongs to exactly
/// one of STH/LTH and always contributes to ALL.
///
/// Uses BinaryHeap with direct BTreeMap iterators — O(log K) merge
/// with zero intermediate Vec allocation.
/// Full K-way merge only runs at day boundaries or when the cache is empty.
/// For intermediate blocks, pushes cached percentile arrays.
pub(crate) fn truncate_push_aggregate_percentiles(
&mut self,
height: Height,
spot: Cents,
date_opt: Option<Date>,
states_path: &Path,
) -> Result<()> {
if date_opt.is_some() || !self.percentile_cache.initialized {
self.merge_and_push_percentiles(height, date_opt, states_path)
} else {
self.push_cached_percentiles(height)
}
}
/// Full K-way merge: compute percentiles from scratch, update cache, push.
fn merge_and_push_percentiles(
&mut self,
height: Height,
date_opt: Option<Date>,
states_path: &Path,
) -> Result<()> {
let collect_merged = date_opt.is_some();
// Phase 1: compute totals + merge.
// Scoped so age_range borrows release before push_target borrows self.all/sth/lth.
let targets = {
let sth_filter = self.sth.metrics.filter().clone();
let mut totals = AllSthLth::<(u64, u128)>::default();
// Collect BTreeMap refs from age_range, skip empty, compute totals.
let maps: Vec<_> = self
.age_range
.iter()
@@ -75,76 +99,121 @@ impl UTXOCohorts {
let all_has_data = totals.all.0 > 0;
let mut targets = totals.map(|(sats, usd)| PercTarget::new(sats, usd, cap));
// K-way merge via BinaryHeap + BTreeMap iterators (no Vec copies)
if all_has_data {
let mut iters: Vec<_> = maps
.iter()
.map(|(map, is_sth)| (map.iter().peekable(), *is_sth))
.collect();
let mut heap: BinaryHeap<Reverse<(CentsCompact, usize)>> =
BinaryHeap::with_capacity(iters.len());
for (i, (iter, _)) in iters.iter_mut().enumerate() {
if let Some(&(&price, _)) = iter.peek() {
heap.push(Reverse((price, i)));
}
}
let mut current_price: Option<CentsCompact> = None;
let mut early_exit = false;
while let Some(Reverse((price, ci))) = heap.pop() {
let (ref mut iter, is_sth) = iters[ci];
let (_, &sats) = iter.next().unwrap();
let amount = u64::from(sats);
let usd = Cents::from(price).as_u128() * amount as u128;
if let Some(prev) = current_price
&& prev != price
{
targets
.for_each_mut(|t| t.finalize_price(prev.into(), collect_merged));
if !collect_merged && targets.all_match(|t| t.done()) {
early_exit = true;
break;
}
}
current_price = Some(price);
targets.all.accumulate(amount, usd);
targets.term_mut(is_sth).accumulate(amount, usd);
if let Some(&(&next_price, _)) = iter.peek() {
heap.push(Reverse((next_price, ci)));
}
}
if !early_exit
&& let Some(price) = current_price
{
targets.for_each_mut(|t| t.finalize_price(price.into(), collect_merged));
}
merge_k_way(&maps, &mut targets, collect_merged);
}
targets
};
// Phase 2: push results (borrows self.all/sth/lth mutably)
push_target(
height, spot, date_opt, states_path, targets.all,
&mut self.all.metrics.cost_basis.extended, "all",
)?;
push_target(
height, spot, date_opt, states_path, targets.sth,
&mut self.sth.metrics.cost_basis.extended, TERM_NAMES.short.id,
)?;
push_target(
height, spot, date_opt, states_path, targets.lth,
&mut self.lth.metrics.cost_basis.extended, TERM_NAMES.long.id,
)?;
// Update cache + push
self.percentile_cache.all = targets.all.to_cached();
self.percentile_cache.sth = targets.sth.to_cached();
self.percentile_cache.lth = targets.lth.to_cached();
self.percentile_cache.initialized = true;
self.percentile_cache
.all
.push(height, &mut self.all.metrics.cost_basis.extended)?;
self.percentile_cache
.sth
.push(height, &mut self.sth.metrics.cost_basis.extended)?;
self.percentile_cache
.lth
.push(height, &mut self.lth.metrics.cost_basis.extended)?;
// Serialize full distribution at day boundaries
if let Some(date) = date_opt {
write_distribution(states_path, "all", date, targets.all.merged)?;
write_distribution(states_path, TERM_NAMES.short.id, date, targets.sth.merged)?;
write_distribution(states_path, TERM_NAMES.long.id, date, targets.lth.merged)?;
}
Ok(())
}
/// Fast path: push cached percentile arrays.
fn push_cached_percentiles(&mut self, height: Height) -> Result<()> {
self.percentile_cache
.all
.push(height, &mut self.all.metrics.cost_basis.extended)?;
self.percentile_cache
.sth
.push(height, &mut self.sth.metrics.cost_basis.extended)?;
self.percentile_cache
.lth
.push(height, &mut self.lth.metrics.cost_basis.extended)?;
Ok(())
}
}
fn write_distribution(
states_path: &Path,
name: &str,
date: Date,
merged: Vec<(CentsCompact, Sats)>,
) -> Result<()> {
let dir = states_path.join(format!("utxo_{name}_cost_basis/by_date"));
fs::create_dir_all(&dir)?;
fs::write(
dir.join(date.to_string()),
CostBasisDistribution::serialize_iter(merged.into_iter())?,
)?;
Ok(())
}
/// K-way merge via BinaryHeap over BTreeMap iterators.
fn merge_k_way(
maps: &[(&std::collections::BTreeMap<CentsCompact, Sats>, bool)],
targets: &mut AllSthLth<PercTarget>,
collect_merged: bool,
) {
let mut iters: Vec<_> = maps
.iter()
.map(|(map, is_sth)| (map.iter().peekable(), *is_sth))
.collect();
let mut heap: BinaryHeap<Reverse<(CentsCompact, usize)>> =
BinaryHeap::with_capacity(iters.len());
for (i, (iter, _)) in iters.iter_mut().enumerate() {
if let Some(&(&price, _)) = iter.peek() {
heap.push(Reverse((price, i)));
}
}
let mut current_price: Option<CentsCompact> = None;
let mut early_exit = false;
while let Some(Reverse((price, ci))) = heap.pop() {
let (ref mut iter, is_sth) = iters[ci];
let (_, &sats) = iter.next().unwrap();
let amount = u64::from(sats);
let usd = Cents::from(price).as_u128() * amount as u128;
if let Some(prev) = current_price
&& prev != price
{
targets.for_each_mut(|t| t.finalize_price(prev.into(), collect_merged));
if !collect_merged && targets.all_match(|t| t.done()) {
early_exit = true;
break;
}
}
current_price = Some(price);
targets.all.accumulate(amount, usd);
targets.term_mut(is_sth).accumulate(amount, usd);
if let Some(&(&next_price, _)) = iter.peek() {
heap.push(Reverse((next_price, ci)));
}
}
if !early_exit
&& let Some(price) = current_price
{
targets.for_each_mut(|t| t.finalize_price(price.into(), collect_merged));
}
}
struct AllSthLth<T> {
@@ -230,6 +299,13 @@ impl PercTarget {
}
}
fn to_cached(&self) -> CachedPercentiles {
CachedPercentiles {
sat_result: self.sat_result,
usd_result: self.usd_result,
}
}
#[inline]
fn accumulate(&mut self, amount: u64, usd: u128) {
self.price_sats += amount;
@@ -275,48 +351,3 @@ impl PercTarget {
&& (self.total_usd == 0 || self.usd_idx >= PERCENTILES_LEN)
}
}
#[allow(clippy::too_many_arguments)]
fn push_target(
height: Height,
spot: Cents,
date_opt: Option<Date>,
states_path: &Path,
target: PercTarget,
ext: &mut CostBasisExtended,
name: &str,
) -> Result<()> {
ext.percentiles.truncate_push(height, &target.sat_result)?;
ext.invested_capital
.truncate_push(height, &target.usd_result)?;
let sat_rank = if target.total_sats > 0 {
compute_spot_percentile_rank(&target.sat_result, spot)
} else {
BasisPoints16::ZERO
};
ext.spot_cost_basis_percentile
.bps
.height
.truncate_push(height, sat_rank)?;
let usd_rank = if target.total_usd > 0 {
compute_spot_percentile_rank(&target.usd_result, spot)
} else {
BasisPoints16::ZERO
};
ext.spot_invested_capital_percentile
.bps
.height
.truncate_push(height, usd_rank)?;
if let Some(date) = date_opt {
let dir = states_path.join(format!("utxo_{name}_cost_basis/by_date"));
fs::create_dir_all(&dir)?;
fs::write(
dir.join(date.to_string()),
CostBasisDistribution::serialize_iter(target.merged.into_iter())?,
)?;
}
Ok(())
}
@@ -11,7 +11,7 @@ impl UTXOCohorts<Rw> {
/// New UTXOs are added to:
/// - The "up_to_1h" age cohort (all new UTXOs start at 0 hours old)
/// - The appropriate epoch cohort based on block height
/// - The appropriate year cohort based on block timestamp
/// - The appropriate class cohort based on block timestamp
/// - The appropriate output type cohort (P2PKH, P2SH, etc.)
/// - The appropriate amount range cohort based on value
pub(crate) fn receive(
@@ -26,7 +26,7 @@ impl UTXOCohorts<Rw> {
// Pre-compute snapshot once for the 3 cohorts sharing the same supply_state
let snapshot = CostBasisSnapshot::from_utxo(price, &supply_state);
// New UTXOs go into up_to_1h, current epoch, and current year
// New UTXOs go into up_to_1h, current epoch, and current class
self.age_range
.up_to_1h
.state
@@ -39,7 +39,7 @@ impl UTXOCohorts<Rw> {
.as_mut()
.unwrap()
.receive_utxo_snapshot(&supply_state, &snapshot);
self.year
self.class
.mut_vec_from_timestamp(timestamp)
.state
.as_mut()
@@ -70,7 +70,7 @@ impl UTXOCohorts<Rw> {
.as_mut()
.unwrap()
.send_utxo_precomputed(&sent.spendable_supply, &pre);
self.year
self.class
.mut_vec_from_timestamp(block_state.timestamp)
.state
.as_mut()
@@ -86,7 +86,7 @@ impl UTXOCohorts<Rw> {
.as_mut()
.unwrap()
.supply -= &sent.spendable_supply;
self.year
self.class
.mut_vec_from_timestamp(block_state.timestamp)
.state
.as_mut()
@@ -1,6 +1,6 @@
use brk_cohort::AGE_BOUNDARIES;
use brk_types::{CostBasisSnapshot, ONE_HOUR_IN_SEC, Timestamp};
use vecdb::Rw;
use vecdb::{Rw, unlikely};
use crate::distribution::state::BlockState;
@@ -12,10 +12,9 @@ impl UTXOCohorts<Rw> {
/// UTXOs age with each block. When they cross hour boundaries,
/// they move between age-based cohorts (e.g., from "0-1h" to "1h-1d").
///
/// Complexity: O(k * log n) where:
/// - k = 20 boundaries to check
/// - n = total blocks in chain_state
/// - Linear scan for end_idx is faster than binary search since typically 0-2 blocks cross each boundary
/// Uses cached positions per boundary to avoid binary search.
/// Since timestamps are monotonic, positions only advance forward.
/// Complexity: O(k * c) where k = 20 boundaries, c = ~1 (forward scan steps).
pub(crate) fn tick_tock_next_block(
&mut self,
chain_state: &[BlockState],
@@ -38,6 +37,7 @@ impl UTXOCohorts<Rw> {
// Cohort 0 covers [0, 1) hours
// Cohort 20 covers [15*365*24, infinity) hours
let mut age_cohorts: Vec<_> = self.age_range.iter_mut().map(|v| &mut v.state).collect();
let cached = &mut self.tick_tock_cached_positions;
// For each boundary (in hours), find blocks that just crossed it
for (boundary_idx, &boundary_hours) in AGE_BOUNDARIES.iter().enumerate() {
@@ -54,8 +54,24 @@ impl UTXOCohorts<Rw> {
continue;
}
// Binary search to find start, then linear scan for end (typically 0-2 blocks)
let start_idx = chain_state.partition_point(|b| *b.timestamp <= lower_timestamp);
// Find start_idx: use cached position + forward scan (O(1) typical).
// On first call after restart, cached is 0 so fall back to binary search.
let start_idx = if unlikely(cached[boundary_idx] == 0 && chain_state.len() > 1) {
let idx = chain_state.partition_point(|b| *b.timestamp <= lower_timestamp);
cached[boundary_idx] = idx;
idx
} else {
let mut idx = cached[boundary_idx];
while idx < chain_state.len()
&& *chain_state[idx].timestamp <= lower_timestamp
{
idx += 1;
}
cached[boundary_idx] = idx;
idx
};
// Linear scan for end (typically 0-2 blocks past start)
let end_idx = chain_state[start_idx..]
.iter()
.position(|b| *b.timestamp > upper_timestamp)
@@ -110,12 +110,14 @@ impl<Metrics: CohortMetricsBase + Traversable> DynCohortVecs for UTXOCohortVecs<
&mut self,
height: Height,
height_price: Cents,
is_day_boundary: bool,
) -> Result<()> {
if let Some(state) = self.state.as_mut() {
self.metrics.compute_then_truncate_push_unrealized_states(
height,
height_price,
state,
is_day_boundary,
)?;
}
Ok(())
@@ -129,16 +131,14 @@ impl<Metrics: CohortMetricsBase + Traversable> DynCohortVecs for UTXOCohortVecs<
exit: &Exit,
) -> Result<()> {
self.metrics
.compute_rest_part1(blocks, prices, starting_indexes, exit)
}
fn compute_net_sentiment_height(
&mut self,
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
self.metrics
.compute_net_sentiment_height(starting_indexes, exit)
.compute_rest_part1(blocks, prices, starting_indexes, exit)?;
// Separate cohorts (with state) compute net_sentiment = greed - pain directly.
// Aggregate cohorts get it via weighted average in groups.rs.
if self.state.is_some() {
self.metrics
.compute_net_sentiment_height(starting_indexes, exit)?;
}
Ok(())
}
fn write_state(&mut self, height: Height, cleanup: bool) -> Result<()> {
@@ -1,5 +1,3 @@
use std::thread;
use brk_cohort::ByAddressType;
use brk_error::Result;
use brk_indexer::Indexer;
@@ -95,6 +93,15 @@ pub(crate) fn process_blocks(
let height_to_timestamp_collected = &cached_timestamps[start_usize..end_usize];
let height_to_price_collected = &cached_prices[start_usize..end_usize];
// Pre-compute day boundaries to avoid per-block division in the hot loop
let is_last_of_day: Vec<bool> = (start_usize..end_usize)
.map(|h| {
h == end_usize - 1
|| *cached_timestamps[h] / ONE_DAY_IN_SEC
!= *cached_timestamps[h + 1] / ONE_DAY_IN_SEC
})
.collect();
debug!("creating VecsReaders");
let mut vr = VecsReaders::new(&vecs.any_address_indexes, &vecs.addresses_data);
debug!("VecsReaders created");
@@ -246,14 +253,11 @@ pub(crate) fn process_blocks(
p2wsh: TypeIndex::from(first_p2wsh_vec[offset].to_usize()),
};
// Reset per-block values for all separate cohorts
reset_block_values(&mut vecs.utxo_cohorts, &mut vecs.address_cohorts);
// Reset per-block activity counts
activity_counts.reset();
// Collect output/input data using reusable iterators (16KB buffered reads)
// Must be done before thread::scope since iterators aren't Send
// Must be done before rayon::join since iterators aren't Send
let txoutdata_vec = txout_iters.collect_block_outputs(first_txoutindex, output_count);
let (input_values, input_prev_heights, input_outputtypes, input_typeindexes) =
@@ -263,55 +267,54 @@ pub(crate) fn process_blocks(
(&[][..], &[][..], &[][..], &[][..])
};
// Process outputs and inputs in parallel with tick-tock
let (outputs_result, inputs_result) = thread::scope(|scope| -> Result<_> {
// Tick-tock age transitions in background
scope.spawn(|| {
// Process outputs, inputs, and tick-tock in parallel via rayon::join
let (_, oi_result) = rayon::join(
|| {
vecs.utxo_cohorts
.tick_tock_next_block(chain_state, timestamp);
});
let outputs_handle = scope.spawn(|| {
// Process outputs (receive)
process_outputs(
txoutindex_to_txindex,
txoutdata_vec,
&first_addressindexes,
&cache,
&vr,
&vecs.any_address_indexes,
&vecs.addresses_data,
)
});
// Process inputs (send) - skip coinbase input
let inputs_result = if input_count > 1 {
process_inputs(
input_count - 1,
&txinindex_to_txindex[1..], // Skip coinbase
input_values,
input_outputtypes,
input_typeindexes,
input_prev_heights,
&first_addressindexes,
&cache,
&vr,
&vecs.any_address_indexes,
&vecs.addresses_data,
)?
} else {
InputsResult {
height_to_sent: Default::default(),
sent_data: Default::default(),
address_data: Default::default(),
txindex_vecs: Default::default(),
}
};
let outputs_result = outputs_handle.join().unwrap()?;
Ok((outputs_result, inputs_result))
})?;
},
|| -> Result<_> {
let (outputs_result, inputs_result) = rayon::join(
|| {
process_outputs(
txoutindex_to_txindex,
txoutdata_vec,
&first_addressindexes,
&cache,
&vr,
&vecs.any_address_indexes,
&vecs.addresses_data,
)
},
|| -> Result<_> {
if input_count > 1 {
process_inputs(
input_count - 1,
&txinindex_to_txindex[1..],
input_values,
input_outputtypes,
input_typeindexes,
input_prev_heights,
&first_addressindexes,
&cache,
&vr,
&vecs.any_address_indexes,
&vecs.addresses_data,
)
} else {
Ok(InputsResult {
height_to_sent: Default::default(),
sent_data: Default::default(),
address_data: Default::default(),
txindex_vecs: Default::default(),
})
}
},
);
Ok((outputs_result?, inputs_result?))
},
);
let (outputs_result, inputs_result) = oi_result?;
// Merge new address data into current cache
cache.merge_funded(outputs_result.address_data);
@@ -363,11 +366,20 @@ pub(crate) fn process_blocks(
}
// Process UTXO cohorts and Address cohorts in parallel
// - Main thread: UTXO cohorts receive/send
// - Spawned thread: Address cohorts process_received/process_sent
thread::scope(|scope| {
// Spawn address cohort processing in background thread
scope.spawn(|| {
let (_, addr_result) = rayon::join(
|| {
// UTXO cohorts receive/send
vecs.utxo_cohorts
.receive(transacted, height, timestamp, block_price);
if let Some(min_h) =
vecs.utxo_cohorts
.send(height_to_sent, chain_state, ctx.price_range_max)
{
min_supply_modified =
Some(min_supply_modified.map_or(min_h, |cur| cur.min(min_h)));
}
},
|| -> Result<()> {
let mut lookup = cache.as_lookup();
// Process received outputs (addresses receiving funds)
@@ -382,7 +394,6 @@ pub(crate) fn process_blocks(
);
// Process sent inputs (addresses sending funds)
// Uses separate price/timestamp vecs to avoid borrowing chain_state
process_sent(
inputs_result.sent_data,
&mut vecs.address_cohorts,
@@ -399,19 +410,9 @@ pub(crate) fn process_blocks(
timestamp,
&mut seen_senders,
)
.unwrap();
});
// Main thread: Update UTXO cohorts
vecs.utxo_cohorts
.receive(transacted, height, timestamp, block_price);
if let Some(min_h) =
vecs.utxo_cohorts
.send(height_to_sent, chain_state, ctx.price_range_max)
{
min_supply_modified = Some(min_supply_modified.map_or(min_h, |cur| cur.min(min_h)));
}
});
},
);
addr_result?;
// Push to height-indexed vectors
vecs.addr_count
@@ -424,9 +425,7 @@ pub(crate) fn process_blocks(
vecs.address_activity
.truncate_push_height(height, &activity_counts)?;
let h = height.to_usize();
let is_last_of_day = height == last_height
|| *cached_timestamps[h] / ONE_DAY_IN_SEC != *cached_timestamps[h + 1] / ONE_DAY_IN_SEC;
let is_last_of_day = is_last_of_day[offset];
let date_opt = is_last_of_day.then(|| Date::from(timestamp));
push_cohort_states(
@@ -434,11 +433,11 @@ pub(crate) fn process_blocks(
&mut vecs.address_cohorts,
height,
block_price,
date_opt.is_some(),
)?;
vecs.utxo_cohorts.truncate_push_aggregate_percentiles(
height,
block_price,
date_opt,
&vecs.states_path,
)?;
@@ -494,36 +493,42 @@ pub(crate) fn process_blocks(
Ok(())
}
/// Reset per-block values for all separate cohorts.
fn reset_block_values(utxo_cohorts: &mut UTXOCohorts, address_cohorts: &mut AddressCohorts) {
utxo_cohorts
.iter_separate_mut()
.for_each(|v| v.reset_single_iteration_values());
address_cohorts
.iter_separate_mut()
.for_each(|v| v.reset_single_iteration_values());
}
/// Push cohort states to height-indexed vectors.
/// Push cohort states to height-indexed vectors, then reset per-block values.
fn push_cohort_states(
utxo_cohorts: &mut UTXOCohorts,
address_cohorts: &mut AddressCohorts,
height: Height,
height_price: Cents,
is_day_boundary: bool,
) -> Result<()> {
let (r1, r2) = rayon::join(
|| {
utxo_cohorts.par_iter_separate_mut().try_for_each(|v| {
v.truncate_push(height)?;
v.compute_then_truncate_push_unrealized_states(height, height_price)
})
utxo_cohorts
.par_iter_separate_mut()
.try_for_each(|v| -> Result<()> {
v.truncate_push(height)?;
v.compute_then_truncate_push_unrealized_states(
height,
height_price,
is_day_boundary,
)?;
v.reset_single_iteration_values();
Ok(())
})
},
|| {
address_cohorts.par_iter_separate_mut().try_for_each(|v| {
v.truncate_push(height)?;
v.compute_then_truncate_push_unrealized_states(height, height_price)
})
address_cohorts
.par_iter_separate_mut()
.try_for_each(|v| -> Result<()> {
v.truncate_push(height)?;
v.compute_then_truncate_push_unrealized_states(
height,
height_price,
is_day_boundary,
)?;
v.reset_single_iteration_values();
Ok(())
})
},
);
r1?;
@@ -2,9 +2,7 @@ use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Bitcoin, Height, Indexes, Sats, StoredF64, Version};
use rayon::prelude::*;
use vecdb::{
AnyStoredVec, AnyVec, EagerVec, Exit, ImportableVec, PcoVec, Rw, StorageMode, WritableVec,
};
use vecdb::{AnyStoredVec, AnyVec, Exit, Rw, StorageMode, WritableVec};
use crate::{
blocks,
@@ -22,16 +20,10 @@ pub struct ActivityMetrics<M: StorageMode = Rw> {
/// 14-day EMA of sent supply (sats, btc, usd)
pub sent_ema: RollingEmas2w<M>,
/// Satoshi-blocks destroyed (supply * blocks_old when spent)
pub satblocks_destroyed: M::Stored<EagerVec<PcoVec<Height, Sats>>>,
/// Satoshi-days destroyed (supply * days_old when spent)
pub satdays_destroyed: M::Stored<EagerVec<PcoVec<Height, Sats>>>,
/// Coin-blocks destroyed (in BTC rather than sats)
/// Coin-blocks destroyed (in BTC)
pub coinblocks_destroyed: ComputedFromHeightCumulativeSum<StoredF64, M>,
/// Coin-days destroyed (in BTC rather than sats)
/// Coin-days destroyed (in BTC)
pub coindays_destroyed: ComputedFromHeightCumulativeSum<StoredF64, M>,
}
@@ -42,20 +34,9 @@ impl ActivityMetrics {
sent: cfg.import_value_cumulative("sent", Version::ZERO)?,
sent_ema: cfg.import_emas_2w("sent", Version::ZERO)?,
satblocks_destroyed: EagerVec::forced_import(
cfg.db,
&cfg.name("satblocks_destroyed"),
cfg.version,
)?,
satdays_destroyed: EagerVec::forced_import(
cfg.db,
&cfg.name("satdays_destroyed"),
cfg.version,
)?,
coinblocks_destroyed: cfg
.import_cumulative_sum("coinblocks_destroyed", Version::ZERO)?,
coindays_destroyed: cfg.import_cumulative_sum("coindays_destroyed", Version::ZERO)?,
.import_cumulative_sum("coinblocks_destroyed", Version::ONE)?,
coindays_destroyed: cfg.import_cumulative_sum("coindays_destroyed", Version::ONE)?,
})
}
@@ -66,8 +47,8 @@ impl ActivityMetrics {
.sats
.height
.len()
.min(self.satblocks_destroyed.len())
.min(self.satdays_destroyed.len())
.min(self.coinblocks_destroyed.height.len())
.min(self.coindays_destroyed.height.len())
}
/// Push activity state values to height-indexed vectors.
@@ -79,10 +60,14 @@ impl ActivityMetrics {
satdays_destroyed: Sats,
) -> Result<()> {
self.sent.base.sats.height.truncate_push(height, sent)?;
self.satblocks_destroyed
.truncate_push(height, satblocks_destroyed)?;
self.satdays_destroyed
.truncate_push(height, satdays_destroyed)?;
self.coinblocks_destroyed.height.truncate_push(
height,
StoredF64::from(Bitcoin::from(satblocks_destroyed)),
)?;
self.coindays_destroyed.height.truncate_push(
height,
StoredF64::from(Bitcoin::from(satdays_destroyed)),
)?;
Ok(())
}
@@ -90,8 +75,8 @@ impl ActivityMetrics {
pub(crate) fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = &mut dyn AnyStoredVec> {
vec![
&mut self.sent.base.sats.height as &mut dyn AnyStoredVec,
&mut self.satblocks_destroyed as &mut dyn AnyStoredVec,
&mut self.satdays_destroyed as &mut dyn AnyStoredVec,
&mut self.coinblocks_destroyed.height as &mut dyn AnyStoredVec,
&mut self.coindays_destroyed.height as &mut dyn AnyStoredVec,
]
.into_par_iter()
}
@@ -120,8 +105,8 @@ impl ActivityMetrics {
}
sum_others!(sent.base.sats.height);
sum_others!(satblocks_destroyed);
sum_others!(satdays_destroyed);
sum_others!(coinblocks_destroyed.height);
sum_others!(coindays_destroyed.height);
Ok(())
}
@@ -144,26 +129,10 @@ impl ActivityMetrics {
)?;
self.coinblocks_destroyed
.compute(starting_indexes.height, &window_starts, exit, |v| {
v.compute_transform(
starting_indexes.height,
&self.satblocks_destroyed,
|(i, v, ..)| (i, StoredF64::from(Bitcoin::from(v))),
exit,
)?;
Ok(())
})?;
.compute_rest(starting_indexes.height, &window_starts, exit)?;
self.coindays_destroyed
.compute(starting_indexes.height, &window_starts, exit, |v| {
v.compute_transform(
starting_indexes.height,
&self.satdays_destroyed,
|(i, v, ..)| (i, StoredF64::from(Bitcoin::from(v))),
exit,
)?;
Ok(())
})?;
.compute_rest(starting_indexes.height, &window_starts, exit)?;
Ok(())
}
@@ -5,7 +5,7 @@ use brk_types::{Cents, Dollars, Height, Indexes, Sats, Version};
use rayon::prelude::*;
use vecdb::{AnyStoredVec, Exit, ReadableVec, Rw, StorageMode};
use crate::{blocks, distribution::state::CohortState, prices};
use crate::{blocks, prices};
use crate::distribution::metrics::{
ActivityMetrics, CohortMetricsBase, CostBasisBase, ImportConfig, OutputsMetrics, RealizedBase,
@@ -73,18 +73,6 @@ impl CohortMetricsBase for AdjustedCohortMetrics {
self.activity.validate_computed_versions(base_version)?;
Ok(())
}
fn compute_then_truncate_push_unrealized_states(
&mut self,
height: Height,
height_price: Cents,
state: &mut CohortState,
) -> Result<()> {
state.apply_pending();
self.cost_basis.truncate_push_minmax(height, state)?;
let unrealized_state = state.compute_unrealized_state(height_price);
self.unrealized.truncate_push(height, &unrealized_state)?;
Ok(())
}
fn collect_all_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
let mut vecs: Vec<&mut dyn AnyStoredVec> = Vec::new();
vecs.extend(self.supply.par_iter_mut().collect::<Vec<_>>());
@@ -80,14 +80,12 @@ impl CohortMetricsBase for AllCohortMetrics {
height: Height,
height_price: Cents,
state: &mut CohortState,
is_day_boundary: bool,
) -> Result<()> {
state.apply_pending();
self.cost_basis.truncate_push_minmax(height, state)?;
let unrealized_state = state.compute_unrealized_state(height_price);
self.unrealized.truncate_push(height, &unrealized_state)?;
self.compute_and_push_unrealized_base(height, height_price, state)?;
self.cost_basis
.extended
.truncate_push_percentiles(height, state, height_price)?;
.truncate_push_percentiles(height, state, is_day_boundary)?;
Ok(())
}
fn collect_all_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
@@ -1,11 +1,11 @@
use brk_cohort::Filter;
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Cents, Dollars, Height, Indexes, Sats, Version};
use brk_types::{Dollars, Height, Indexes, Sats, Version};
use rayon::prelude::*;
use vecdb::{AnyStoredVec, Exit, ReadableVec, Rw, StorageMode};
use crate::{blocks, distribution::state::CohortState, prices};
use crate::{blocks, prices};
use crate::distribution::metrics::{
ActivityMetrics, CohortMetricsBase, CostBasisBase, ImportConfig, OutputsMetrics, RealizedBase,
@@ -72,18 +72,6 @@ impl CohortMetricsBase for BasicCohortMetrics {
self.activity.validate_computed_versions(base_version)?;
Ok(())
}
fn compute_then_truncate_push_unrealized_states(
&mut self,
height: Height,
height_price: Cents,
state: &mut CohortState,
) -> Result<()> {
state.apply_pending();
self.cost_basis.truncate_push_minmax(height, state)?;
let unrealized_state = state.compute_unrealized_state(height_price);
self.unrealized.truncate_push(height, &unrealized_state)?;
Ok(())
}
fn collect_all_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
let mut vecs: Vec<&mut dyn AnyStoredVec> = Vec::new();
vecs.extend(self.supply.par_iter_mut().collect::<Vec<_>>());
@@ -80,14 +80,12 @@ impl CohortMetricsBase for ExtendedCohortMetrics {
height: Height,
height_price: Cents,
state: &mut CohortState,
is_day_boundary: bool,
) -> Result<()> {
state.apply_pending();
self.cost_basis.truncate_push_minmax(height, state)?;
let unrealized_state = state.compute_unrealized_state(height_price);
self.unrealized.truncate_push(height, &unrealized_state)?;
self.compute_and_push_unrealized_base(height, height_price, state)?;
self.cost_basis
.extended
.truncate_push_percentiles(height, state, height_price)?;
.truncate_push_percentiles(height, state, is_day_boundary)?;
Ok(())
}
fn collect_all_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
@@ -79,14 +79,12 @@ impl CohortMetricsBase for ExtendedAdjustedCohortMetrics {
height: Height,
height_price: Cents,
state: &mut CohortState,
is_day_boundary: bool,
) -> Result<()> {
state.apply_pending();
self.cost_basis.truncate_push_minmax(height, state)?;
let unrealized_state = state.compute_unrealized_state(height_price);
self.unrealized.truncate_push(height, &unrealized_state)?;
self.compute_and_push_unrealized_base(height, height_price, state)?;
self.cost_basis
.extended
.truncate_push_percentiles(height, state, height_price)?;
.truncate_push_percentiles(height, state, is_day_boundary)?;
Ok(())
}
fn collect_all_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
@@ -1,11 +1,11 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{BasisPoints16, Cents, Height, Version};
use vecdb::{AnyStoredVec, Rw, StorageMode, WritableVec};
use brk_types::{Cents, Height, Version};
use vecdb::{AnyStoredVec, Rw, StorageMode};
use crate::{
distribution::state::CohortState,
internal::{PERCENTILES_LEN, PercentFromHeight, PercentilesVecs, compute_spot_percentile_rank},
internal::{PERCENTILES_LEN, PercentilesVecs},
};
use crate::distribution::metrics::ImportConfig;
@@ -18,12 +18,6 @@ pub struct CostBasisExtended<M: StorageMode = Rw> {
/// Invested capital percentiles (USD-weighted)
pub invested_capital: PercentilesVecs<M>,
/// What percentile of cost basis is below spot (sat-weighted)
pub spot_cost_basis_percentile: PercentFromHeight<BasisPoints16, M>,
/// What percentile of invested capital is below spot (USD-weighted)
pub spot_invested_capital_percentile: PercentFromHeight<BasisPoints16, M>,
}
impl CostBasisExtended {
@@ -41,10 +35,6 @@ impl CostBasisExtended {
cfg.version,
cfg.indexes,
)?,
spot_cost_basis_percentile: cfg
.import_percent_bp16("spot_cost_basis_percentile", Version::ZERO)?,
spot_invested_capital_percentile: cfg
.import_percent_bp16("spot_invested_capital_percentile", Version::ZERO)?,
})
}
@@ -52,34 +42,36 @@ impl CostBasisExtended {
&mut self,
height: Height,
state: &mut CohortState,
spot: Cents,
is_day_boundary: bool,
) -> Result<()> {
let computed = state.compute_percentiles();
let computed = if is_day_boundary {
state.compute_percentiles()
} else {
state.cached_percentiles()
};
let sat_prices = computed
.as_ref()
.map(|p| p.sat_weighted)
.unwrap_or([Cents::ZERO; PERCENTILES_LEN]);
self.percentiles.truncate_push(height, &sat_prices)?;
let rank = compute_spot_percentile_rank(&sat_prices, spot);
self.spot_cost_basis_percentile
.bps
.height
.truncate_push(height, rank)?;
let usd_prices = computed
.as_ref()
.map(|p| p.usd_weighted)
.unwrap_or([Cents::ZERO; PERCENTILES_LEN]);
self.invested_capital.truncate_push(height, &usd_prices)?;
let rank = compute_spot_percentile_rank(&usd_prices, spot);
self.spot_invested_capital_percentile
.bps
.height
.truncate_push(height, rank)?;
self.push_arrays(height, &sat_prices, &usd_prices)
}
/// Push pre-computed percentile arrays.
/// Shared by both individual cohort and aggregate (K-way merge) paths.
pub(crate) fn push_arrays(
&mut self,
height: Height,
sat_prices: &[Cents; PERCENTILES_LEN],
usd_prices: &[Cents; PERCENTILES_LEN],
) -> Result<()> {
self.percentiles.truncate_push(height, sat_prices)?;
self.invested_capital.truncate_push(height, usd_prices)?;
Ok(())
}
@@ -97,8 +89,6 @@ impl CostBasisExtended {
.iter_mut()
.map(|v| &mut v.cents.height as &mut dyn AnyStoredVec),
);
vecs.push(&mut self.spot_cost_basis_percentile.bps.height);
vecs.push(&mut self.spot_invested_capital_percentile.bps.height);
vecs
}
@@ -107,14 +97,6 @@ impl CostBasisExtended {
.validate_computed_version_or_reset(base_version)?;
self.invested_capital
.validate_computed_version_or_reset(base_version)?;
self.spot_cost_basis_percentile
.bps
.height
.validate_computed_version_or_reset(base_version)?;
self.spot_invested_capital_percentile
.bps
.height
.validate_computed_version_or_reset(base_version)?;
Ok(())
}
}
@@ -42,12 +42,32 @@ pub trait CohortMetricsBase: Send + Sync {
fn validate_computed_versions(&mut self, base_version: Version) -> Result<()>;
/// Apply pending, push min/max cost basis, compute and push unrealized state.
fn compute_and_push_unrealized_base(
&mut self,
height: Height,
height_price: Cents,
state: &mut CohortState,
) -> Result<()> {
state.apply_pending();
self.cost_basis_base_mut()
.truncate_push_minmax(height, state)?;
let unrealized_state = state.compute_unrealized_state(height_price);
self.unrealized_base_mut()
.truncate_push(height, &unrealized_state)?;
Ok(())
}
/// Compute and push unrealized states. Extended types override to also push percentiles.
fn compute_then_truncate_push_unrealized_states(
&mut self,
height: Height,
height_price: Cents,
state: &mut CohortState,
) -> Result<()>;
_is_day_boundary: bool,
) -> Result<()> {
self.compute_and_push_unrealized_base(height, height_price, state)
}
fn collect_all_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec>;
@@ -1,7 +1,7 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{
BasisPoints16, BasisPoints32, BasisPointsSigned32, Bitcoin, Cents, CentsSats, CentsSigned,
BasisPoints32, BasisPointsSigned32, Bitcoin, Cents, CentsSats, CentsSigned,
CentsSquaredSats, Dollars, Height, Indexes, Sats, StoredF32, StoredF64, Version,
};
use vecdb::{
@@ -16,7 +16,7 @@ use crate::{
CentsPlus, CentsUnsignedToDollars, ComputedFromHeight, ComputedFromHeightCumulative,
ComputedFromHeightRatio, FiatFromHeight, Identity, LazyFromHeight,
NegCentsUnsignedToDollars, PercentFromHeight, PercentRollingEmas1w1m,
PercentRollingWindows, Price, RatioCents64, RatioCentsBp16, RatioCentsBp32,
PercentRollingWindows, Price, RatioCents64, RatioCentsBp32,
RatioCentsSignedCentsBps32, RatioCentsSignedDollarsBps32, RollingEmas1w1m, RollingEmas2w,
RollingWindows, ValueFromHeightCumulative,
},
@@ -53,8 +53,8 @@ pub struct RealizedBase<M: StorageMode = Rw> {
pub net_realized_pnl_ema_1w: ComputedFromHeight<CentsSigned, M>,
pub gross_pnl: FiatFromHeight<Cents, M>,
pub realized_profit_rel_to_realized_cap: PercentFromHeight<BasisPoints16, M>,
pub realized_loss_rel_to_realized_cap: PercentFromHeight<BasisPoints16, M>,
pub realized_profit_rel_to_realized_cap: PercentFromHeight<BasisPoints32, M>,
pub realized_loss_rel_to_realized_cap: PercentFromHeight<BasisPoints32, M>,
pub net_realized_pnl_rel_to_realized_cap: PercentFromHeight<BasisPointsSigned32, M>,
pub profit_value_created: ComputedFromHeight<Cents, M>,
@@ -122,9 +122,9 @@ impl RealizedBase {
let gross_pnl = cfg.import_fiat("realized_gross_pnl", v0)?;
let realized_profit_rel_to_realized_cap =
cfg.import_percent_bp16("realized_profit_rel_to_realized_cap", v1)?;
cfg.import_percent_bp32("realized_profit_rel_to_realized_cap", Version::new(2))?;
let realized_loss_rel_to_realized_cap =
cfg.import_percent_bp16("realized_loss_rel_to_realized_cap", v1)?;
cfg.import_percent_bp32("realized_loss_rel_to_realized_cap", Version::new(2))?;
let net_realized_pnl_rel_to_realized_cap =
cfg.import_percent_bps32("net_realized_pnl_rel_to_realized_cap", Version::new(2))?;
@@ -649,14 +649,14 @@ impl RealizedBase {
// Realized profit/loss/net relative to realized cap
self.realized_profit_rel_to_realized_cap
.compute_binary::<Cents, Cents, RatioCentsBp16>(
.compute_binary::<Cents, Cents, RatioCentsBp32>(
starting_indexes.height,
&self.realized_profit.height,
&self.realized_cap_cents.height,
exit,
)?;
self.realized_loss_rel_to_realized_cap
.compute_binary::<Cents, Cents, RatioCentsBp16>(
.compute_binary::<Cents, Cents, RatioCentsBp32>(
starting_indexes.height,
&self.realized_loss.height,
&self.realized_cap_cents.height,
@@ -185,7 +185,11 @@ impl CohortState {
let sats = supply.value;
let current_ps = CentsSats::from_price_sats(current_price, sats);
let prev_ps = CentsSats::from_price_sats(prev_price, sats);
let ath_ps = CentsSats::from_price_sats(ath, sats);
let ath_ps = if ath == current_price {
current_ps
} else {
CentsSats::from_price_sats(ath, sats)
};
let prev_investor_cap = prev_ps.to_investor_cap(prev_price);
Some(SendPrecomputed {
sats,
@@ -287,6 +291,10 @@ impl CohortState {
self.cost_basis_data.compute_percentiles()
}
pub(crate) fn cached_percentiles(&self) -> Option<Percentiles> {
self.cost_basis_data.cached_percentiles()
}
pub(crate) fn compute_unrealized_state(&mut self, height_price: Cents) -> UnrealizedState {
self.cost_basis_data.compute_unrealized_state(height_price)
}
@@ -83,7 +83,7 @@ impl CostBasisData {
}
fn assert_pending_empty(&self) {
assert!(
debug_assert!(
self.pending.is_empty() && self.pending_raw_is_zero(),
"CostBasisData: pending not empty, call apply_pending first"
);
@@ -180,7 +180,7 @@ impl CostBasisData {
}
pub(crate) fn apply_pending(&mut self) {
if self.pending.is_empty() && self.pending_raw_is_zero() {
if self.pending.is_empty() {
return;
}
self.generation = self.generation.wrapping_add(1);
@@ -277,6 +277,10 @@ impl CostBasisData {
self.cached_percentiles = None;
}
pub(crate) fn cached_percentiles(&self) -> Option<Percentiles> {
self.cached_percentiles
}
pub(crate) fn compute_percentiles(&mut self) -> Option<Percentiles> {
self.assert_pending_empty();
if !self.percentiles_dirty {
@@ -35,6 +35,9 @@ impl RealizedState {
/// Get realized cap as CentsUnsigned (divides by ONE_BTC).
#[inline]
pub(crate) fn cap(&self) -> Cents {
if self.cap_raw == 0 {
return Cents::ZERO;
}
Cents::new((self.cap_raw / Sats::ONE_BTC_U128) as u64)
}
@@ -76,18 +79,27 @@ impl RealizedState {
/// Get realized profit as CentsUnsigned.
#[inline]
pub(crate) fn profit(&self) -> Cents {
if self.profit_raw == 0 {
return Cents::ZERO;
}
Cents::new((self.profit_raw / Sats::ONE_BTC_U128) as u64)
}
/// Get realized loss as CentsUnsigned.
#[inline]
pub(crate) fn loss(&self) -> Cents {
if self.loss_raw == 0 {
return Cents::ZERO;
}
Cents::new((self.loss_raw / Sats::ONE_BTC_U128) as u64)
}
/// Get profit value created as CentsUnsigned (sell_price × sats for profit cases).
#[inline]
pub(crate) fn profit_value_created(&self) -> Cents {
if self.profit_value_created_raw == 0 {
return Cents::ZERO;
}
Cents::new((self.profit_value_created_raw / Sats::ONE_BTC_U128) as u64)
}
@@ -95,12 +107,18 @@ impl RealizedState {
/// This is also known as profit_flow.
#[inline]
pub(crate) fn profit_value_destroyed(&self) -> Cents {
if self.profit_value_destroyed_raw == 0 {
return Cents::ZERO;
}
Cents::new((self.profit_value_destroyed_raw / Sats::ONE_BTC_U128) as u64)
}
/// Get loss value created as CentsUnsigned (sell_price × sats for loss cases).
#[inline]
pub(crate) fn loss_value_created(&self) -> Cents {
if self.loss_value_created_raw == 0 {
return Cents::ZERO;
}
Cents::new((self.loss_value_created_raw / Sats::ONE_BTC_U128) as u64)
}
@@ -108,6 +126,9 @@ impl RealizedState {
/// This is also known as capitulation_flow.
#[inline]
pub(crate) fn loss_value_destroyed(&self) -> Cents {
if self.loss_value_destroyed_raw == 0 {
return Cents::ZERO;
}
Cents::new((self.loss_value_destroyed_raw / Sats::ONE_BTC_U128) as u64)
}
@@ -116,6 +137,9 @@ impl RealizedState {
/// by selling at peak instead of when actually sold.
#[inline]
pub(crate) fn peak_regret(&self) -> Cents {
if self.peak_regret_raw == 0 {
return Cents::ZERO;
}
Cents::new((self.peak_regret_raw / Sats::ONE_BTC_U128) as u64)
}
@@ -61,17 +61,22 @@ struct CachedStateRaw {
impl CachedStateRaw {
/// Convert raw values to final output by dividing by ONE_BTC.
fn to_output(&self) -> UnrealizedState {
#[inline(always)]
fn div_btc(raw: u128) -> Cents {
if raw == 0 {
Cents::ZERO
} else {
Cents::new((raw / Sats::ONE_BTC_U128) as u64)
}
}
UnrealizedState {
supply_in_profit: self.supply_in_profit,
supply_in_loss: self.supply_in_loss,
unrealized_profit: Cents::new((self.unrealized_profit / Sats::ONE_BTC_U128) as u64),
unrealized_loss: Cents::new((self.unrealized_loss / Sats::ONE_BTC_U128) as u64),
invested_capital_in_profit: Cents::new(
(self.invested_capital_in_profit / Sats::ONE_BTC_U128) as u64,
),
invested_capital_in_loss: Cents::new(
(self.invested_capital_in_loss / Sats::ONE_BTC_U128) as u64,
),
unrealized_profit: div_btc(self.unrealized_profit),
unrealized_loss: div_btc(self.unrealized_loss),
invested_capital_in_profit: div_btc(self.invested_capital_in_profit),
invested_capital_in_loss: div_btc(self.invested_capital_in_loss),
investor_cap_in_profit_raw: self.investor_cap_in_profit,
investor_cap_in_loss_raw: self.investor_cap_in_loss,
invested_capital_in_profit_raw: self.invested_capital_in_profit,
@@ -2,6 +2,7 @@ use std::ops::{Add, AddAssign};
use brk_cohort::{ByAmountRange, GroupedByType};
use brk_types::{OutputType, Sats, SupplyState};
use vecdb::unlikely;
#[derive(Default, Debug)]
pub struct Transacted {
@@ -20,7 +21,7 @@ impl Transacted {
*self.by_type.get_mut(_type) += &supply;
if _type.is_unspendable() {
if unlikely(_type.is_unspendable()) {
return;
}
@@ -2,41 +2,36 @@ use brk_types::StoredF32;
/// Fast expanding percentile tracker using a Fenwick tree (Binary Indexed Tree).
///
/// Values are discretized to BasisPoints32 precision (×10000) and tracked in
/// Values are discretized to 10 BPS (0.1%) resolution and tracked in
/// a fixed-size frequency array with Fenwick prefix sums. This gives:
/// - O(log N) insert (N = tree size, ~18 ops for 200k buckets)
/// - O(log N) insert (N = tree size, ~16 ops for 43k buckets)
/// - O(log N) percentile query via prefix-sum walk
/// - Exact at BasisPoints32 resolution (no approximation)
/// - 0.1% value resolution (10 BPS granularity)
#[derive(Clone)]
pub(crate) struct ExpandingPercentiles {
/// Fenwick tree storing cumulative frequency counts.
/// Index 0 is unused (1-indexed). tree[i] covers bucket (i - 1 + offset).
tree: Vec<u64>,
count: u64,
/// Offset so bucket 0 in the tree corresponds to BPS value `offset`.
offset: i32,
size: usize,
/// 1-indexed: tree[0] is unused, tree[1..=TREE_SIZE] hold data.
tree: Vec<u32>,
count: u32,
}
/// Max BPS value supported. Ratio of 42.0 = 420,000 BPS.
/// Bucket granularity in BPS. 10 BPS = 0.1% = 0.001 ratio.
const BUCKET_BPS: i32 = 10;
/// Max ratio supported: 43.0 = 430,000 BPS.
const MAX_BPS: i32 = 430_000;
/// Min BPS value supported (0 = ratio of 0.0).
const MIN_BPS: i32 = 0;
const TREE_SIZE: usize = (MAX_BPS - MIN_BPS) as usize + 1;
const TREE_SIZE: usize = (MAX_BPS / BUCKET_BPS) as usize + 1;
impl Default for ExpandingPercentiles {
fn default() -> Self {
Self {
tree: vec![0u64; TREE_SIZE + 1], // 1-indexed
tree: vec![0u32; TREE_SIZE + 1], // 1-indexed
count: 0,
offset: MIN_BPS,
size: TREE_SIZE,
}
}
}
impl ExpandingPercentiles {
pub fn count(&self) -> u64 {
pub fn count(&self) -> u32 {
self.count
}
@@ -47,29 +42,27 @@ impl ExpandingPercentiles {
/// Convert f32 ratio to bucket index (1-indexed for Fenwick).
#[inline]
fn to_bucket(&self, value: f32) -> usize {
fn to_bucket(value: f32) -> usize {
let bps = (value as f64 * 10000.0).round() as i32;
let clamped = bps.clamp(self.offset, self.offset + self.size as i32 - 1);
(clamped - self.offset) as usize + 1 // 1-indexed
let bucket = (bps / BUCKET_BPS).clamp(0, TREE_SIZE as i32 - 1);
bucket as usize + 1
}
/// Bulk-load values in O(n + N) instead of O(n log N).
/// Builds raw frequency counts, then converts to Fenwick in-place.
pub fn add_bulk(&mut self, values: &[StoredF32]) {
// Build raw frequency counts into tree (treated as flat array)
for &v in values {
let v = *v;
if v.is_nan() {
continue;
}
self.count += 1;
let bucket = self.to_bucket(v);
self.tree[bucket] += 1;
self.tree[Self::to_bucket(v)] += 1;
}
// Convert flat frequencies to Fenwick tree in O(N)
for i in 1..=self.size {
for i in 1..=TREE_SIZE {
let parent = i + (i & i.wrapping_neg());
if parent <= self.size {
if parent <= TREE_SIZE {
let val = self.tree[i];
self.tree[parent] += val;
}
@@ -83,47 +76,40 @@ impl ExpandingPercentiles {
return;
}
self.count += 1;
let mut i = self.to_bucket(value);
while i <= self.size {
let mut i = Self::to_bucket(value);
while i <= TREE_SIZE {
self.tree[i] += 1;
i += i & i.wrapping_neg(); // i += lowbit(i)
i += i & i.wrapping_neg();
}
}
/// Find the bucket containing the k-th element (1-indexed k).
/// Uses the standard Fenwick tree walk-down in O(log N).
#[inline]
fn kth(&self, mut k: u64) -> usize {
fn kth(&self, mut k: u32) -> usize {
let mut pos = 0;
let mut bit = 1 << (usize::BITS - 1 - self.size.leading_zeros()); // highest power of 2 <= size
let mut bit = 1 << (usize::BITS - 1 - TREE_SIZE.leading_zeros());
while bit > 0 {
let next = pos + bit;
if next <= self.size && self.tree[next] < k {
if next <= TREE_SIZE && self.tree[next] < k {
k -= self.tree[next];
pos = next;
}
bit >>= 1;
}
pos + 1 // 1-indexed bucket
}
/// Convert bucket index back to BPS u32 value.
#[inline]
fn bucket_to_bps(&self, bucket: usize) -> u32 {
(bucket as i32 - 1 + self.offset) as u32
pos + 1
}
/// Compute 6 percentiles in one call. O(6 × log N).
/// Quantiles q must be in (0, 1).
/// Quantiles q must be in (0, 1). Output is in BPS.
pub fn quantiles(&self, qs: &[f64; 6], out: &mut [u32; 6]) {
if self.count == 0 {
out.iter_mut().for_each(|o| *o = 0);
return;
}
for (i, &q) in qs.iter().enumerate() {
// k = ceil(q * count), clamped to [1, count]
let k = ((q * self.count as f64).ceil() as u64).clamp(1, self.count);
out[i] = self.bucket_to_bps(self.kth(k));
let k = ((q * self.count as f64).ceil() as u32).clamp(1, self.count);
out[i] = (self.kth(k) as u32 - 1) * BUCKET_BPS as u32;
}
}
}
@@ -141,30 +127,19 @@ mod tests {
#[test]
fn basic_quantiles() {
let mut ep = ExpandingPercentiles::default();
// Add ratios 0.01 to 1.0 (BPS 100 to 10000)
for i in 1..=1000 {
ep.add(i as f32 / 1000.0);
}
assert_eq!(ep.count(), 1000);
let median = quantile(&ep, 0.5);
// 0.5 ratio = 5000 BPS, median of 1..1000 ratios ≈ 500/1000 = 0.5 = 5000 BPS
assert!(
(median as i32 - 5000).abs() < 100,
"median was {median}"
);
assert!((median as i32 - 5000).abs() < 100, "median was {median}");
let p99 = quantile(&ep, 0.99);
assert!(
(p99 as i32 - 9900).abs() < 100,
"p99 was {p99}"
);
assert!((p99 as i32 - 9900).abs() < 100, "p99 was {p99}");
let p01 = quantile(&ep, 0.01);
assert!(
(p01 as i32 - 100).abs() < 100,
"p01 was {p01}"
);
assert!((p01 as i32 - 100).abs() < 100, "p01 was {p01}");
}
#[test]
@@ -177,10 +152,9 @@ mod tests {
#[test]
fn single_value() {
let mut ep = ExpandingPercentiles::default();
ep.add(0.42); // 4200 BPS
assert_eq!(quantile(&ep, 0.0001), 4200);
assert_eq!(quantile(&ep, 0.5), 4200);
assert_eq!(quantile(&ep, 0.9999), 4200);
ep.add(0.42);
let v = quantile(&ep, 0.5);
assert!((v as i32 - 4200).abs() <= BUCKET_BPS, "got {v}");
}
#[test]
@@ -61,6 +61,19 @@ where
T: Default + SubAssign,
{
compute_height(&mut self.height)?;
self.compute_rest(max_from, windows, exit)
}
/// Compute cumulative + rolling sum from already-populated height data.
pub(crate) fn compute_rest(
&mut self,
max_from: Height,
windows: &WindowStarts<'_>,
exit: &Exit,
) -> Result<()>
where
T: Default + SubAssign,
{
self.cumulative
.height
.compute_cumulative(max_from, &self.height, exit)?;
@@ -1,6 +1,6 @@
use brk_error::Result;
use brk_traversable::{Traversable, TreeNode};
use brk_types::{BasisPoints16, Cents, Height, Version};
use brk_types::{Cents, Height, Version};
use vecdb::{AnyExportableVec, Database, ReadOnlyClone, Ro, Rw, StorageMode, WritableVec};
use crate::indexes;
@@ -11,62 +11,6 @@ pub const PERCENTILES: [u8; 19] = [
];
pub const PERCENTILES_LEN: usize = PERCENTILES.len();
/// Compute spot percentile rank by interpolating within percentile bands.
/// Returns a value between 0 and 100 indicating where spot sits in the distribution.
pub(crate) fn compute_spot_percentile_rank(
percentile_prices: &[Cents; PERCENTILES_LEN],
spot: Cents,
) -> BasisPoints16 {
if spot == Cents::ZERO && percentile_prices[0] == Cents::ZERO {
return BasisPoints16::ZERO;
}
let spot_f64 = f64::from(spot);
// Below lowest percentile (p5) - extrapolate towards 0
let p5 = f64::from(percentile_prices[0]);
if spot_f64 <= p5 {
if p5 == 0.0 {
return BasisPoints16::ZERO;
}
// Linear extrapolation: rank = 5% * (spot / p5)
return BasisPoints16::from((0.05 * spot_f64 / p5).max(0.0));
}
// Above highest percentile (p95) - extrapolate towards 100
let p95 = f64::from(percentile_prices[PERCENTILES_LEN - 1]);
let p90 = f64::from(percentile_prices[PERCENTILES_LEN - 2]);
if spot_f64 >= p95 {
if p95 == p90 {
return BasisPoints16::ONE;
}
// Linear extrapolation using p90-p95 slope
let slope = 0.05 / (p95 - p90);
return BasisPoints16::from((0.95 + (spot_f64 - p95) * slope).min(1.0));
}
// Find the band containing spot and interpolate
for i in 0..PERCENTILES_LEN - 1 {
let lower = f64::from(percentile_prices[i]);
let upper = f64::from(percentile_prices[i + 1]);
if spot_f64 >= lower && spot_f64 <= upper {
let lower_pct = f64::from(PERCENTILES[i]) / 100.0;
let upper_pct = f64::from(PERCENTILES[i + 1]) / 100.0;
if upper == lower {
return BasisPoints16::from(lower_pct);
}
// Linear interpolation
let ratio = (spot_f64 - lower) / (upper - lower);
return BasisPoints16::from(lower_pct + ratio * (upper_pct - lower_pct));
}
}
BasisPoints16::ZERO
}
pub struct PercentilesVecs<M: StorageMode = Rw> {
pub vecs: [Price<ComputedFromHeight<Cents, M>>; PERCENTILES_LEN],
}
@@ -5,20 +5,20 @@ use derive_more::{Deref, DerefMut};
use vecdb::{Database, EagerVec, Exit, PcoVec, Rw, StorageMode};
use crate::internal::{ComputedFromHeight, Price};
use crate::{blocks, indexes, prices};
use crate::{indexes, prices};
use super::ComputedFromHeightRatioExtended;
use super::ComputedFromHeightRatio;
#[derive(Deref, DerefMut, Traversable)]
pub struct ComputedFromHeightPriceWithRatioExtended<M: StorageMode = Rw> {
pub struct ComputedFromHeightPriceWithRatio<M: StorageMode = Rw> {
#[deref]
#[deref_mut]
#[traversable(flatten)]
pub inner: ComputedFromHeightRatioExtended<M>,
pub inner: ComputedFromHeightRatio<M>,
pub price: Price<ComputedFromHeight<Cents, M>>,
}
impl ComputedFromHeightPriceWithRatioExtended {
impl ComputedFromHeightPriceWithRatio {
pub(crate) fn forced_import(
db: &Database,
name: &str,
@@ -27,15 +27,14 @@ impl ComputedFromHeightPriceWithRatioExtended {
) -> Result<Self> {
let v = version + Version::TWO;
Ok(Self {
inner: ComputedFromHeightRatioExtended::forced_import(db, name, version, indexes)?,
inner: ComputedFromHeightRatio::forced_import(db, name, version, indexes)?,
price: Price::forced_import(db, name, v, indexes)?,
})
}
/// Compute price via closure (in cents), then compute ratio + extended metrics.
/// Compute price via closure (in cents), then compute ratio.
pub(crate) fn compute_all<F>(
&mut self,
blocks: &blocks::Vecs,
prices: &prices::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
@@ -45,13 +44,9 @@ impl ComputedFromHeightPriceWithRatioExtended {
F: FnMut(&mut EagerVec<PcoVec<Height, Cents>>) -> Result<()>,
{
compute_price(&mut self.price.cents.height)?;
self.inner.compute_rest(
blocks,
prices,
starting_indexes,
exit,
&self.price.cents.height,
)?;
let close_price = &prices.price.cents.height;
self.inner
.compute_ratio(starting_indexes, close_price, &self.price.cents.height, exit)?;
Ok(())
}
}
@@ -23,7 +23,7 @@ pub use derived::{
RatioCents64, TimesSqrt,
};
pub use ratio::{
NegRatioDollarsBps32, RatioCentsBp16, RatioCentsBp32, RatioCentsSignedCentsBps32,
NegRatioDollarsBps32, RatioCentsBp32, RatioCentsSignedCentsBps32,
RatioCentsSignedDollarsBps32, RatioDiffCentsBps32, RatioDiffDollarsBps32, RatioDiffF32Bps32,
RatioDollarsBp16, RatioDollarsBp32, RatioDollarsBps32, RatioSatsBp16, RatioU32Bp16,
RatioU64Bp16,
@@ -30,19 +30,6 @@ impl BinaryTransform<Sats, Sats, BasisPoints16> for RatioSatsBp16 {
}
}
pub struct RatioCentsBp16;
impl BinaryTransform<Cents, Cents, BasisPoints16> for RatioCentsBp16 {
#[inline(always)]
fn apply(numerator: Cents, denominator: Cents) -> BasisPoints16 {
if denominator == Cents::ZERO {
BasisPoints16::ZERO
} else {
BasisPoints16::from(numerator.inner() as f64 / denominator.inner() as f64)
}
}
}
pub struct RatioCentsBp32;
impl BinaryTransform<Cents, Cents, BasisPoints32> for RatioCentsBp32 {
@@ -143,7 +130,12 @@ pub struct RatioDollarsBp32;
impl BinaryTransform<Dollars, Dollars, BasisPoints32> for RatioDollarsBp32 {
#[inline(always)]
fn apply(numerator: Dollars, denominator: Dollars) -> BasisPoints32 {
BasisPoints32::from(f64::from(numerator) / f64::from(denominator))
let ratio = f64::from(numerator) / f64::from(denominator);
if ratio.is_finite() {
BasisPoints32::from(ratio)
} else {
BasisPoints32::ZERO
}
}
}
@@ -29,10 +29,6 @@ impl Vecs {
self.returns
.compute(prices, blocks, &self.lookback, starting_indexes, exit)?;
// Volatility (depends on returns)
self.volatility
.compute(&self.returns, starting_indexes.height, exit)?;
// Range metrics (independent)
self.range.compute(prices, blocks, starting_indexes, exit)?;
@@ -66,16 +66,17 @@ impl Vecs {
self.period_cost_basis.zip_mut_with_days(&self.period_stack)
{
let days = days as usize;
let start = average_price.cents.height.len();
let stack_data = stack
.sats
.height
.collect_range_at(sh, stack.sats.height.len());
.collect_range_at(start, stack.sats.height.len());
average_price.cents.height.compute_transform(
starting_indexes.height,
h2d,
|(h, di, _)| {
let di_usize = di.to_usize();
let stack_sats = stack_data[h.to_usize() - sh];
let stack_sats = stack_data[h.to_usize() - start];
let avg = if di_usize > first_price_di {
let num_days = days.min(di_usize + 1).min(di_usize + 1 - first_price_di);
Cents::from(DCA_AMOUNT * num_days / Bitcoin::from(stack_sats))
@@ -123,15 +124,16 @@ impl Vecs {
self.period_lump_sum_stack.zip_mut_with_days(&lookback_dca)
{
let total_invested = DCA_AMOUNT * days as usize;
let ls_start = stack.sats.height.len();
let lookback_data = lookback_price
.cents
.height
.collect_range_at(sh, lookback_price.cents.height.len());
.collect_range_at(ls_start, lookback_price.cents.height.len());
stack.sats.height.compute_transform(
starting_indexes.height,
h2d,
|(h, _di, _)| {
let lp = lookback_data[h.to_usize() - sh];
let lp = lookback_data[h.to_usize() - ls_start];
let sats = if lp == Cents::ZERO {
Sats::ZERO
} else {
@@ -217,10 +219,11 @@ impl Vecs {
.zip(start_days)
{
let from_usize = from.to_usize();
let cls_start = average_price.cents.height.len();
let stack_data = stack
.sats
.height
.collect_range_at(sh, stack.sats.height.len());
.collect_range_at(cls_start, stack.sats.height.len());
average_price.cents.height.compute_transform(
starting_indexes.height,
h2d,
@@ -229,7 +232,7 @@ impl Vecs {
if di_usize < from_usize {
return (h, Cents::ZERO);
}
let stack_sats = stack_data[h.to_usize() - sh];
let stack_sats = stack_data[h.to_usize() - cls_start];
let num_days = di_usize + 1 - from_usize;
let avg = Cents::from(DCA_AMOUNT * num_days / Bitcoin::from(stack_sats));
(h, avg)
+1 -1
View File
@@ -25,7 +25,7 @@ impl Vecs {
let ath = AthVecs::forced_import(&db, version, indexes)?;
let lookback = LookbackVecs::forced_import(&db, version, indexes)?;
let returns = ReturnsVecs::forced_import(&db, version, indexes)?;
let volatility = VolatilityVecs::forced_import(&db, version, indexes, &returns)?;
let volatility = VolatilityVecs::forced_import(version, &returns)?;
let range = RangeVecs::forced_import(&db, version, indexes)?;
let moving_average = MovingAverageVecs::forced_import(&db, version, indexes)?;
let dca = DcaVecs::forced_import(&db, version, indexes)?;
+1 -1
View File
@@ -29,7 +29,7 @@ pub struct Vecs<M: StorageMode = Rw> {
pub ath: AthVecs<M>,
pub lookback: LookbackVecs<M>,
pub returns: ReturnsVecs<M>,
pub volatility: VolatilityVecs<M>,
pub volatility: VolatilityVecs,
pub range: RangeVecs<M>,
pub moving_average: MovingAverageVecs<M>,
pub dca: DcaVecs<M>,
@@ -34,7 +34,7 @@ impl Vecs {
(&mut self.price_sma_4y, 4 * 365),
] {
let window_starts = blocks.count.start_vec(period);
sma.compute_all(blocks, prices, starting_indexes, exit, |v| {
sma.compute_all(prices, starting_indexes, exit, |v| {
v.compute_rolling_average(starting_indexes.height, window_starts, close, exit)?;
Ok(())
})?;
@@ -59,7 +59,7 @@ impl Vecs {
(&mut self.price_ema_4y, 4 * 365),
] {
let window_starts = blocks.count.start_vec(period);
ema.compute_all(blocks, prices, starting_indexes, exit, |v| {
ema.compute_all(prices, starting_indexes, exit, |v| {
v.compute_rolling_ema(starting_indexes.height, window_starts, close, exit)?;
Ok(())
})?;
@@ -5,7 +5,7 @@ use vecdb::Database;
use super::Vecs;
use crate::{
indexes,
internal::{CentsTimesTenths, ComputedFromHeightPriceWithRatioExtended, Price},
internal::{CentsTimesTenths, ComputedFromHeightPriceWithRatio, Price},
};
impl Vecs {
@@ -16,7 +16,7 @@ impl Vecs {
) -> Result<Self> {
macro_rules! import {
($name:expr) => {
ComputedFromHeightPriceWithRatioExtended::forced_import(
ComputedFromHeightPriceWithRatio::forced_import(
db, $name, version, indexes,
)?
};
@@ -2,42 +2,42 @@ use brk_traversable::Traversable;
use brk_types::Cents;
use vecdb::{Rw, StorageMode};
use crate::internal::{ComputedFromHeightPriceWithRatioExtended, LazyFromHeight, Price};
use crate::internal::{ComputedFromHeightPriceWithRatio, LazyFromHeight, Price};
#[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw> {
pub price_sma_1w: ComputedFromHeightPriceWithRatioExtended<M>,
pub price_sma_8d: ComputedFromHeightPriceWithRatioExtended<M>,
pub price_sma_13d: ComputedFromHeightPriceWithRatioExtended<M>,
pub price_sma_21d: ComputedFromHeightPriceWithRatioExtended<M>,
pub price_sma_1m: ComputedFromHeightPriceWithRatioExtended<M>,
pub price_sma_34d: ComputedFromHeightPriceWithRatioExtended<M>,
pub price_sma_55d: ComputedFromHeightPriceWithRatioExtended<M>,
pub price_sma_89d: ComputedFromHeightPriceWithRatioExtended<M>,
pub price_sma_111d: ComputedFromHeightPriceWithRatioExtended<M>,
pub price_sma_144d: ComputedFromHeightPriceWithRatioExtended<M>,
pub price_sma_200d: ComputedFromHeightPriceWithRatioExtended<M>,
pub price_sma_350d: ComputedFromHeightPriceWithRatioExtended<M>,
pub price_sma_1y: ComputedFromHeightPriceWithRatioExtended<M>,
pub price_sma_2y: ComputedFromHeightPriceWithRatioExtended<M>,
pub price_sma_200w: ComputedFromHeightPriceWithRatioExtended<M>,
pub price_sma_4y: ComputedFromHeightPriceWithRatioExtended<M>,
pub price_sma_1w: ComputedFromHeightPriceWithRatio<M>,
pub price_sma_8d: ComputedFromHeightPriceWithRatio<M>,
pub price_sma_13d: ComputedFromHeightPriceWithRatio<M>,
pub price_sma_21d: ComputedFromHeightPriceWithRatio<M>,
pub price_sma_1m: ComputedFromHeightPriceWithRatio<M>,
pub price_sma_34d: ComputedFromHeightPriceWithRatio<M>,
pub price_sma_55d: ComputedFromHeightPriceWithRatio<M>,
pub price_sma_89d: ComputedFromHeightPriceWithRatio<M>,
pub price_sma_111d: ComputedFromHeightPriceWithRatio<M>,
pub price_sma_144d: ComputedFromHeightPriceWithRatio<M>,
pub price_sma_200d: ComputedFromHeightPriceWithRatio<M>,
pub price_sma_350d: ComputedFromHeightPriceWithRatio<M>,
pub price_sma_1y: ComputedFromHeightPriceWithRatio<M>,
pub price_sma_2y: ComputedFromHeightPriceWithRatio<M>,
pub price_sma_200w: ComputedFromHeightPriceWithRatio<M>,
pub price_sma_4y: ComputedFromHeightPriceWithRatio<M>,
pub price_ema_1w: ComputedFromHeightPriceWithRatioExtended<M>,
pub price_ema_8d: ComputedFromHeightPriceWithRatioExtended<M>,
pub price_ema_12d: ComputedFromHeightPriceWithRatioExtended<M>,
pub price_ema_13d: ComputedFromHeightPriceWithRatioExtended<M>,
pub price_ema_21d: ComputedFromHeightPriceWithRatioExtended<M>,
pub price_ema_26d: ComputedFromHeightPriceWithRatioExtended<M>,
pub price_ema_1m: ComputedFromHeightPriceWithRatioExtended<M>,
pub price_ema_34d: ComputedFromHeightPriceWithRatioExtended<M>,
pub price_ema_55d: ComputedFromHeightPriceWithRatioExtended<M>,
pub price_ema_89d: ComputedFromHeightPriceWithRatioExtended<M>,
pub price_ema_144d: ComputedFromHeightPriceWithRatioExtended<M>,
pub price_ema_200d: ComputedFromHeightPriceWithRatioExtended<M>,
pub price_ema_1y: ComputedFromHeightPriceWithRatioExtended<M>,
pub price_ema_2y: ComputedFromHeightPriceWithRatioExtended<M>,
pub price_ema_200w: ComputedFromHeightPriceWithRatioExtended<M>,
pub price_ema_4y: ComputedFromHeightPriceWithRatioExtended<M>,
pub price_ema_1w: ComputedFromHeightPriceWithRatio<M>,
pub price_ema_8d: ComputedFromHeightPriceWithRatio<M>,
pub price_ema_12d: ComputedFromHeightPriceWithRatio<M>,
pub price_ema_13d: ComputedFromHeightPriceWithRatio<M>,
pub price_ema_21d: ComputedFromHeightPriceWithRatio<M>,
pub price_ema_26d: ComputedFromHeightPriceWithRatio<M>,
pub price_ema_1m: ComputedFromHeightPriceWithRatio<M>,
pub price_ema_34d: ComputedFromHeightPriceWithRatio<M>,
pub price_ema_55d: ComputedFromHeightPriceWithRatio<M>,
pub price_ema_89d: ComputedFromHeightPriceWithRatio<M>,
pub price_ema_144d: ComputedFromHeightPriceWithRatio<M>,
pub price_ema_200d: ComputedFromHeightPriceWithRatio<M>,
pub price_ema_1y: ComputedFromHeightPriceWithRatio<M>,
pub price_ema_2y: ComputedFromHeightPriceWithRatio<M>,
pub price_ema_200w: ComputedFromHeightPriceWithRatio<M>,
pub price_ema_4y: ComputedFromHeightPriceWithRatio<M>,
pub price_sma_200d_x2_4: Price<LazyFromHeight<Cents, Cents>>,
pub price_sma_200d_x0_8: Price<LazyFromHeight<Cents, Cents>>,
@@ -1,5 +1,5 @@
use brk_error::Result;
use brk_types::{BasisPointsSigned32, Dollars, Indexes, StoredF32};
use brk_types::{BasisPointsSigned32, Dollars, Indexes};
use vecdb::Exit;
use super::Vecs;
@@ -54,26 +54,6 @@ impl Vecs {
sd.compute_all(blocks, starting_indexes, exit, _24h_price_return_ratio)?;
}
// Downside returns: min(return, 0)
self.price_downside_24h.compute_transform(
starting_indexes.height,
_24h_price_return_ratio,
|(i, ret, ..)| {
let v = f32::from(ret).min(0.0);
(i, StoredF32::from(v))
},
exit,
)?;
// Downside deviation (SD of downside returns)
for sd in [
&mut self.price_downside_24h_sd_1w,
&mut self.price_downside_24h_sd_1m,
&mut self.price_downside_24h_sd_1y,
] {
sd.compute_all(blocks, starting_indexes, exit, &self.price_downside_24h)?;
}
Ok(())
}
}
@@ -1,6 +1,6 @@
use brk_error::Result;
use brk_types::Version;
use vecdb::{Database, EagerVec, ImportableVec};
use vecdb::Database;
use super::super::lookback::ByLookbackPeriod;
use super::Vecs;
@@ -52,42 +52,12 @@ impl Vecs {
indexes,
)?;
let price_downside_24h = EagerVec::forced_import(db, "price_downside_24h", version)?;
let price_downside_24h_sd_1w = ComputedFromHeightStdDev::forced_import(
db,
"price_downside_24h",
"1w",
7,
version + v1,
indexes,
)?;
let price_downside_24h_sd_1m = ComputedFromHeightStdDev::forced_import(
db,
"price_downside_24h",
"1m",
30,
version + v1,
indexes,
)?;
let price_downside_24h_sd_1y = ComputedFromHeightStdDev::forced_import(
db,
"price_downside_24h",
"1y",
365,
version + v1,
indexes,
)?;
Ok(Self {
price_return,
price_cagr,
price_return_24h_sd_1w,
price_return_24h_sd_1m,
price_return_24h_sd_1y,
price_downside_24h,
price_downside_24h_sd_1w,
price_downside_24h_sd_1m,
price_downside_24h_sd_1y,
})
}
}
@@ -1,6 +1,6 @@
use brk_traversable::Traversable;
use brk_types::{BasisPointsSigned32, Height, StoredF32};
use vecdb::{EagerVec, PcoVec, Rw, StorageMode};
use brk_types::BasisPointsSigned32;
use vecdb::{Rw, StorageMode};
use crate::{
internal::{ComputedFromHeightStdDev, PercentFromHeight},
@@ -18,9 +18,4 @@ pub struct Vecs<M: StorageMode = Rw> {
pub price_return_24h_sd_1m: ComputedFromHeightStdDev<M>,
pub price_return_24h_sd_1y: ComputedFromHeightStdDev<M>,
// Downside returns and deviation (for Sortino ratio)
pub price_downside_24h: M::Stored<EagerVec<PcoVec<Height, StoredF32>>>,
pub price_downside_24h_sd_1w: ComputedFromHeightStdDev<M>,
pub price_downside_24h_sd_1m: ComputedFromHeightStdDev<M>,
pub price_downside_24h_sd_1y: ComputedFromHeightStdDev<M>,
}
@@ -1,98 +0,0 @@
use brk_error::Result;
use brk_types::{Height, StoredF32};
use vecdb::{EagerVec, Exit, PcoVec, ReadableVec};
use super::super::returns;
use super::Vecs;
impl Vecs {
pub(crate) fn compute(
&mut self,
returns: &returns::Vecs,
starting_indexes_height: Height,
exit: &Exit,
) -> Result<()> {
// Sharpe ratios: returns / volatility
for (out, ret, vol) in [
(
&mut self.price_sharpe_1w,
&returns.price_return._1w.ratio.height,
&self.price_volatility_1w.height,
),
(
&mut self.price_sharpe_1m,
&returns.price_return._1m.ratio.height,
&self.price_volatility_1m.height,
),
(
&mut self.price_sharpe_1y,
&returns.price_return._1y.ratio.height,
&self.price_volatility_1y.height,
),
] {
compute_divided(
&mut out.height,
starting_indexes_height,
ret,
vol,
1.0,
exit,
)?;
}
// Sortino ratios: returns / downside volatility (sd * sqrt(days))
for (out, ret, sd, sqrt_days) in [
(
&mut self.price_sortino_1w,
&returns.price_return._1w.ratio.height,
&returns.price_downside_24h_sd_1w.sd.height,
7.0_f32.sqrt(),
),
(
&mut self.price_sortino_1m,
&returns.price_return._1m.ratio.height,
&returns.price_downside_24h_sd_1m.sd.height,
30.0_f32.sqrt(),
),
(
&mut self.price_sortino_1y,
&returns.price_return._1y.ratio.height,
&returns.price_downside_24h_sd_1y.sd.height,
365.0_f32.sqrt(),
),
] {
compute_divided(
&mut out.height,
starting_indexes_height,
ret,
sd,
sqrt_days,
exit,
)?;
}
Ok(())
}
}
fn compute_divided(
out: &mut EagerVec<PcoVec<Height, StoredF32>>,
starting_indexes_height: Height,
ret: &impl ReadableVec<Height, StoredF32>,
divisor: &impl ReadableVec<Height, StoredF32>,
divisor_scale: f32,
exit: &Exit,
) -> Result<()> {
out.compute_transform2(
starting_indexes_height,
ret,
divisor,
|(h, ret, div, ..)| {
let denom = (*div) * divisor_scale;
let ratio = if denom == 0.0 { 0.0 } else { (*ret) / denom };
(h, StoredF32::from(ratio))
},
exit,
)?;
Ok(())
}
@@ -1,19 +1,13 @@
use brk_error::Result;
use brk_types::Version;
use vecdb::{Database, ReadableCloneableVec};
use vecdb::ReadableCloneableVec;
use super::super::returns;
use super::Vecs;
use crate::indexes;
use crate::internal::{ComputedFromHeight, Days7, Days30, Days365, LazyFromHeight, TimesSqrt};
use crate::internal::{Days30, Days365, Days7, LazyFromHeight, TimesSqrt};
impl Vecs {
pub(crate) fn forced_import(
db: &Database,
version: Version,
indexes: &indexes::Vecs,
returns: &returns::Vecs,
) -> Result<Self> {
pub(crate) fn forced_import(version: Version, returns: &returns::Vecs) -> Result<Self> {
let v2 = Version::TWO;
let price_volatility_1w = LazyFromHeight::from_computed::<TimesSqrt<Days7>>(
@@ -49,30 +43,10 @@ impl Vecs {
&returns.price_return_24h_sd_1y.sd,
);
let price_sharpe_1w =
ComputedFromHeight::forced_import(db, "price_sharpe_1w", version + v2, indexes)?;
let price_sharpe_1m =
ComputedFromHeight::forced_import(db, "price_sharpe_1m", version + v2, indexes)?;
let price_sharpe_1y =
ComputedFromHeight::forced_import(db, "price_sharpe_1y", version + v2, indexes)?;
let price_sortino_1w =
ComputedFromHeight::forced_import(db, "price_sortino_1w", version + v2, indexes)?;
let price_sortino_1m =
ComputedFromHeight::forced_import(db, "price_sortino_1m", version + v2, indexes)?;
let price_sortino_1y =
ComputedFromHeight::forced_import(db, "price_sortino_1y", version + v2, indexes)?;
Ok(Self {
price_volatility_1w,
price_volatility_1m,
price_volatility_1y,
price_sharpe_1w,
price_sharpe_1m,
price_sharpe_1y,
price_sortino_1w,
price_sortino_1m,
price_sortino_1y,
})
}
}
@@ -1,4 +1,3 @@
mod compute;
mod import;
mod vecs;
@@ -1,20 +1,11 @@
use brk_traversable::Traversable;
use vecdb::{Rw, StorageMode};
use crate::internal::{ComputedFromHeight, LazyFromHeight};
use crate::internal::LazyFromHeight;
use brk_types::StoredF32;
#[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw> {
#[derive(Clone, Traversable)]
pub struct Vecs {
pub price_volatility_1w: LazyFromHeight<StoredF32>,
pub price_volatility_1m: LazyFromHeight<StoredF32>,
pub price_volatility_1y: LazyFromHeight<StoredF32>,
pub price_sharpe_1w: ComputedFromHeight<StoredF32, M>,
pub price_sharpe_1m: ComputedFromHeight<StoredF32, M>,
pub price_sharpe_1y: ComputedFromHeight<StoredF32, M>,
pub price_sortino_1w: ComputedFromHeight<StoredF32, M>,
pub price_sortino_1m: ComputedFromHeight<StoredF32, M>,
pub price_sortino_1y: ComputedFromHeight<StoredF32, M>,
}
+9 -100
View File
@@ -2,14 +2,14 @@ use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{BasisPoints16, Height, Indexes, PoolSlug, StoredU32};
use vecdb::{
AnyVec, BinaryTransform, Database, Exit, ReadableVec, Rw, StorageMode, VecIndex, Version,
BinaryTransform, Database, Exit, ReadableVec, Rw, StorageMode, Version,
};
use crate::{
blocks, indexes,
internal::{
ComputedFromHeight, ComputedFromHeightCumulativeSum, MaskSats, PercentFromHeight,
PercentRollingWindows, RatioU32Bp16, RollingWindows, ValueFromHeightCumulativeSum,
ComputedFromHeightCumulativeSum, MaskSats, PercentFromHeight,
PercentRollingWindows, RatioU32Bp16, ValueFromHeightCumulativeSum,
},
mining, prices,
};
@@ -19,13 +19,9 @@ pub struct Vecs<M: StorageMode = Rw> {
slug: PoolSlug,
pub blocks_mined: ComputedFromHeightCumulativeSum<StoredU32, M>,
pub blocks_mined_sum: RollingWindows<StoredU32, M>,
pub subsidy: ValueFromHeightCumulativeSum<M>,
pub fee: ValueFromHeightCumulativeSum<M>,
pub coinbase: ValueFromHeightCumulativeSum<M>,
pub rewards: ValueFromHeightCumulativeSum<M>,
pub dominance: PercentFromHeight<BasisPoints16, M>,
pub dominance_rolling: PercentRollingWindows<BasisPoints16, M>,
pub blocks_since_last_mined: ComputedFromHeight<StoredU32, M>,
}
impl Vecs {
@@ -45,17 +41,8 @@ impl Vecs {
indexes,
)?;
let blocks_mined_sum =
RollingWindows::forced_import(db, &suffix("blocks_mined_sum"), version, indexes)?;
let subsidy =
ValueFromHeightCumulativeSum::forced_import(db, &suffix("subsidy"), version, indexes)?;
let fee =
ValueFromHeightCumulativeSum::forced_import(db, &suffix("fee"), version, indexes)?;
let coinbase =
ValueFromHeightCumulativeSum::forced_import(db, &suffix("coinbase"), version, indexes)?;
let rewards =
ValueFromHeightCumulativeSum::forced_import(db, &suffix("rewards"), version, indexes)?;
let dominance =
PercentFromHeight::forced_import(db, &suffix("dominance"), version, indexes)?;
@@ -67,16 +54,7 @@ impl Vecs {
dominance_rolling,
slug,
blocks_mined,
blocks_mined_sum,
coinbase,
subsidy,
fee,
blocks_since_last_mined: ComputedFromHeight::forced_import(
db,
&suffix("blocks_since_last_mined"),
version,
indexes,
)?,
rewards,
})
}
@@ -112,13 +90,6 @@ impl Vecs {
Ok(())
})?;
self.blocks_mined_sum.compute_rolling_sum(
starting_indexes.height,
&window_starts,
&self.blocks_mined.height,
exit,
)?;
self.dominance
.compute_binary::<StoredU32, StoredU32, RatioU32Bp16>(
starting_indexes.height,
@@ -131,7 +102,7 @@ impl Vecs {
.dominance_rolling
.as_mut_array()
.into_iter()
.zip(self.blocks_mined_sum.as_array())
.zip(self.blocks_mined.sum.as_array())
.zip(blocks.count.block_count_sum.as_array())
{
dom.compute_binary::<StoredU32, StoredU32, RatioU32Bp16>(
@@ -142,39 +113,7 @@ impl Vecs {
)?;
}
self.subsidy.compute(
starting_indexes.height,
&window_starts,
prices,
exit,
|vec| {
Ok(vec.compute_transform2(
starting_indexes.height,
&self.blocks_mined.height,
&mining.rewards.subsidy.base.sats.height,
|(h, mask, val, ..)| (h, MaskSats::apply(mask, val)),
exit,
)?)
},
)?;
self.fee.compute(
starting_indexes.height,
&window_starts,
prices,
exit,
|vec| {
Ok(vec.compute_transform2(
starting_indexes.height,
&self.blocks_mined.height,
&mining.rewards.fees.base.sats.height,
|(h, mask, val, ..)| (h, MaskSats::apply(mask, val)),
exit,
)?)
},
)?;
self.coinbase.compute(
self.rewards.compute(
starting_indexes.height,
&window_starts,
prices,
@@ -190,36 +129,6 @@ impl Vecs {
},
)?;
{
let resume_from = self
.blocks_since_last_mined
.height
.len()
.min(starting_indexes.height.to_usize());
let mut prev = if resume_from > 0 {
self.blocks_since_last_mined
.height
.collect_one_at(resume_from - 1)
.unwrap()
} else {
StoredU32::ZERO
};
self.blocks_since_last_mined.height.compute_transform(
starting_indexes.height,
&self.blocks_mined.height,
|(h, mined, ..)| {
let blocks = if mined.is_zero() {
prev + StoredU32::ONE
} else {
StoredU32::ZERO
};
prev = blocks;
(h, blocks)
},
exit,
)?;
}
Ok(())
}
}
+6
View File
@@ -54,12 +54,18 @@ impl Age {
/// Calculate satblocks destroyed for given supply
#[inline]
pub fn satblocks_destroyed(&self, supply: Sats) -> Sats {
if self.blocks == 0 {
return Sats::ZERO;
}
Sats::from(u64::from(supply) * self.blocks as u64)
}
/// Calculate satdays destroyed for given supply
#[inline]
pub fn satdays_destroyed(&self, supply: Sats) -> Sats {
if self.blocks == 0 {
return Sats::ZERO;
}
Sats::from((u64::from(supply) as f64 * self.days).floor() as u64)
}
}
+2 -5
View File
@@ -78,11 +78,8 @@ impl From<BasisPoints32> for u32 {
impl From<f64> for BasisPoints32 {
#[inline]
fn from(value: f64) -> Self {
debug_assert!(
value >= 0.0 && value <= u32::MAX as f64 / 10000.0,
"f64 out of BasisPoints32 range: {value}"
);
Self((value * 10000.0).round() as u32)
let scaled = (value * 10000.0).round().clamp(0.0, u32::MAX as f64);
Self(scaled as u32)
}
}
@@ -83,11 +83,8 @@ impl From<BasisPointsSigned32> for i32 {
impl From<f64> for BasisPointsSigned32 {
#[inline]
fn from(value: f64) -> Self {
debug_assert!(
value >= i32::MIN as f64 / 10000.0 && value <= i32::MAX as f64 / 10000.0,
"f64 out of BasisPointsSigned32 range: {value}"
);
Self((value * 10000.0).round() as i32)
let scaled = (value * 10000.0).round().clamp(i32::MIN as f64, i32::MAX as f64);
Self(scaled as i32)
}
}
File diff suppressed because it is too large Load Diff
+267 -317
View File
@@ -2334,33 +2334,6 @@ class _0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdSmaZscorePattern:
self.sma: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'sma_4y'))
self.zscore: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'zscore_4y'))
class BpsPriceRatioPattern:
"""Pattern struct for repeated tree structure."""
def __init__(self, client: BrkClientBase, acc: str):
"""Create pattern node with accumulated metric name."""
self.bps: MetricPattern1[BasisPoints32] = MetricPattern1(client, _m(acc, 'ratio_bps'))
self.price: CentsSatsUsdPattern = CentsSatsUsdPattern(client, acc)
self.ratio: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'ratio'))
self.ratio_pct1: BpsRatioPattern = BpsRatioPattern(client, _m(acc, 'ratio_pct1'))
self.ratio_pct1_price: CentsSatsUsdPattern = CentsSatsUsdPattern(client, _m(acc, 'ratio_pct1'))
self.ratio_pct2: BpsRatioPattern = BpsRatioPattern(client, _m(acc, 'ratio_pct2'))
self.ratio_pct2_price: CentsSatsUsdPattern = CentsSatsUsdPattern(client, _m(acc, 'ratio_pct2'))
self.ratio_pct5: BpsRatioPattern = BpsRatioPattern(client, _m(acc, 'ratio_pct5'))
self.ratio_pct5_price: CentsSatsUsdPattern = CentsSatsUsdPattern(client, _m(acc, 'ratio_pct5'))
self.ratio_pct95: BpsRatioPattern = BpsRatioPattern(client, _m(acc, 'ratio_pct95'))
self.ratio_pct95_price: CentsSatsUsdPattern = CentsSatsUsdPattern(client, _m(acc, 'ratio_pct95'))
self.ratio_pct98: BpsRatioPattern = BpsRatioPattern(client, _m(acc, 'ratio_pct98'))
self.ratio_pct98_price: CentsSatsUsdPattern = CentsSatsUsdPattern(client, _m(acc, 'ratio_pct98'))
self.ratio_pct99: BpsRatioPattern = BpsRatioPattern(client, _m(acc, 'ratio_pct99'))
self.ratio_pct99_price: CentsSatsUsdPattern = CentsSatsUsdPattern(client, _m(acc, 'ratio_pct99'))
self.ratio_sd: _0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdSmaZscorePattern = _0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdSmaZscorePattern(client, _m(acc, 'ratio'))
self.ratio_sd_1y: _0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdSmaZscorePattern = _0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdSmaZscorePattern(client, _m(acc, 'ratio'))
self.ratio_sd_2y: _0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdSmaZscorePattern = _0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdSmaZscorePattern(client, _m(acc, 'ratio'))
self.ratio_sd_4y: _0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdSmaZscorePattern = _0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdSmaZscorePattern(client, _m(acc, 'ratio'))
self.ratio_sma_1m: BpsRatioPattern = BpsRatioPattern(client, _m(acc, 'ratio_sma_1m'))
self.ratio_sma_1w: BpsRatioPattern = BpsRatioPattern(client, _m(acc, 'ratio_sma_1w'))
class BpsRatioPattern2:
"""Pattern struct for repeated tree structure."""
@@ -2593,7 +2566,7 @@ class ActivityAddrCostOutputsRealizedRelativeSupplyUnrealizedPattern:
def __init__(self, client: BrkClientBase, acc: str):
"""Create pattern node with accumulated metric name."""
self.activity: CoinblocksCoindaysSatblocksSatdaysSentPattern = CoinblocksCoindaysSatblocksSatdaysSentPattern(client, acc)
self.activity: CoinblocksCoindaysSentPattern = CoinblocksCoindaysSentPattern(client, acc)
self.addr_count: MetricPattern1[StoredU64] = MetricPattern1(client, _m(acc, 'addr_count'))
self.addr_count_change_1m: MetricPattern1[StoredF64] = MetricPattern1(client, _m(acc, 'addr_count_change_1m'))
self.cost_basis: MaxMinPattern = MaxMinPattern(client, _m(acc, 'cost_basis'))
@@ -2677,20 +2650,6 @@ class _1m1w1y24hBtcCentsSatsUsdPattern:
self.sats: MetricPattern18[Sats] = MetricPattern18(client, acc)
self.usd: MetricPattern18[Dollars] = MetricPattern18(client, _m(acc, 'usd'))
class BlocksCoinbaseDominanceFeeSubsidyPattern:
"""Pattern struct for repeated tree structure."""
def __init__(self, client: BrkClientBase, acc: str):
"""Create pattern node with accumulated metric name."""
self.blocks_mined: CumulativeHeightSumPattern[StoredU32] = CumulativeHeightSumPattern(client, _m(acc, 'blocks_mined'))
self.blocks_mined_sum: _1m1w1y24hPattern[StoredU32] = _1m1w1y24hPattern(client, _m(acc, 'blocks_mined_sum'))
self.blocks_since_last_mined: MetricPattern1[StoredU32] = MetricPattern1(client, _m(acc, 'blocks_since_last_mined'))
self.coinbase: BaseCumulativeSumPattern = BaseCumulativeSumPattern(client, _m(acc, 'coinbase'))
self.dominance: BpsPercentRatioPattern = BpsPercentRatioPattern(client, _m(acc, 'dominance'))
self.dominance_rolling: _1m1w1y24hPattern2 = _1m1w1y24hPattern2(client, _m(acc, 'dominance'))
self.fee: BaseCumulativeSumPattern = BaseCumulativeSumPattern(client, _m(acc, 'fee'))
self.subsidy: BaseCumulativeSumPattern = BaseCumulativeSumPattern(client, _m(acc, 'subsidy'))
class AverageMaxMedianMinPct10Pct25Pct75Pct90Pattern(Generic[T]):
"""Pattern struct for repeated tree structure."""
@@ -2723,8 +2682,8 @@ class ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern:
def __init__(self, client: BrkClientBase, acc: str):
"""Create pattern node with accumulated metric name."""
self.activity: CoinblocksCoindaysSatblocksSatdaysSentPattern = CoinblocksCoindaysSatblocksSatdaysSentPattern(client, acc)
self.cost_basis: InvestedMaxMinPercentilesSpotPattern = InvestedMaxMinPercentilesSpotPattern(client, acc)
self.activity: CoinblocksCoindaysSentPattern = CoinblocksCoindaysSentPattern(client, acc)
self.cost_basis: InvestedMaxMinPercentilesPattern = InvestedMaxMinPercentilesPattern(client, acc)
self.outputs: UtxoPattern = UtxoPattern(client, _m(acc, 'utxo_count'))
self.realized: CapCapitulationGrossInvestorLossLowerMvrvNegNetPeakProfitRealizedSellSentSoprUpperValuePattern2 = CapCapitulationGrossInvestorLossLowerMvrvNegNetPeakProfitRealizedSellSentSoprUpperValuePattern2(client, acc)
self.relative: InvestedNegNetNuplSupplyUnrealizedPattern2 = InvestedNegNetNuplSupplyUnrealizedPattern2(client, acc)
@@ -2736,7 +2695,7 @@ class ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern4:
def __init__(self, client: BrkClientBase, acc: str):
"""Create pattern node with accumulated metric name."""
self.activity: CoinblocksCoindaysSatblocksSatdaysSentPattern = CoinblocksCoindaysSatblocksSatdaysSentPattern(client, acc)
self.activity: CoinblocksCoindaysSentPattern = CoinblocksCoindaysSentPattern(client, acc)
self.cost_basis: MaxMinPattern = MaxMinPattern(client, _m(acc, 'cost_basis'))
self.outputs: UtxoPattern = UtxoPattern(client, _m(acc, 'utxo_count'))
self.realized: AdjustedCapCapitulationGrossInvestorLossLowerMvrvNegNetPeakProfitRealizedSellSentSoprUpperValuePattern2 = AdjustedCapCapitulationGrossInvestorLossLowerMvrvNegNetPeakProfitRealizedSellSentSoprUpperValuePattern2(client, acc)
@@ -2749,7 +2708,7 @@ class ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3:
def __init__(self, client: BrkClientBase, acc: str):
"""Create pattern node with accumulated metric name."""
self.activity: CoinblocksCoindaysSatblocksSatdaysSentPattern = CoinblocksCoindaysSatblocksSatdaysSentPattern(client, acc)
self.activity: CoinblocksCoindaysSentPattern = CoinblocksCoindaysSentPattern(client, acc)
self.cost_basis: MaxMinPattern = MaxMinPattern(client, _m(acc, 'cost_basis'))
self.outputs: UtxoPattern = UtxoPattern(client, _m(acc, 'utxo_count'))
self.realized: CapCapitulationGrossInvestorLossLowerMvrvNegNetPeakProfitRealizedSellSentSoprUpperValuePattern = CapCapitulationGrossInvestorLossLowerMvrvNegNetPeakProfitRealizedSellSentSoprUpperValuePattern(client, acc)
@@ -2781,30 +2740,6 @@ class BalanceBothReactivatedReceivingSendingPattern:
self.receiving: AverageHeightMaxMedianMinPct10Pct25Pct75Pct90Pattern[StoredU32] = AverageHeightMaxMedianMinPct10Pct25Pct75Pct90Pattern(client, _m(acc, 'receiving'))
self.sending: AverageHeightMaxMedianMinPct10Pct25Pct75Pct90Pattern[StoredU32] = AverageHeightMaxMedianMinPct10Pct25Pct75Pct90Pattern(client, _m(acc, 'sending'))
class CoinblocksCoindaysSatblocksSatdaysSentPattern:
"""Pattern struct for repeated tree structure."""
def __init__(self, client: BrkClientBase, acc: str):
"""Create pattern node with accumulated metric name."""
self.coinblocks_destroyed: CumulativeHeightSumPattern[StoredF64] = CumulativeHeightSumPattern(client, _m(acc, 'coinblocks_destroyed'))
self.coindays_destroyed: CumulativeHeightSumPattern[StoredF64] = CumulativeHeightSumPattern(client, _m(acc, 'coindays_destroyed'))
self.satblocks_destroyed: MetricPattern18[Sats] = MetricPattern18(client, _m(acc, 'satblocks_destroyed'))
self.satdays_destroyed: MetricPattern18[Sats] = MetricPattern18(client, _m(acc, 'satdays_destroyed'))
self.sent: BaseCumulativePattern = BaseCumulativePattern(client, _m(acc, 'sent'))
self.sent_ema: _2wPattern = _2wPattern(client, _m(acc, 'sent_ema_2w'))
class InvestedMaxMinPercentilesSpotPattern:
"""Pattern struct for repeated tree structure."""
def __init__(self, client: BrkClientBase, acc: str):
"""Create pattern node with accumulated metric name."""
self.invested_capital: Pct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern = Pct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern(client, _m(acc, 'invested_capital'))
self.max: CentsSatsUsdPattern = CentsSatsUsdPattern(client, _m(acc, 'cost_basis_max'))
self.min: CentsSatsUsdPattern = CentsSatsUsdPattern(client, _m(acc, 'cost_basis_min'))
self.percentiles: Pct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern = Pct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern(client, _m(acc, 'cost_basis'))
self.spot_cost_basis_percentile: BpsPercentRatioPattern = BpsPercentRatioPattern(client, _m(acc, 'spot_cost_basis_percentile'))
self.spot_invested_capital_percentile: BpsPercentRatioPattern = BpsPercentRatioPattern(client, _m(acc, 'spot_invested_capital_percentile'))
class EmaHistogramLineSignalPattern:
"""Pattern struct for repeated tree structure."""
@@ -2836,6 +2771,16 @@ class _1m1w1y24hPattern5:
self._1y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, _m(acc, '1y'))
self._24h: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, _m(acc, '24h'))
class BlocksDominanceRewardsPattern:
"""Pattern struct for repeated tree structure."""
def __init__(self, client: BrkClientBase, acc: str):
"""Create pattern node with accumulated metric name."""
self.blocks_mined: CumulativeHeightSumPattern[StoredU32] = CumulativeHeightSumPattern(client, _m(acc, 'blocks_mined'))
self.dominance: BpsPercentRatioPattern = BpsPercentRatioPattern(client, _m(acc, 'dominance'))
self.dominance_rolling: _1m1w1y24hPattern2 = _1m1w1y24hPattern2(client, _m(acc, 'dominance'))
self.rewards: BaseCumulativeSumPattern = BaseCumulativeSumPattern(client, _m(acc, 'rewards'))
class BtcCentsSatsUsdPattern:
"""Pattern struct for repeated tree structure."""
@@ -2846,6 +2791,26 @@ class BtcCentsSatsUsdPattern:
self.sats: MetricPattern1[Sats] = MetricPattern1(client, acc)
self.usd: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'usd'))
class CoinblocksCoindaysSentPattern:
"""Pattern struct for repeated tree structure."""
def __init__(self, client: BrkClientBase, acc: str):
"""Create pattern node with accumulated metric name."""
self.coinblocks_destroyed: CumulativeHeightSumPattern[StoredF64] = CumulativeHeightSumPattern(client, _m(acc, 'coinblocks_destroyed'))
self.coindays_destroyed: CumulativeHeightSumPattern[StoredF64] = CumulativeHeightSumPattern(client, _m(acc, 'coindays_destroyed'))
self.sent: BaseCumulativePattern = BaseCumulativePattern(client, _m(acc, 'sent'))
self.sent_ema: _2wPattern = _2wPattern(client, _m(acc, 'sent_ema_2w'))
class InvestedMaxMinPercentilesPattern:
"""Pattern struct for repeated tree structure."""
def __init__(self, client: BrkClientBase, acc: str):
"""Create pattern node with accumulated metric name."""
self.invested_capital: Pct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern = Pct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern(client, _m(acc, 'invested_capital'))
self.max: CentsSatsUsdPattern = CentsSatsUsdPattern(client, _m(acc, 'cost_basis_max'))
self.min: CentsSatsUsdPattern = CentsSatsUsdPattern(client, _m(acc, 'cost_basis_min'))
self.percentiles: Pct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern = Pct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern(client, _m(acc, 'cost_basis'))
class _1m1w1y24hPattern(Generic[T]):
"""Pattern struct for repeated tree structure."""
@@ -2883,6 +2848,15 @@ class BpsPercentRatioPattern:
self.percent: MetricPattern1[StoredF32] = MetricPattern1(client, acc)
self.ratio: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'ratio'))
class BpsPriceRatioPattern:
"""Pattern struct for repeated tree structure."""
def __init__(self, client: BrkClientBase, acc: str):
"""Create pattern node with accumulated metric name."""
self.bps: MetricPattern1[BasisPoints32] = MetricPattern1(client, _m(acc, 'ratio_bps'))
self.price: CentsSatsUsdPattern = CentsSatsUsdPattern(client, acc)
self.ratio: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'ratio'))
class CentsSatsUsdPattern2:
"""Pattern struct for repeated tree structure."""
@@ -3797,20 +3771,6 @@ class MetricsTree_Market_Returns_PriceReturn24hSd1m:
self.sma: MetricPattern1[StoredF32] = MetricPattern1(client, 'price_return_24h_sma_1m')
self.sd: MetricPattern1[StoredF32] = MetricPattern1(client, 'price_return_24h_sd_1m')
class MetricsTree_Market_Returns_PriceDownside24hSd1w:
"""Metrics tree node."""
def __init__(self, client: BrkClientBase, base_path: str = ''):
self.sma: MetricPattern1[StoredF32] = MetricPattern1(client, 'price_downside_24h_sma_1w')
self.sd: MetricPattern1[StoredF32] = MetricPattern1(client, 'price_downside_24h_sd_1w')
class MetricsTree_Market_Returns_PriceDownside24hSd1m:
"""Metrics tree node."""
def __init__(self, client: BrkClientBase, base_path: str = ''):
self.sma: MetricPattern1[StoredF32] = MetricPattern1(client, 'price_downside_24h_sma_1m')
self.sd: MetricPattern1[StoredF32] = MetricPattern1(client, 'price_downside_24h_sd_1m')
class MetricsTree_Market_Returns:
"""Metrics tree node."""
@@ -3820,10 +3780,6 @@ class MetricsTree_Market_Returns:
self.price_return_24h_sd_1w: MetricsTree_Market_Returns_PriceReturn24hSd1w = MetricsTree_Market_Returns_PriceReturn24hSd1w(client)
self.price_return_24h_sd_1m: MetricsTree_Market_Returns_PriceReturn24hSd1m = MetricsTree_Market_Returns_PriceReturn24hSd1m(client)
self.price_return_24h_sd_1y: SdSmaPattern = SdSmaPattern(client, 'price_return_24h')
self.price_downside_24h: MetricPattern18[StoredF32] = MetricPattern18(client, 'price_downside_24h')
self.price_downside_24h_sd_1w: MetricsTree_Market_Returns_PriceDownside24hSd1w = MetricsTree_Market_Returns_PriceDownside24hSd1w(client)
self.price_downside_24h_sd_1m: MetricsTree_Market_Returns_PriceDownside24hSd1m = MetricsTree_Market_Returns_PriceDownside24hSd1m(client)
self.price_downside_24h_sd_1y: SdSmaPattern = SdSmaPattern(client, 'price_downside_24h')
class MetricsTree_Market_Volatility:
"""Metrics tree node."""
@@ -3832,12 +3788,6 @@ class MetricsTree_Market_Volatility:
self.price_volatility_1w: MetricPattern1[StoredF32] = MetricPattern1(client, 'price_volatility_1w')
self.price_volatility_1m: MetricPattern1[StoredF32] = MetricPattern1(client, 'price_volatility_1m')
self.price_volatility_1y: MetricPattern1[StoredF32] = MetricPattern1(client, 'price_volatility_1y')
self.price_sharpe_1w: MetricPattern1[StoredF32] = MetricPattern1(client, 'price_sharpe_1w')
self.price_sharpe_1m: MetricPattern1[StoredF32] = MetricPattern1(client, 'price_sharpe_1m')
self.price_sharpe_1y: MetricPattern1[StoredF32] = MetricPattern1(client, 'price_sharpe_1y')
self.price_sortino_1w: MetricPattern1[StoredF32] = MetricPattern1(client, 'price_sortino_1w')
self.price_sortino_1m: MetricPattern1[StoredF32] = MetricPattern1(client, 'price_sortino_1m')
self.price_sortino_1y: MetricPattern1[StoredF32] = MetricPattern1(client, 'price_sortino_1y')
class MetricsTree_Market_Range:
"""Metrics tree node."""
@@ -4101,168 +4051,168 @@ class MetricsTree_Pools_Vecs:
"""Metrics tree node."""
def __init__(self, client: BrkClientBase, base_path: str = ''):
self.unknown: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'unknown')
self.blockfills: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'blockfills')
self.ultimuspool: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'ultimuspool')
self.terrapool: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'terrapool')
self.luxor: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'luxor')
self.onethash: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'onethash')
self.btccom: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'btccom')
self.bitfarms: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'bitfarms')
self.huobipool: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'huobipool')
self.wayicn: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'wayicn')
self.canoepool: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'canoepool')
self.btctop: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'btctop')
self.bitcoincom: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'bitcoincom')
self.pool175btc: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'pool175btc')
self.gbminers: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'gbminers')
self.axbt: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'axbt')
self.asicminer: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'asicminer')
self.bitminter: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'bitminter')
self.bitcoinrussia: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'bitcoinrussia')
self.btcserv: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'btcserv')
self.simplecoinus: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'simplecoinus')
self.btcguild: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'btcguild')
self.eligius: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'eligius')
self.ozcoin: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'ozcoin')
self.eclipsemc: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'eclipsemc')
self.maxbtc: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'maxbtc')
self.triplemining: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'triplemining')
self.coinlab: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'coinlab')
self.pool50btc: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'pool50btc')
self.ghashio: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'ghashio')
self.stminingcorp: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'stminingcorp')
self.bitparking: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'bitparking')
self.mmpool: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'mmpool')
self.polmine: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'polmine')
self.kncminer: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'kncminer')
self.bitalo: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'bitalo')
self.f2pool: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'f2pool')
self.hhtt: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'hhtt')
self.megabigpower: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'megabigpower')
self.mtred: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'mtred')
self.nmcbit: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'nmcbit')
self.yourbtcnet: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'yourbtcnet')
self.givemecoins: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'givemecoins')
self.braiinspool: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'braiinspool')
self.antpool: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'antpool')
self.multicoinco: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'multicoinco')
self.bcpoolio: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'bcpoolio')
self.cointerra: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'cointerra')
self.kanopool: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'kanopool')
self.solock: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'solock')
self.ckpool: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'ckpool')
self.nicehash: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'nicehash')
self.bitclub: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'bitclub')
self.bitcoinaffiliatenetwork: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'bitcoinaffiliatenetwork')
self.btcc: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'btcc')
self.bwpool: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'bwpool')
self.exxbw: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'exxbw')
self.bitsolo: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'bitsolo')
self.bitfury: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'bitfury')
self.twentyoneinc: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'twentyoneinc')
self.digitalbtc: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'digitalbtc')
self.eightbaochi: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'eightbaochi')
self.mybtccoinpool: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'mybtccoinpool')
self.tbdice: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'tbdice')
self.hashpool: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'hashpool')
self.nexious: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'nexious')
self.bravomining: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'bravomining')
self.hotpool: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'hotpool')
self.okexpool: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'okexpool')
self.bcmonster: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'bcmonster')
self.onehash: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'onehash')
self.bixin: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'bixin')
self.tatmaspool: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'tatmaspool')
self.viabtc: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'viabtc')
self.connectbtc: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'connectbtc')
self.batpool: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'batpool')
self.waterhole: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'waterhole')
self.dcexploration: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'dcexploration')
self.dcex: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'dcex')
self.btpool: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'btpool')
self.fiftyeightcoin: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'fiftyeightcoin')
self.bitcoinindia: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'bitcoinindia')
self.shawnp0wers: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'shawnp0wers')
self.phashio: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'phashio')
self.rigpool: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'rigpool')
self.haozhuzhu: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'haozhuzhu')
self.sevenpool: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'sevenpool')
self.miningkings: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'miningkings')
self.hashbx: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'hashbx')
self.dpool: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'dpool')
self.rawpool: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'rawpool')
self.haominer: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'haominer')
self.helix: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'helix')
self.bitcoinukraine: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'bitcoinukraine')
self.poolin: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'poolin')
self.secretsuperstar: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'secretsuperstar')
self.tigerpoolnet: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'tigerpoolnet')
self.sigmapoolcom: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'sigmapoolcom')
self.okpooltop: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'okpooltop')
self.hummerpool: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'hummerpool')
self.tangpool: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'tangpool')
self.bytepool: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'bytepool')
self.spiderpool: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'spiderpool')
self.novablock: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'novablock')
self.miningcity: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'miningcity')
self.binancepool: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'binancepool')
self.minerium: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'minerium')
self.lubiancom: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'lubiancom')
self.okkong: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'okkong')
self.aaopool: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'aaopool')
self.emcdpool: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'emcdpool')
self.foundryusa: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'foundryusa')
self.sbicrypto: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'sbicrypto')
self.arkpool: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'arkpool')
self.purebtccom: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'purebtccom')
self.marapool: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'marapool')
self.kucoinpool: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'kucoinpool')
self.entrustcharitypool: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'entrustcharitypool')
self.okminer: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'okminer')
self.titan: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'titan')
self.pegapool: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'pegapool')
self.btcnuggets: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'btcnuggets')
self.cloudhashing: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'cloudhashing')
self.digitalxmintsy: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'digitalxmintsy')
self.telco214: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'telco214')
self.btcpoolparty: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'btcpoolparty')
self.multipool: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'multipool')
self.transactioncoinmining: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'transactioncoinmining')
self.btcdig: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'btcdig')
self.trickysbtcpool: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'trickysbtcpool')
self.btcmp: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'btcmp')
self.eobot: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'eobot')
self.unomp: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'unomp')
self.patels: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'patels')
self.gogreenlight: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'gogreenlight')
self.bitcoinindiapool: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'bitcoinindiapool')
self.ekanembtc: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'ekanembtc')
self.canoe: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'canoe')
self.tiger: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'tiger')
self.onem1x: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'onem1x')
self.zulupool: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'zulupool')
self.secpool: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'secpool')
self.ocean: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'ocean')
self.whitepool: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'whitepool')
self.wiz: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'wiz')
self.wk057: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'wk057')
self.futurebitapollosolo: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'futurebitapollosolo')
self.carbonnegative: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'carbonnegative')
self.portlandhodl: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'portlandhodl')
self.phoenix: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'phoenix')
self.neopool: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'neopool')
self.maxipool: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'maxipool')
self.bitfufupool: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'bitfufupool')
self.gdpool: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'gdpool')
self.miningdutch: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'miningdutch')
self.publicpool: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'publicpool')
self.miningsquared: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'miningsquared')
self.innopolistech: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'innopolistech')
self.btclab: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'btclab')
self.parasite: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'parasite')
self.redrockpool: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'redrockpool')
self.est3lar: BlocksCoinbaseDominanceFeeSubsidyPattern = BlocksCoinbaseDominanceFeeSubsidyPattern(client, 'est3lar')
self.unknown: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'unknown')
self.blockfills: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'blockfills')
self.ultimuspool: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'ultimuspool')
self.terrapool: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'terrapool')
self.luxor: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'luxor')
self.onethash: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'onethash')
self.btccom: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'btccom')
self.bitfarms: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'bitfarms')
self.huobipool: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'huobipool')
self.wayicn: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'wayicn')
self.canoepool: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'canoepool')
self.btctop: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'btctop')
self.bitcoincom: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'bitcoincom')
self.pool175btc: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'pool175btc')
self.gbminers: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'gbminers')
self.axbt: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'axbt')
self.asicminer: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'asicminer')
self.bitminter: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'bitminter')
self.bitcoinrussia: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'bitcoinrussia')
self.btcserv: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'btcserv')
self.simplecoinus: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'simplecoinus')
self.btcguild: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'btcguild')
self.eligius: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'eligius')
self.ozcoin: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'ozcoin')
self.eclipsemc: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'eclipsemc')
self.maxbtc: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'maxbtc')
self.triplemining: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'triplemining')
self.coinlab: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'coinlab')
self.pool50btc: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'pool50btc')
self.ghashio: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'ghashio')
self.stminingcorp: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'stminingcorp')
self.bitparking: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'bitparking')
self.mmpool: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'mmpool')
self.polmine: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'polmine')
self.kncminer: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'kncminer')
self.bitalo: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'bitalo')
self.f2pool: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'f2pool')
self.hhtt: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'hhtt')
self.megabigpower: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'megabigpower')
self.mtred: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'mtred')
self.nmcbit: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'nmcbit')
self.yourbtcnet: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'yourbtcnet')
self.givemecoins: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'givemecoins')
self.braiinspool: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'braiinspool')
self.antpool: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'antpool')
self.multicoinco: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'multicoinco')
self.bcpoolio: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'bcpoolio')
self.cointerra: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'cointerra')
self.kanopool: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'kanopool')
self.solock: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'solock')
self.ckpool: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'ckpool')
self.nicehash: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'nicehash')
self.bitclub: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'bitclub')
self.bitcoinaffiliatenetwork: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'bitcoinaffiliatenetwork')
self.btcc: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'btcc')
self.bwpool: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'bwpool')
self.exxbw: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'exxbw')
self.bitsolo: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'bitsolo')
self.bitfury: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'bitfury')
self.twentyoneinc: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'twentyoneinc')
self.digitalbtc: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'digitalbtc')
self.eightbaochi: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'eightbaochi')
self.mybtccoinpool: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'mybtccoinpool')
self.tbdice: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'tbdice')
self.hashpool: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'hashpool')
self.nexious: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'nexious')
self.bravomining: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'bravomining')
self.hotpool: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'hotpool')
self.okexpool: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'okexpool')
self.bcmonster: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'bcmonster')
self.onehash: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'onehash')
self.bixin: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'bixin')
self.tatmaspool: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'tatmaspool')
self.viabtc: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'viabtc')
self.connectbtc: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'connectbtc')
self.batpool: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'batpool')
self.waterhole: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'waterhole')
self.dcexploration: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'dcexploration')
self.dcex: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'dcex')
self.btpool: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'btpool')
self.fiftyeightcoin: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'fiftyeightcoin')
self.bitcoinindia: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'bitcoinindia')
self.shawnp0wers: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'shawnp0wers')
self.phashio: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'phashio')
self.rigpool: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'rigpool')
self.haozhuzhu: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'haozhuzhu')
self.sevenpool: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'sevenpool')
self.miningkings: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'miningkings')
self.hashbx: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'hashbx')
self.dpool: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'dpool')
self.rawpool: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'rawpool')
self.haominer: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'haominer')
self.helix: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'helix')
self.bitcoinukraine: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'bitcoinukraine')
self.poolin: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'poolin')
self.secretsuperstar: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'secretsuperstar')
self.tigerpoolnet: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'tigerpoolnet')
self.sigmapoolcom: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'sigmapoolcom')
self.okpooltop: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'okpooltop')
self.hummerpool: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'hummerpool')
self.tangpool: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'tangpool')
self.bytepool: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'bytepool')
self.spiderpool: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'spiderpool')
self.novablock: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'novablock')
self.miningcity: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'miningcity')
self.binancepool: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'binancepool')
self.minerium: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'minerium')
self.lubiancom: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'lubiancom')
self.okkong: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'okkong')
self.aaopool: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'aaopool')
self.emcdpool: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'emcdpool')
self.foundryusa: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'foundryusa')
self.sbicrypto: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'sbicrypto')
self.arkpool: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'arkpool')
self.purebtccom: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'purebtccom')
self.marapool: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'marapool')
self.kucoinpool: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'kucoinpool')
self.entrustcharitypool: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'entrustcharitypool')
self.okminer: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'okminer')
self.titan: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'titan')
self.pegapool: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'pegapool')
self.btcnuggets: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'btcnuggets')
self.cloudhashing: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'cloudhashing')
self.digitalxmintsy: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'digitalxmintsy')
self.telco214: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'telco214')
self.btcpoolparty: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'btcpoolparty')
self.multipool: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'multipool')
self.transactioncoinmining: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'transactioncoinmining')
self.btcdig: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'btcdig')
self.trickysbtcpool: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'trickysbtcpool')
self.btcmp: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'btcmp')
self.eobot: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'eobot')
self.unomp: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'unomp')
self.patels: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'patels')
self.gogreenlight: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'gogreenlight')
self.bitcoinindiapool: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'bitcoinindiapool')
self.ekanembtc: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'ekanembtc')
self.canoe: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'canoe')
self.tiger: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'tiger')
self.onem1x: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'onem1x')
self.zulupool: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'zulupool')
self.secpool: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'secpool')
self.ocean: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'ocean')
self.whitepool: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'whitepool')
self.wiz: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'wiz')
self.wk057: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'wk057')
self.futurebitapollosolo: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'futurebitapollosolo')
self.carbonnegative: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'carbonnegative')
self.portlandhodl: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'portlandhodl')
self.phoenix: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'phoenix')
self.neopool: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'neopool')
self.maxipool: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'maxipool')
self.bitfufupool: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'bitfufupool')
self.gdpool: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'gdpool')
self.miningdutch: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'miningdutch')
self.publicpool: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'publicpool')
self.miningsquared: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'miningsquared')
self.innopolistech: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'innopolistech')
self.btclab: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'btclab')
self.parasite: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'parasite')
self.redrockpool: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'redrockpool')
self.est3lar: BlocksDominanceRewardsPattern = BlocksDominanceRewardsPattern(client, 'est3lar')
class MetricsTree_Pools:
"""Metrics tree node."""
@@ -4356,9 +4306,9 @@ class MetricsTree_Distribution_UtxoCohorts_All:
def __init__(self, client: BrkClientBase, base_path: str = ''):
self.supply: ChangeHalvedTotalPattern = ChangeHalvedTotalPattern(client, 'supply')
self.outputs: UtxoPattern = UtxoPattern(client, 'utxo_count')
self.activity: CoinblocksCoindaysSatblocksSatdaysSentPattern = CoinblocksCoindaysSatblocksSatdaysSentPattern(client, '')
self.activity: CoinblocksCoindaysSentPattern = CoinblocksCoindaysSentPattern(client, '')
self.realized: AdjustedCapCapitulationGrossInvestorLossLowerMvrvNegNetPeakProfitRealizedSellSentSoprUpperValuePattern = AdjustedCapCapitulationGrossInvestorLossLowerMvrvNegNetPeakProfitRealizedSellSentSoprUpperValuePattern(client, '')
self.cost_basis: InvestedMaxMinPercentilesSpotPattern = InvestedMaxMinPercentilesSpotPattern(client, '')
self.cost_basis: InvestedMaxMinPercentilesPattern = InvestedMaxMinPercentilesPattern(client, '')
self.unrealized: GreedGrossInvestedInvestorNegNetPainSupplyUnrealizedPattern = GreedGrossInvestedInvestorNegNetPainSupplyUnrealizedPattern(client, '')
self.relative: MetricsTree_Distribution_UtxoCohorts_All_Relative = MetricsTree_Distribution_UtxoCohorts_All_Relative(client)
@@ -4368,9 +4318,9 @@ class MetricsTree_Distribution_UtxoCohorts_Sth:
def __init__(self, client: BrkClientBase, base_path: str = ''):
self.supply: ChangeHalvedTotalPattern = ChangeHalvedTotalPattern(client, 'sth_supply')
self.outputs: UtxoPattern = UtxoPattern(client, 'sth_utxo_count')
self.activity: CoinblocksCoindaysSatblocksSatdaysSentPattern = CoinblocksCoindaysSatblocksSatdaysSentPattern(client, 'sth')
self.activity: CoinblocksCoindaysSentPattern = CoinblocksCoindaysSentPattern(client, 'sth')
self.realized: AdjustedCapCapitulationGrossInvestorLossLowerMvrvNegNetPeakProfitRealizedSellSentSoprUpperValuePattern = AdjustedCapCapitulationGrossInvestorLossLowerMvrvNegNetPeakProfitRealizedSellSentSoprUpperValuePattern(client, 'sth')
self.cost_basis: InvestedMaxMinPercentilesSpotPattern = InvestedMaxMinPercentilesSpotPattern(client, 'sth')
self.cost_basis: InvestedMaxMinPercentilesPattern = InvestedMaxMinPercentilesPattern(client, 'sth')
self.unrealized: GreedGrossInvestedInvestorNegNetPainSupplyUnrealizedPattern = GreedGrossInvestedInvestorNegNetPainSupplyUnrealizedPattern(client, 'sth')
self.relative: InvestedNegNetNuplSupplyUnrealizedPattern2 = InvestedNegNetNuplSupplyUnrealizedPattern2(client, 'sth')
@@ -4512,28 +4462,28 @@ class MetricsTree_Distribution_UtxoCohorts_Epoch:
self._3: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3(client, 'epoch_3')
self._4: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3(client, 'epoch_4')
class MetricsTree_Distribution_UtxoCohorts_Year:
class MetricsTree_Distribution_UtxoCohorts_Class:
"""Metrics tree node."""
def __init__(self, client: BrkClientBase, base_path: str = ''):
self._2009: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3(client, 'year_2009')
self._2010: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3(client, 'year_2010')
self._2011: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3(client, 'year_2011')
self._2012: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3(client, 'year_2012')
self._2013: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3(client, 'year_2013')
self._2014: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3(client, 'year_2014')
self._2015: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3(client, 'year_2015')
self._2016: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3(client, 'year_2016')
self._2017: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3(client, 'year_2017')
self._2018: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3(client, 'year_2018')
self._2019: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3(client, 'year_2019')
self._2020: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3(client, 'year_2020')
self._2021: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3(client, 'year_2021')
self._2022: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3(client, 'year_2022')
self._2023: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3(client, 'year_2023')
self._2024: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3(client, 'year_2024')
self._2025: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3(client, 'year_2025')
self._2026: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3(client, 'year_2026')
self._2009: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3(client, 'class_2009')
self._2010: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3(client, 'class_2010')
self._2011: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3(client, 'class_2011')
self._2012: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3(client, 'class_2012')
self._2013: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3(client, 'class_2013')
self._2014: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3(client, 'class_2014')
self._2015: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3(client, 'class_2015')
self._2016: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3(client, 'class_2016')
self._2017: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3(client, 'class_2017')
self._2018: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3(client, 'class_2018')
self._2019: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3(client, 'class_2019')
self._2020: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3(client, 'class_2020')
self._2021: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3(client, 'class_2021')
self._2022: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3(client, 'class_2022')
self._2023: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3(client, 'class_2023')
self._2024: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3(client, 'class_2024')
self._2025: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3(client, 'class_2025')
self._2026: ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3 = ActivityCostOutputsRealizedRelativeSupplyUnrealizedPattern3(client, 'class_2026')
class MetricsTree_Distribution_UtxoCohorts_Type:
"""Metrics tree node."""
@@ -4565,7 +4515,7 @@ class MetricsTree_Distribution_UtxoCohorts:
self.amount_range: MetricsTree_Distribution_UtxoCohorts_AmountRange = MetricsTree_Distribution_UtxoCohorts_AmountRange(client)
self.lt_amount: MetricsTree_Distribution_UtxoCohorts_LtAmount = MetricsTree_Distribution_UtxoCohorts_LtAmount(client)
self.epoch: MetricsTree_Distribution_UtxoCohorts_Epoch = MetricsTree_Distribution_UtxoCohorts_Epoch(client)
self.year: MetricsTree_Distribution_UtxoCohorts_Year = MetricsTree_Distribution_UtxoCohorts_Year(client)
self.class: MetricsTree_Distribution_UtxoCohorts_Class = MetricsTree_Distribution_UtxoCohorts_Class(client)
self.type_: MetricsTree_Distribution_UtxoCohorts_Type = MetricsTree_Distribution_UtxoCohorts_Type(client)
class MetricsTree_Distribution_AddressCohorts_GeAmount:
@@ -5002,96 +4952,96 @@ class BrkClient(BrkClientBase):
}
}
YEAR_NAMES = {
CLASS_NAMES = {
"_2009": {
"id": "year_2009",
"id": "class_2009",
"short": "2009",
"long": "Year 2009"
"long": "Class 2009"
},
"_2010": {
"id": "year_2010",
"id": "class_2010",
"short": "2010",
"long": "Year 2010"
"long": "Class 2010"
},
"_2011": {
"id": "year_2011",
"id": "class_2011",
"short": "2011",
"long": "Year 2011"
"long": "Class 2011"
},
"_2012": {
"id": "year_2012",
"id": "class_2012",
"short": "2012",
"long": "Year 2012"
"long": "Class 2012"
},
"_2013": {
"id": "year_2013",
"id": "class_2013",
"short": "2013",
"long": "Year 2013"
"long": "Class 2013"
},
"_2014": {
"id": "year_2014",
"id": "class_2014",
"short": "2014",
"long": "Year 2014"
"long": "Class 2014"
},
"_2015": {
"id": "year_2015",
"id": "class_2015",
"short": "2015",
"long": "Year 2015"
"long": "Class 2015"
},
"_2016": {
"id": "year_2016",
"id": "class_2016",
"short": "2016",
"long": "Year 2016"
"long": "Class 2016"
},
"_2017": {
"id": "year_2017",
"id": "class_2017",
"short": "2017",
"long": "Year 2017"
"long": "Class 2017"
},
"_2018": {
"id": "year_2018",
"id": "class_2018",
"short": "2018",
"long": "Year 2018"
"long": "Class 2018"
},
"_2019": {
"id": "year_2019",
"id": "class_2019",
"short": "2019",
"long": "Year 2019"
"long": "Class 2019"
},
"_2020": {
"id": "year_2020",
"id": "class_2020",
"short": "2020",
"long": "Year 2020"
"long": "Class 2020"
},
"_2021": {
"id": "year_2021",
"id": "class_2021",
"short": "2021",
"long": "Year 2021"
"long": "Class 2021"
},
"_2022": {
"id": "year_2022",
"id": "class_2022",
"short": "2022",
"long": "Year 2022"
"long": "Class 2022"
},
"_2023": {
"id": "year_2023",
"id": "class_2023",
"short": "2023",
"long": "Year 2023"
"long": "Class 2023"
},
"_2024": {
"id": "year_2024",
"id": "class_2024",
"short": "2024",
"long": "Year 2024"
"long": "Class 2024"
},
"_2025": {
"id": "year_2025",
"id": "class_2025",
"short": "2025",
"long": "Year 2025"
"long": "Class 2025"
},
"_2026": {
"id": "year_2026",
"id": "class_2026",
"short": "2026",
"long": "Year 2026"
"long": "Class 2026"
}
}
+5 -5
View File
@@ -37,7 +37,7 @@ export function buildCohortData() {
LT_AMOUNT_NAMES,
AMOUNT_RANGE_NAMES,
SPENDABLE_TYPE_NAMES,
YEAR_NAMES,
CLASS_NAMES,
} = brk;
// Base cohort representing "all"
@@ -224,11 +224,11 @@ export function buildCohortData() {
};
});
// Year cohorts
const year = entries(utxoCohorts.year)
// Class cohorts
const class_ = entries(utxoCohorts.class)
.reverse()
.map(([key, tree], i, arr) => {
const names = YEAR_NAMES[key];
const names = CLASS_NAMES[key];
return {
name: names.short,
title: names.long,
@@ -253,6 +253,6 @@ export function buildCohortData() {
addressesAmountRanges,
typeAddressable,
typeOther,
year,
class: class_,
};
}
-10
View File
@@ -538,16 +538,6 @@ export function createMarketSection() {
...priceLines({ unit: Unit.index, numbers: [61.8, 38.2] }),
],
},
volatilityChart("Sharpe Ratio", "Sharpe Ratio", Unit.ratio, {
_1w: volatility.sharpe1w,
_1m: volatility.sharpe1m,
_1y: volatility.sharpe1y,
}),
volatilityChart("Sortino Ratio", "Sortino Ratio", Unit.ratio, {
_1w: volatility.sortino1w,
_1m: volatility.sortino1m,
_1y: volatility.sortino1y,
}),
],
},
+3 -3
View File
@@ -51,7 +51,7 @@ export function createPartialOptions() {
addressesAmountRanges,
typeAddressable,
typeOther,
year,
class: class_,
} = buildCohortData();
return [
@@ -263,10 +263,10 @@ export function createPartialOptions() {
createGroupedCohortFolderBasicWithoutMarketCap({
name: "Compare",
title: "Years",
list: year,
list: class_,
all: cohortAll,
}),
...year.map(createCohortFolderBasicWithoutMarketCap),
...class_.map(createCohortFolderBasicWithoutMarketCap),
],
},
],