mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-24 06:39:58 -07:00
global: snapshot
This commit is contained in:
@@ -2161,10 +2161,10 @@ impl _1m1w1y24hPattern3 {
|
||||
/// Create a new pattern node with accumulated metric name.
|
||||
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
|
||||
Self {
|
||||
_1m: CentsUsdPattern::new(client.clone(), _m(&acc, "1m_change")),
|
||||
_1w: CentsUsdPattern::new(client.clone(), _m(&acc, "1w_change")),
|
||||
_1y: CentsUsdPattern::new(client.clone(), _m(&acc, "1y_change")),
|
||||
_24h: CentsUsdPattern::new(client.clone(), _m(&acc, "24h_change")),
|
||||
_1m: CentsUsdPattern::new(client.clone(), _m(&acc, "1m")),
|
||||
_1w: CentsUsdPattern::new(client.clone(), _m(&acc, "1w")),
|
||||
_1y: CentsUsdPattern::new(client.clone(), _m(&acc, "1y")),
|
||||
_24h: CentsUsdPattern::new(client.clone(), _m(&acc, "24h")),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2965,8 +2965,8 @@ impl CentsUsdPattern {
|
||||
/// Create a new pattern node with accumulated metric name.
|
||||
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
|
||||
Self {
|
||||
cents: MetricPattern1::new(client.clone(), acc.clone()),
|
||||
usd: MetricPattern1::new(client.clone(), _m(&acc, "usd")),
|
||||
cents: MetricPattern1::new(client.clone(), _m(&acc, "cents")),
|
||||
usd: MetricPattern1::new(client.clone(), acc.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,22 +140,12 @@ impl ActivityCountVecs {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn truncate_push_height(
|
||||
&mut self,
|
||||
height: Height,
|
||||
counts: &BlockActivityCounts,
|
||||
) -> Result<()> {
|
||||
self.reactivated
|
||||
.height
|
||||
.truncate_push(height, counts.reactivated.into())?;
|
||||
self.sending
|
||||
.height
|
||||
.truncate_push(height, counts.sending.into())?;
|
||||
self.receiving
|
||||
.height
|
||||
.truncate_push(height, counts.receiving.into())?;
|
||||
self.both.height.truncate_push(height, counts.both.into())?;
|
||||
Ok(())
|
||||
#[inline(always)]
|
||||
pub(crate) fn push_height(&mut self, counts: &BlockActivityCounts) {
|
||||
self.reactivated.height.push(counts.reactivated.into());
|
||||
self.sending.height.push(counts.sending.into());
|
||||
self.receiving.height.push(counts.receiving.into());
|
||||
self.both.height.push(counts.both.into());
|
||||
}
|
||||
|
||||
pub(crate) fn compute_rest(
|
||||
@@ -242,15 +232,11 @@ impl AddressTypeToActivityCountVecs {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn truncate_push_height(
|
||||
&mut self,
|
||||
height: Height,
|
||||
counts: &AddressTypeToActivityCounts,
|
||||
) -> Result<()> {
|
||||
#[inline(always)]
|
||||
pub(crate) fn push_height(&mut self, counts: &AddressTypeToActivityCounts) {
|
||||
for (vecs, c) in self.0.values_mut().zip(counts.0.values()) {
|
||||
vecs.truncate_push_height(height, c)?;
|
||||
vecs.push_height(c);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,14 +294,10 @@ impl AddressActivityVecs {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn truncate_push_height(
|
||||
&mut self,
|
||||
height: Height,
|
||||
counts: &AddressTypeToActivityCounts,
|
||||
) -> Result<()> {
|
||||
#[inline(always)]
|
||||
pub(crate) fn push_height(&mut self, counts: &AddressTypeToActivityCounts) {
|
||||
let totals = counts.totals();
|
||||
self.all.truncate_push_height(height, &totals)?;
|
||||
self.by_address_type.truncate_push_height(height, counts)?;
|
||||
Ok(())
|
||||
self.all.push_height(&totals);
|
||||
self.by_address_type.push_height(counts);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,19 +137,14 @@ impl AddressTypeToAddressCountVecs {
|
||||
.map(|v| &mut v.height as &mut dyn AnyStoredVec)
|
||||
}
|
||||
|
||||
pub(crate) fn truncate_push_height(
|
||||
&mut self,
|
||||
height: Height,
|
||||
address_counts: &AddressTypeToAddressCount,
|
||||
) -> Result<()> {
|
||||
#[inline(always)]
|
||||
pub(crate) fn push_height(&mut self, address_counts: &AddressTypeToAddressCount) {
|
||||
for (vecs, &count) in self.0.values_mut().zip(address_counts.values()) {
|
||||
vecs.height.truncate_push(height, count.into())?;
|
||||
vecs.height.push(count.into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn reset_height(&mut self) -> Result<()> {
|
||||
use vecdb::WritableVec;
|
||||
for v in self.0.values_mut() {
|
||||
v.height.reset()?;
|
||||
}
|
||||
@@ -198,16 +193,10 @@ impl AddressCountsVecs {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn truncate_push_height(
|
||||
&mut self,
|
||||
height: Height,
|
||||
total: u64,
|
||||
address_counts: &AddressTypeToAddressCount,
|
||||
) -> Result<()> {
|
||||
self.all.height.truncate_push(height, total.into())?;
|
||||
self.by_address_type
|
||||
.truncate_push_height(height, address_counts)?;
|
||||
Ok(())
|
||||
#[inline(always)]
|
||||
pub(crate) fn push_height(&mut self, total: u64, address_counts: &AddressTypeToAddressCount) {
|
||||
self.all.height.push(total.into());
|
||||
self.by_address_type.push_height(address_counts);
|
||||
}
|
||||
|
||||
pub(crate) fn compute_rest(
|
||||
|
||||
@@ -155,31 +155,22 @@ impl DynCohortVecs for AddressCohortVecs {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn truncate_push(&mut self, height: Height) -> Result<()> {
|
||||
fn push_state(&mut self, height: Height) {
|
||||
if self.starting_height.is_some_and(|h| h > height) {
|
||||
return Ok(());
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(state) = self.state.as_ref() {
|
||||
self.address_count
|
||||
.height
|
||||
.truncate_push(height, state.address_count.into())?;
|
||||
self.metrics.supply.truncate_push(height, &state.inner)?;
|
||||
self.metrics.outputs.truncate_push(height, &state.inner)?;
|
||||
self.metrics.realized.truncate_push(height, &state.inner)?;
|
||||
.push(state.address_count.into());
|
||||
self.metrics.supply.push_state(&state.inner);
|
||||
self.metrics.outputs.push_state(&state.inner);
|
||||
self.metrics.realized.push_state(&state.inner);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compute_then_truncate_push_unrealized_states(
|
||||
&mut self,
|
||||
_height: Height,
|
||||
_height_price: Cents,
|
||||
_is_day_boundary: bool,
|
||||
) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
fn push_unrealized_state(&mut self, _height_price: Cents) {}
|
||||
|
||||
fn compute_rest_part1(
|
||||
&mut self,
|
||||
|
||||
@@ -20,16 +20,12 @@ pub trait DynCohortVecs: Send + Sync {
|
||||
/// Validate that computed vectors have correct versions.
|
||||
fn validate_computed_versions(&mut self, base_version: Version) -> Result<()>;
|
||||
|
||||
/// Push state to height-indexed vectors (truncating if needed).
|
||||
fn truncate_push(&mut self, height: Height) -> Result<()>;
|
||||
/// Push state to height-indexed vectors.
|
||||
/// Height is used for the state_starting_height guard check.
|
||||
fn push_state(&mut self, height: Height);
|
||||
|
||||
/// Compute and push unrealized profit/loss states and percentiles.
|
||||
fn compute_then_truncate_push_unrealized_states(
|
||||
&mut self,
|
||||
height: Height,
|
||||
height_price: Cents,
|
||||
is_day_boundary: bool,
|
||||
) -> Result<()>;
|
||||
/// Compute and push unrealized profit/loss states.
|
||||
fn push_unrealized_state(&mut self, height_price: Cents);
|
||||
|
||||
/// First phase of post-processing computations.
|
||||
fn compute_rest_part1(
|
||||
|
||||
@@ -339,15 +339,11 @@ impl UTXOCohorts<Rw> {
|
||||
}
|
||||
|
||||
/// Push maturation sats to the matured vecs for the given height.
|
||||
pub(crate) fn push_maturation(
|
||||
&mut self,
|
||||
height: Height,
|
||||
matured: &AgeRange<Sats>,
|
||||
) -> Result<()> {
|
||||
#[inline(always)]
|
||||
pub(crate) fn push_maturation(&mut self, matured: &AgeRange<Sats>) {
|
||||
for (v, &sats) in self.matured.iter_mut().zip(matured.iter()) {
|
||||
v.base.sats.height.truncate_push(height, sats)?;
|
||||
v.base.sats.height.push(sats);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn par_iter_separate_mut(
|
||||
@@ -795,8 +791,8 @@ impl UTXOCohorts<Rw> {
|
||||
}
|
||||
|
||||
/// Aggregate RealizedFull fields from age_range states and push to all/sth/lth.
|
||||
/// Called during the block loop after separate cohorts' truncate_push but before reset.
|
||||
pub(crate) fn push_overlapping_realized_full(&mut self, height: Height) -> Result<()> {
|
||||
/// Called during the block loop after separate cohorts' push_state but before reset.
|
||||
pub(crate) fn push_overlapping_realized_full(&mut self) {
|
||||
let Self {
|
||||
all,
|
||||
sth,
|
||||
@@ -823,11 +819,9 @@ impl UTXOCohorts<Rw> {
|
||||
}
|
||||
}
|
||||
|
||||
all.metrics.realized.push_from_accum(&all_acc, height)?;
|
||||
sth.metrics.realized.push_from_accum(&sth_acc, height)?;
|
||||
lth.metrics.realized.push_from_accum(<h_acc, height)?;
|
||||
|
||||
Ok(())
|
||||
all.metrics.realized.push_accum(&all_acc);
|
||||
sth.metrics.realized.push_accum(&sth_acc);
|
||||
lth.metrics.realized.push_accum(<h_acc);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::{cmp::Reverse, collections::BinaryHeap, fs, path::Path};
|
||||
|
||||
use brk_cohort::{Filtered, PROFITABILITY_RANGE_COUNT, PROFIT_COUNT, TERM_NAMES};
|
||||
use brk_error::Result;
|
||||
use brk_types::{BasisPoints16, Cents, CentsCompact, CostBasisDistribution, Date, Dollars, Height, Sats};
|
||||
use brk_types::{BasisPoints16, Cents, CentsCompact, CostBasisDistribution, Date, Dollars, Sats};
|
||||
|
||||
use crate::distribution::metrics::{CostBasis, ProfitabilityMetrics};
|
||||
|
||||
@@ -16,15 +16,14 @@ impl UTXOCohorts {
|
||||
///
|
||||
/// Percentiles and profitability are computed per-block from the Fenwick tree.
|
||||
/// Disk distributions are written only at day boundaries via K-way merge.
|
||||
pub(crate) fn truncate_push_aggregate_percentiles(
|
||||
pub(crate) fn push_aggregate_percentiles(
|
||||
&mut self,
|
||||
height: Height,
|
||||
spot_price: Cents,
|
||||
date_opt: Option<Date>,
|
||||
states_path: &Path,
|
||||
) -> Result<()> {
|
||||
if self.fenwick.is_initialized() {
|
||||
self.push_fenwick_results(height, spot_price)?;
|
||||
self.push_fenwick_results(spot_price);
|
||||
}
|
||||
|
||||
// Disk distributions only at day boundaries
|
||||
@@ -36,20 +35,20 @@ impl UTXOCohorts {
|
||||
}
|
||||
|
||||
/// Push all Fenwick-derived per-block results: percentiles, density, profitability.
|
||||
fn push_fenwick_results(&mut self, height: Height, spot_price: Cents) -> Result<()> {
|
||||
fn push_fenwick_results(&mut self, spot_price: Cents) {
|
||||
let (all_d, sth_d, lth_d) = self.fenwick.density(spot_price);
|
||||
|
||||
let all = self.fenwick.percentiles_all();
|
||||
push_cost_basis(height, &all, all_d, &mut self.all.metrics.cost_basis)?;
|
||||
push_cost_basis(&all, all_d, &mut self.all.metrics.cost_basis);
|
||||
|
||||
let sth = self.fenwick.percentiles_sth();
|
||||
push_cost_basis(height, &sth, sth_d, &mut self.sth.metrics.cost_basis)?;
|
||||
push_cost_basis(&sth, sth_d, &mut self.sth.metrics.cost_basis);
|
||||
|
||||
let lth = self.fenwick.percentiles_lth();
|
||||
push_cost_basis(height, <h, lth_d, &mut self.lth.metrics.cost_basis)?;
|
||||
push_cost_basis(<h, lth_d, &mut self.lth.metrics.cost_basis);
|
||||
|
||||
let prof = self.fenwick.profitability(spot_price);
|
||||
push_profitability(height, &prof, &mut self.profitability)
|
||||
push_profitability(&prof, &mut self.profitability);
|
||||
}
|
||||
|
||||
/// K-way merge only for writing daily cost basis distributions to disk.
|
||||
@@ -92,15 +91,15 @@ impl UTXOCohorts {
|
||||
}
|
||||
|
||||
/// Push percentiles + density to cost basis vecs.
|
||||
#[inline(always)]
|
||||
fn push_cost_basis(
|
||||
height: Height,
|
||||
percentiles: &PercentileResult,
|
||||
density_bps: u16,
|
||||
cost_basis: &mut CostBasis,
|
||||
) -> Result<()> {
|
||||
cost_basis.truncate_push_minmax(height, percentiles.min_price, percentiles.max_price)?;
|
||||
cost_basis.truncate_push_percentiles(height, &percentiles.sat_prices, &percentiles.usd_prices)?;
|
||||
cost_basis.truncate_push_density(height, BasisPoints16::from(density_bps))
|
||||
) {
|
||||
cost_basis.push_minmax(percentiles.min_price, percentiles.max_price);
|
||||
cost_basis.push_percentiles(&percentiles.sat_prices, &percentiles.usd_prices);
|
||||
cost_basis.push_density(BasisPoints16::from(density_bps));
|
||||
}
|
||||
|
||||
/// Convert raw (cents × sats) accumulator to Dollars (÷ 100 for cents→dollars, ÷ 1e8 for sats).
|
||||
@@ -111,13 +110,9 @@ fn raw_usd_to_dollars(raw: u128) -> Dollars {
|
||||
|
||||
/// Push profitability range + profit/loss aggregate values to vecs.
|
||||
fn push_profitability(
|
||||
height: Height,
|
||||
buckets: &[ProfitabilityRangeResult; PROFITABILITY_RANGE_COUNT],
|
||||
metrics: &mut ProfitabilityMetrics,
|
||||
) -> Result<()> {
|
||||
// Truncate all buckets once upfront to avoid per-push checks
|
||||
metrics.truncate(height)?;
|
||||
|
||||
) {
|
||||
// Push 25 range buckets
|
||||
for (i, bucket) in metrics.range.as_array_mut().into_iter().enumerate() {
|
||||
let r = &buckets[i];
|
||||
@@ -170,8 +165,6 @@ fn push_profitability(
|
||||
raw_usd_to_dollars(cum_sth_usd),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_distribution(
|
||||
|
||||
@@ -28,38 +28,30 @@ impl DynCohortVecs for UTXOCohortVecs<CoreCohortMetrics> {
|
||||
self.metrics.validate_computed_versions(base_version)
|
||||
}
|
||||
|
||||
fn truncate_push(&mut self, height: Height) -> Result<()> {
|
||||
fn push_state(&mut self, height: Height) {
|
||||
if self.state_starting_height.is_some_and(|h| h > height) {
|
||||
return Ok(());
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(state) = self.state.as_ref() {
|
||||
self.metrics.supply.truncate_push(height, state)?;
|
||||
self.metrics.outputs.truncate_push(height, state)?;
|
||||
self.metrics.activity.truncate_push(height, state)?;
|
||||
self.metrics.realized.truncate_push(height, state)?;
|
||||
self.metrics.supply.push_state(state);
|
||||
self.metrics.outputs.push_state(state);
|
||||
self.metrics.activity.push_state(state);
|
||||
self.metrics.realized.push_state(state);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compute_then_truncate_push_unrealized_states(
|
||||
&mut self,
|
||||
height: Height,
|
||||
height_price: Cents,
|
||||
_is_day_boundary: bool,
|
||||
) -> Result<()> {
|
||||
fn push_unrealized_state(&mut self, height_price: Cents) {
|
||||
if let Some(state) = self.state.as_mut() {
|
||||
state.apply_pending();
|
||||
let unrealized_state = state.compute_unrealized_state(height_price);
|
||||
self.metrics
|
||||
.unrealized
|
||||
.truncate_push(height, &unrealized_state)?;
|
||||
.push_state(&unrealized_state);
|
||||
self.metrics
|
||||
.supply
|
||||
.truncate_push_profitability(height, &unrealized_state)?;
|
||||
.push_profitability(&unrealized_state);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compute_rest_part1(
|
||||
|
||||
@@ -31,28 +31,19 @@ impl DynCohortVecs for UTXOCohortVecs<MinimalCohortMetrics> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn truncate_push(&mut self, height: Height) -> Result<()> {
|
||||
fn push_state(&mut self, height: Height) {
|
||||
if self.state_starting_height.is_some_and(|h| h > height) {
|
||||
return Ok(());
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(state) = self.state.as_ref() {
|
||||
self.metrics.supply.truncate_push(height, state)?;
|
||||
self.metrics.outputs.truncate_push(height, state)?;
|
||||
self.metrics.realized.truncate_push(height, state)?;
|
||||
self.metrics.supply.push_state(state);
|
||||
self.metrics.outputs.push_state(state);
|
||||
self.metrics.realized.push_state(state);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compute_then_truncate_push_unrealized_states(
|
||||
&mut self,
|
||||
_height: Height,
|
||||
_height_price: Cents,
|
||||
_is_day_boundary: bool,
|
||||
) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
fn push_unrealized_state(&mut self, _height_price: Cents) {}
|
||||
|
||||
fn compute_rest_part1(
|
||||
&mut self,
|
||||
|
||||
@@ -166,29 +166,21 @@ impl<M: CohortMetricsBase + Traversable> DynCohortVecs for UTXOCohortVecs<M> {
|
||||
self.metrics.validate_computed_versions(base_version)
|
||||
}
|
||||
|
||||
fn truncate_push(&mut self, height: Height) -> Result<()> {
|
||||
fn push_state(&mut self, height: Height) {
|
||||
if self.state_starting_height.is_some_and(|h| h > height) {
|
||||
return Ok(());
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(state) = self.state.as_ref() {
|
||||
self.metrics.truncate_push(height, state)?;
|
||||
self.metrics.push_state(state);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compute_then_truncate_push_unrealized_states(
|
||||
&mut self,
|
||||
height: Height,
|
||||
height_price: Cents,
|
||||
_is_day_boundary: bool,
|
||||
) -> Result<()> {
|
||||
fn push_unrealized_state(&mut self, height_price: Cents) {
|
||||
if let Some(state) = self.state.as_mut() {
|
||||
self.metrics
|
||||
.compute_and_push_unrealized(height, height_price, state)?;
|
||||
.compute_and_push_unrealized(height_price, state);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compute_rest_part1(
|
||||
|
||||
@@ -28,37 +28,29 @@ impl DynCohortVecs for UTXOCohortVecs<TypeCohortMetrics> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn truncate_push(&mut self, height: Height) -> Result<()> {
|
||||
fn push_state(&mut self, height: Height) {
|
||||
if self.state_starting_height.is_some_and(|h| h > height) {
|
||||
return Ok(());
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(state) = self.state.as_ref() {
|
||||
self.metrics.supply.truncate_push(height, state)?;
|
||||
self.metrics.outputs.truncate_push(height, state)?;
|
||||
self.metrics.realized.truncate_push(height, state)?;
|
||||
self.metrics.supply.push_state(state);
|
||||
self.metrics.outputs.push_state(state);
|
||||
self.metrics.realized.push_state(state);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compute_then_truncate_push_unrealized_states(
|
||||
&mut self,
|
||||
height: Height,
|
||||
height_price: Cents,
|
||||
_is_day_boundary: bool,
|
||||
) -> Result<()> {
|
||||
fn push_unrealized_state(&mut self, height_price: Cents) {
|
||||
if let Some(state) = self.state.as_mut() {
|
||||
state.apply_pending();
|
||||
let unrealized_state = state.compute_unrealized_state(height_price);
|
||||
self.metrics
|
||||
.unrealized
|
||||
.truncate_push(height, &unrealized_state)?;
|
||||
.push_state(&unrealized_state);
|
||||
self.metrics
|
||||
.supply
|
||||
.truncate_push_profitability(height, &unrealized_state)?;
|
||||
.push_profitability(&unrealized_state);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compute_rest_part1(
|
||||
|
||||
@@ -7,7 +7,7 @@ use brk_types::{
|
||||
use rayon::prelude::*;
|
||||
use rustc_hash::FxHashSet;
|
||||
use tracing::{debug, info};
|
||||
use vecdb::{AnyVec, Exit, ReadableVec, VecIndex, WritableVec};
|
||||
use vecdb::{AnyStoredVec, AnyVec, Exit, ReadableVec, VecIndex, WritableVec};
|
||||
|
||||
use crate::{
|
||||
distribution::{
|
||||
@@ -210,6 +210,22 @@ pub(crate) fn process_blocks(
|
||||
// Initialize Fenwick tree from imported BTreeMap state (one-time)
|
||||
vecs.utxo_cohorts.init_fenwick_if_needed();
|
||||
|
||||
// Pre-truncate all stored vecs to starting_height (one-time).
|
||||
// This eliminates per-push truncation checks inside the block loop.
|
||||
{
|
||||
let start = starting_height.to_usize();
|
||||
vecs.utxo_cohorts
|
||||
.par_iter_vecs_mut()
|
||||
.chain(vecs.address_cohorts.par_iter_vecs_mut())
|
||||
.chain(vecs.addresses.funded.par_iter_height_mut())
|
||||
.chain(vecs.addresses.empty.par_iter_height_mut())
|
||||
.chain(vecs.addresses.activity.par_iter_height_mut())
|
||||
.chain(rayon::iter::once(
|
||||
&mut vecs.coinblocks_destroyed.base.height as &mut dyn AnyStoredVec,
|
||||
))
|
||||
.try_for_each(|v| v.any_truncate_if_needed_at(start))?;
|
||||
}
|
||||
|
||||
// Reusable hashsets (avoid per-block allocation)
|
||||
let mut received_addresses = ByAddressType::<FxHashSet<TypeIndex>>::default();
|
||||
let mut seen_senders = ByAddressType::<FxHashSet<TypeIndex>>::default();
|
||||
@@ -364,14 +380,13 @@ pub(crate) fn process_blocks(
|
||||
blocks_old as u128 * u64::from(sent.spendable_supply.value) as u128
|
||||
})
|
||||
.sum();
|
||||
vecs.coinblocks_destroyed.base.height.truncate_push(
|
||||
height,
|
||||
vecs.coinblocks_destroyed.base.height.push(
|
||||
StoredF64::from(total_satblocks as f64 / Sats::ONE_BTC_U128 as f64),
|
||||
)?;
|
||||
);
|
||||
}
|
||||
|
||||
// Record maturation (sats crossing age boundaries)
|
||||
vecs.utxo_cohorts.push_maturation(height, &matured)?;
|
||||
vecs.utxo_cohorts.push_maturation(&matured);
|
||||
|
||||
// Build set of addresses that received this block (for detecting "both" in sent)
|
||||
// Reuse pre-allocated hashsets: clear preserves capacity, avoiding reallocation
|
||||
@@ -437,14 +452,10 @@ pub(crate) fn process_blocks(
|
||||
|
||||
// Push to height-indexed vectors
|
||||
vecs.addresses.funded
|
||||
.truncate_push_height(height, address_counts.sum(), &address_counts)?;
|
||||
vecs.addresses.empty.truncate_push_height(
|
||||
height,
|
||||
empty_address_counts.sum(),
|
||||
&empty_address_counts,
|
||||
)?;
|
||||
vecs.addresses.activity
|
||||
.truncate_push_height(height, &activity_counts)?;
|
||||
.push_height(address_counts.sum(), &address_counts);
|
||||
vecs.addresses.empty
|
||||
.push_height(empty_address_counts.sum(), &empty_address_counts);
|
||||
vecs.addresses.activity.push_height(&activity_counts);
|
||||
|
||||
let is_last_of_day = is_last_of_day[offset];
|
||||
let date_opt = is_last_of_day.then(|| Date::from(timestamp));
|
||||
@@ -454,11 +465,9 @@ pub(crate) fn process_blocks(
|
||||
&mut vecs.address_cohorts,
|
||||
height,
|
||||
block_price,
|
||||
date_opt.is_some(),
|
||||
)?;
|
||||
);
|
||||
|
||||
vecs.utxo_cohorts.truncate_push_aggregate_percentiles(
|
||||
height,
|
||||
vecs.utxo_cohorts.push_aggregate_percentiles(
|
||||
block_price,
|
||||
date_opt,
|
||||
&vecs.states_path,
|
||||
@@ -521,42 +530,29 @@ fn push_cohort_states(
|
||||
address_cohorts: &mut AddressCohorts,
|
||||
height: Height,
|
||||
height_price: Cents,
|
||||
is_day_boundary: bool,
|
||||
) -> Result<()> {
|
||||
) {
|
||||
// Phase 1: push + unrealized (no reset yet — states still needed for aggregation)
|
||||
let (r1, r2) = rayon::join(
|
||||
rayon::join(
|
||||
|| {
|
||||
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,
|
||||
)?;
|
||||
Ok(())
|
||||
.for_each(|v| {
|
||||
v.push_state(height);
|
||||
v.push_unrealized_state(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,
|
||||
)?;
|
||||
Ok(())
|
||||
.for_each(|v| {
|
||||
v.push_state(height);
|
||||
v.push_unrealized_state(height_price);
|
||||
})
|
||||
},
|
||||
);
|
||||
r1?;
|
||||
r2?;
|
||||
|
||||
// Phase 2: aggregate age_range realized states → push to overlapping cohorts' RealizedFull
|
||||
utxo_cohorts.push_overlapping_realized_full(height)?;
|
||||
utxo_cohorts.push_overlapping_realized_full();
|
||||
|
||||
// Phase 3: reset per-block values
|
||||
utxo_cohorts
|
||||
@@ -565,6 +561,4 @@ fn push_cohort_states(
|
||||
address_cohorts
|
||||
.iter_separate_mut()
|
||||
.for_each(|v| v.reset_single_iteration_values());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Bitcoin, Height, Indexes, Sats, StoredF64, Version};
|
||||
use brk_types::{Bitcoin, Indexes, Sats, StoredF64, Version};
|
||||
use vecdb::{AnyStoredVec, AnyVec, Exit, Rw, StorageMode, WritableVec};
|
||||
|
||||
use crate::{
|
||||
@@ -40,27 +40,25 @@ impl ActivityCore {
|
||||
.min(self.sent_in_loss.base.sats.height.len())
|
||||
}
|
||||
|
||||
pub(crate) fn truncate_push(
|
||||
#[inline(always)]
|
||||
pub(crate) fn push_state(
|
||||
&mut self,
|
||||
height: Height,
|
||||
state: &CohortState<impl RealizedOps, impl CostBasisOps>,
|
||||
) -> Result<()> {
|
||||
self.sent.base.height.truncate_push(height, state.sent)?;
|
||||
self.coindays_destroyed.base.height.truncate_push(
|
||||
height,
|
||||
) {
|
||||
self.sent.base.height.push(state.sent);
|
||||
self.coindays_destroyed.base.height.push(
|
||||
StoredF64::from(Bitcoin::from(state.satdays_destroyed)),
|
||||
)?;
|
||||
);
|
||||
self.sent_in_profit
|
||||
.base
|
||||
.sats
|
||||
.height
|
||||
.truncate_push(height, state.realized.sent_in_profit())?;
|
||||
.push(state.realized.sent_in_profit());
|
||||
self.sent_in_loss
|
||||
.base
|
||||
.sats
|
||||
.height
|
||||
.truncate_push(height, state.realized.sent_in_loss())?;
|
||||
Ok(())
|
||||
.push(state.realized.sent_in_loss());
|
||||
}
|
||||
|
||||
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Bitcoin, Height, Indexes, StoredF32, StoredF64, Version};
|
||||
use brk_types::{Bitcoin, Indexes, StoredF32, StoredF64, Version};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use vecdb::{AnyStoredVec, Exit, ReadableCloneableVec, Rw, StorageMode};
|
||||
|
||||
@@ -45,12 +45,12 @@ impl ActivityFull {
|
||||
self.inner.min_len()
|
||||
}
|
||||
|
||||
pub(crate) fn full_truncate_push(
|
||||
#[inline(always)]
|
||||
pub(crate) fn full_push_state(
|
||||
&mut self,
|
||||
height: Height,
|
||||
state: &CohortState<impl RealizedOps, impl CostBasisOps>,
|
||||
) -> Result<()> {
|
||||
self.inner.truncate_push(height, state)
|
||||
) {
|
||||
self.inner.push_state(state);
|
||||
}
|
||||
|
||||
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
|
||||
|
||||
@@ -5,7 +5,7 @@ pub use self::core::ActivityCore;
|
||||
pub use full::ActivityFull;
|
||||
|
||||
use brk_error::Result;
|
||||
use brk_types::{Height, Indexes, Version};
|
||||
use brk_types::{Indexes, Version};
|
||||
use vecdb::Exit;
|
||||
|
||||
use crate::distribution::state::{CohortState, CostBasisOps, RealizedOps};
|
||||
@@ -14,11 +14,10 @@ pub trait ActivityLike: Send + Sync {
|
||||
fn as_core(&self) -> &ActivityCore;
|
||||
fn as_core_mut(&mut self) -> &mut ActivityCore;
|
||||
fn min_len(&self) -> usize;
|
||||
fn truncate_push<R: RealizedOps>(
|
||||
fn push_state<R: RealizedOps>(
|
||||
&mut self,
|
||||
height: Height,
|
||||
state: &CohortState<R, impl CostBasisOps>,
|
||||
) -> Result<()>;
|
||||
);
|
||||
fn validate_computed_versions(&mut self, base_version: Version) -> Result<()>;
|
||||
fn compute_from_stateful(
|
||||
&mut self,
|
||||
@@ -37,8 +36,8 @@ impl ActivityLike for ActivityCore {
|
||||
fn as_core(&self) -> &ActivityCore { self }
|
||||
fn as_core_mut(&mut self) -> &mut ActivityCore { self }
|
||||
fn min_len(&self) -> usize { self.min_len() }
|
||||
fn truncate_push<R: RealizedOps>(&mut self, height: Height, state: &CohortState<R, impl CostBasisOps>) -> Result<()> {
|
||||
self.truncate_push(height, state)
|
||||
fn push_state<R: RealizedOps>(&mut self, state: &CohortState<R, impl CostBasisOps>) {
|
||||
self.push_state(state);
|
||||
}
|
||||
fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> {
|
||||
self.validate_computed_versions(base_version)
|
||||
@@ -55,8 +54,8 @@ impl ActivityLike for ActivityFull {
|
||||
fn as_core(&self) -> &ActivityCore { &self.inner }
|
||||
fn as_core_mut(&mut self) -> &mut ActivityCore { &mut self.inner }
|
||||
fn min_len(&self) -> usize { self.full_min_len() }
|
||||
fn truncate_push<R: RealizedOps>(&mut self, height: Height, state: &CohortState<R, impl CostBasisOps>) -> Result<()> {
|
||||
self.full_truncate_push(height, state)
|
||||
fn push_state<R: RealizedOps>(&mut self, state: &CohortState<R, impl CostBasisOps>) {
|
||||
self.full_push_state(state);
|
||||
}
|
||||
fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> {
|
||||
self.inner.validate_computed_versions(base_version)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{BasisPoints16, Cents, Height, Version};
|
||||
use brk_types::{BasisPoints16, Cents, Version};
|
||||
use vecdb::{AnyStoredVec, AnyVec, Rw, StorageMode, WritableVec};
|
||||
|
||||
use crate::internal::{PerBlock, PercentPerBlock, PercentilesVecs, Price, PERCENTILES_LEN};
|
||||
@@ -53,34 +53,25 @@ impl CostBasis {
|
||||
.min(self.supply_density.bps.height.len())
|
||||
}
|
||||
|
||||
pub(crate) fn truncate_push_minmax(
|
||||
&mut self,
|
||||
height: Height,
|
||||
min_price: Cents,
|
||||
max_price: Cents,
|
||||
) -> Result<()> {
|
||||
self.min.cents.height.truncate_push(height, min_price)?;
|
||||
self.max.cents.height.truncate_push(height, max_price)?;
|
||||
Ok(())
|
||||
#[inline(always)]
|
||||
pub(crate) fn push_minmax(&mut self, min_price: Cents, max_price: Cents) {
|
||||
self.min.cents.height.push(min_price);
|
||||
self.max.cents.height.push(max_price);
|
||||
}
|
||||
|
||||
pub(crate) fn truncate_push_percentiles(
|
||||
#[inline(always)]
|
||||
pub(crate) fn push_percentiles(
|
||||
&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(())
|
||||
) {
|
||||
self.percentiles.push(sat_prices);
|
||||
self.invested_capital.push(usd_prices);
|
||||
}
|
||||
|
||||
pub(crate) fn truncate_push_density(
|
||||
&mut self,
|
||||
height: Height,
|
||||
density_bps: BasisPoints16,
|
||||
) -> Result<()> {
|
||||
Ok(self.supply_density.bps.height.truncate_push(height, density_bps)?)
|
||||
#[inline(always)]
|
||||
pub(crate) fn push_density(&mut self, density_bps: BasisPoints16) {
|
||||
self.supply_density.bps.height.push(density_bps);
|
||||
}
|
||||
|
||||
pub(crate) fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> {
|
||||
|
||||
@@ -85,7 +85,7 @@ pub use unrealized::{
|
||||
|
||||
use brk_cohort::Filter;
|
||||
use brk_error::Result;
|
||||
use brk_types::{Cents, Height, Indexes, Version};
|
||||
use brk_types::{Cents, Indexes, Version};
|
||||
use vecdb::{AnyStoredVec, Exit, StorageMode};
|
||||
|
||||
use crate::{
|
||||
@@ -183,17 +183,13 @@ pub trait CohortMetricsBase:
|
||||
/// Apply pending state, compute and push unrealized state.
|
||||
fn compute_and_push_unrealized(
|
||||
&mut self,
|
||||
height: Height,
|
||||
height_price: Cents,
|
||||
state: &mut CohortState<RealizedState, CostBasisData<WithCapital>>,
|
||||
) -> Result<()> {
|
||||
) {
|
||||
state.apply_pending();
|
||||
let unrealized_state = state.compute_unrealized_state(height_price);
|
||||
self.unrealized_mut()
|
||||
.truncate_push(height, &unrealized_state)?;
|
||||
self.supply_mut()
|
||||
.truncate_push_profitability(height, &unrealized_state)?;
|
||||
Ok(())
|
||||
self.unrealized_mut().push_state(&unrealized_state);
|
||||
self.supply_mut().push_profitability(&unrealized_state);
|
||||
}
|
||||
|
||||
fn collect_all_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec>;
|
||||
@@ -207,16 +203,14 @@ pub trait CohortMetricsBase:
|
||||
.min(self.unrealized().min_stateful_len())
|
||||
}
|
||||
|
||||
fn truncate_push(
|
||||
fn push_state(
|
||||
&mut self,
|
||||
height: Height,
|
||||
state: &CohortState<RealizedState, CostBasisData<WithCapital>>,
|
||||
) -> Result<()> {
|
||||
self.supply_mut().truncate_push(height, state)?;
|
||||
self.outputs_mut().truncate_push(height, state)?;
|
||||
self.activity_mut().truncate_push(height, state)?;
|
||||
self.realized_mut().truncate_push(height, state)?;
|
||||
Ok(())
|
||||
) {
|
||||
self.supply_mut().push_state(state);
|
||||
self.outputs_mut().push_state(state);
|
||||
self.activity_mut().push_state(state);
|
||||
self.realized_mut().push_state(state);
|
||||
}
|
||||
|
||||
/// First phase of computed metrics (indexes from height).
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{BasisPointsSigned32, Height, Indexes, StoredI64, StoredU64, Version};
|
||||
use brk_types::{BasisPointsSigned32, Indexes, StoredI64, StoredU64, Version};
|
||||
use vecdb::{AnyStoredVec, AnyVec, Exit, Rw, StorageMode, WritableVec};
|
||||
|
||||
use crate::{
|
||||
@@ -35,11 +35,11 @@ impl OutputsBase {
|
||||
self.unspent_count.height.len()
|
||||
}
|
||||
|
||||
pub(crate) fn truncate_push(&mut self, height: Height, state: &CohortState<impl RealizedOps, impl CostBasisOps>) -> Result<()> {
|
||||
#[inline(always)]
|
||||
pub(crate) fn push_state(&mut self, state: &CohortState<impl RealizedOps, impl CostBasisOps>) {
|
||||
self.unspent_count
|
||||
.height
|
||||
.truncate_push(height, StoredU64::from(state.supply.utxo_count))?;
|
||||
Ok(())
|
||||
.push(StoredU64::from(state.supply.utxo_count));
|
||||
}
|
||||
|
||||
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
|
||||
|
||||
@@ -2,7 +2,7 @@ use brk_cohort::{Loss, Profit, ProfitabilityRange};
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{
|
||||
BasisPoints32, BasisPointsSigned32, Cents, Dollars, Height, Indexes, Sats, StoredF32, Version,
|
||||
BasisPoints32, BasisPointsSigned32, Cents, Dollars, Indexes, Sats, StoredF32, Version,
|
||||
};
|
||||
use vecdb::{AnyStoredVec, AnyVec, Database, Exit, Rw, StorageMode, WritableVec};
|
||||
|
||||
@@ -103,15 +103,6 @@ impl ProfitabilityBucket {
|
||||
})
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn truncate(&mut self, height: Height) -> Result<()> {
|
||||
self.supply.all.sats.height.truncate_if_needed(height)?;
|
||||
self.supply.sth.sats.height.truncate_if_needed(height)?;
|
||||
self.realized_cap.all.height.truncate_if_needed(height)?;
|
||||
self.realized_cap.sth.height.truncate_if_needed(height)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn push(
|
||||
&mut self,
|
||||
@@ -223,10 +214,6 @@ impl<M: StorageMode> ProfitabilityMetrics<M> {
|
||||
}
|
||||
|
||||
impl ProfitabilityMetrics {
|
||||
pub(crate) fn truncate(&mut self, height: Height) -> Result<()> {
|
||||
self.iter_mut().try_for_each(|b| b.truncate(height))
|
||||
}
|
||||
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
version: Version,
|
||||
|
||||
@@ -73,9 +73,9 @@ impl RealizedCore {
|
||||
self.minimal.min_stateful_len()
|
||||
}
|
||||
|
||||
pub(crate) fn truncate_push(&mut self, height: Height, state: &CohortState<impl RealizedOps, impl CostBasisOps>) -> Result<()> {
|
||||
self.minimal.truncate_push(height, state)?;
|
||||
Ok(())
|
||||
#[inline(always)]
|
||||
pub(crate) fn push_state(&mut self, state: &CohortState<impl RealizedOps, impl CostBasisOps>) {
|
||||
self.minimal.push_state(state);
|
||||
}
|
||||
|
||||
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
|
||||
|
||||
@@ -234,49 +234,47 @@ impl RealizedFull {
|
||||
.min(self.peak_regret.value.base.height.len())
|
||||
}
|
||||
|
||||
pub(crate) fn truncate_push(
|
||||
#[inline(always)]
|
||||
pub(crate) fn push_state(
|
||||
&mut self,
|
||||
height: Height,
|
||||
state: &CohortState<RealizedState, CostBasisData<WithCapital>>,
|
||||
) -> Result<()> {
|
||||
self.core.truncate_push(height, state)?;
|
||||
) {
|
||||
self.core.push_state(state);
|
||||
self.profit
|
||||
.value_created
|
||||
.base
|
||||
.height
|
||||
.truncate_push(height, state.realized.profit_value_created())?;
|
||||
.push(state.realized.profit_value_created());
|
||||
self.profit
|
||||
.value_destroyed
|
||||
.base
|
||||
.height
|
||||
.truncate_push(height, state.realized.profit_value_destroyed())?;
|
||||
.push(state.realized.profit_value_destroyed());
|
||||
self.loss
|
||||
.value_created
|
||||
.base
|
||||
.height
|
||||
.truncate_push(height, state.realized.loss_value_created())?;
|
||||
.push(state.realized.loss_value_created());
|
||||
self.loss
|
||||
.value_destroyed
|
||||
.base
|
||||
.height
|
||||
.truncate_push(height, state.realized.loss_value_destroyed())?;
|
||||
.push(state.realized.loss_value_destroyed());
|
||||
self.investor
|
||||
.price
|
||||
.cents
|
||||
.height
|
||||
.truncate_push(height, state.realized.investor_price())?;
|
||||
.push(state.realized.investor_price());
|
||||
self.cap_raw
|
||||
.truncate_push(height, state.realized.cap_raw())?;
|
||||
.push(state.realized.cap_raw());
|
||||
self.investor
|
||||
.cap_raw
|
||||
.truncate_push(height, state.realized.investor_cap_raw())?;
|
||||
.push(state.realized.investor_cap_raw());
|
||||
self.peak_regret
|
||||
.value
|
||||
.base
|
||||
.height
|
||||
.truncate_push(height, state.realized.peak_regret())?;
|
||||
|
||||
Ok(())
|
||||
.push(state.realized.peak_regret());
|
||||
}
|
||||
|
||||
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
|
||||
@@ -304,36 +302,36 @@ impl RealizedFull {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn push_from_accum(
|
||||
#[inline(always)]
|
||||
pub(crate) fn push_accum(
|
||||
&mut self,
|
||||
accum: &RealizedFullAccum,
|
||||
height: Height,
|
||||
) -> Result<()> {
|
||||
) {
|
||||
self.profit
|
||||
.value_created
|
||||
.base
|
||||
.height
|
||||
.truncate_push(height, accum.profit_value_created())?;
|
||||
.push(accum.profit_value_created());
|
||||
self.profit
|
||||
.value_destroyed
|
||||
.base
|
||||
.height
|
||||
.truncate_push(height, accum.profit_value_destroyed())?;
|
||||
.push(accum.profit_value_destroyed());
|
||||
self.loss
|
||||
.value_created
|
||||
.base
|
||||
.height
|
||||
.truncate_push(height, accum.loss_value_created())?;
|
||||
.push(accum.loss_value_created());
|
||||
self.loss
|
||||
.value_destroyed
|
||||
.base
|
||||
.height
|
||||
.truncate_push(height, accum.loss_value_destroyed())?;
|
||||
.push(accum.loss_value_destroyed());
|
||||
self.cap_raw
|
||||
.truncate_push(height, accum.cap_raw)?;
|
||||
.push(accum.cap_raw);
|
||||
self.investor
|
||||
.cap_raw
|
||||
.truncate_push(height, accum.investor_cap_raw)?;
|
||||
.push(accum.investor_cap_raw);
|
||||
|
||||
let investor_price = {
|
||||
let cap = accum.cap_raw.as_u128();
|
||||
@@ -347,15 +345,13 @@ impl RealizedFull {
|
||||
.price
|
||||
.cents
|
||||
.height
|
||||
.truncate_push(height, investor_price)?;
|
||||
.push(investor_price);
|
||||
|
||||
self.peak_regret
|
||||
.value
|
||||
.base
|
||||
.height
|
||||
.truncate_push(height, accum.peak_regret())?;
|
||||
|
||||
Ok(())
|
||||
.push(accum.peak_regret());
|
||||
}
|
||||
|
||||
pub(crate) fn compute_rest_part1(
|
||||
|
||||
@@ -80,21 +80,21 @@ impl RealizedMinimal {
|
||||
.min(self.sopr.value_destroyed.base.height.len())
|
||||
}
|
||||
|
||||
pub(crate) fn truncate_push(&mut self, height: Height, state: &CohortState<impl RealizedOps, impl CostBasisOps>) -> Result<()> {
|
||||
self.cap.cents.height.truncate_push(height, state.realized.cap())?;
|
||||
self.profit.base.cents.height.truncate_push(height, state.realized.profit())?;
|
||||
self.loss.base.cents.height.truncate_push(height, state.realized.loss())?;
|
||||
#[inline(always)]
|
||||
pub(crate) fn push_state(&mut self, state: &CohortState<impl RealizedOps, impl CostBasisOps>) {
|
||||
self.cap.cents.height.push(state.realized.cap());
|
||||
self.profit.base.cents.height.push(state.realized.profit());
|
||||
self.loss.base.cents.height.push(state.realized.loss());
|
||||
self.sopr
|
||||
.value_created
|
||||
.base
|
||||
.height
|
||||
.truncate_push(height, state.realized.value_created())?;
|
||||
.push(state.realized.value_created());
|
||||
self.sopr
|
||||
.value_destroyed
|
||||
.base
|
||||
.height
|
||||
.truncate_push(height, state.realized.value_destroyed())?;
|
||||
Ok(())
|
||||
.push(state.realized.value_destroyed());
|
||||
}
|
||||
|
||||
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
|
||||
|
||||
@@ -9,7 +9,7 @@ pub use full::{RealizedFull, RealizedFullAccum};
|
||||
pub use minimal::RealizedMinimal;
|
||||
|
||||
use brk_error::Result;
|
||||
use brk_types::{Height, Indexes};
|
||||
use brk_types::Indexes;
|
||||
use vecdb::Exit;
|
||||
|
||||
use crate::distribution::state::{WithCapital, CohortState, CostBasisData, RealizedState};
|
||||
@@ -18,7 +18,7 @@ pub trait RealizedLike: Send + Sync {
|
||||
fn as_core(&self) -> &RealizedCore;
|
||||
fn as_core_mut(&mut self) -> &mut RealizedCore;
|
||||
fn min_stateful_len(&self) -> usize;
|
||||
fn truncate_push(&mut self, height: Height, state: &CohortState<RealizedState, CostBasisData<WithCapital>>) -> Result<()>;
|
||||
fn push_state(&mut self, state: &CohortState<RealizedState, CostBasisData<WithCapital>>);
|
||||
fn compute_rest_part1(&mut self, starting_indexes: &Indexes, exit: &Exit) -> Result<()>;
|
||||
fn compute_from_stateful(
|
||||
&mut self,
|
||||
@@ -32,8 +32,9 @@ impl RealizedLike for RealizedCore {
|
||||
fn as_core(&self) -> &RealizedCore { self }
|
||||
fn as_core_mut(&mut self) -> &mut RealizedCore { self }
|
||||
fn min_stateful_len(&self) -> usize { self.min_stateful_len() }
|
||||
fn truncate_push(&mut self, height: Height, state: &CohortState<RealizedState, CostBasisData<WithCapital>>) -> Result<()> {
|
||||
self.truncate_push(height, state)
|
||||
#[inline(always)]
|
||||
fn push_state(&mut self, state: &CohortState<RealizedState, CostBasisData<WithCapital>>) {
|
||||
self.push_state(state)
|
||||
}
|
||||
fn compute_rest_part1(&mut self, starting_indexes: &Indexes, exit: &Exit) -> Result<()> {
|
||||
self.compute_rest_part1(starting_indexes, exit)
|
||||
@@ -47,8 +48,9 @@ impl RealizedLike for RealizedFull {
|
||||
fn as_core(&self) -> &RealizedCore { &self.core }
|
||||
fn as_core_mut(&mut self) -> &mut RealizedCore { &mut self.core }
|
||||
fn min_stateful_len(&self) -> usize { self.min_stateful_len() }
|
||||
fn truncate_push(&mut self, height: Height, state: &CohortState<RealizedState, CostBasisData<WithCapital>>) -> Result<()> {
|
||||
self.truncate_push(height, state)
|
||||
#[inline(always)]
|
||||
fn push_state(&mut self, state: &CohortState<RealizedState, CostBasisData<WithCapital>>) {
|
||||
self.push_state(state)
|
||||
}
|
||||
fn compute_rest_part1(&mut self, starting_indexes: &Indexes, exit: &Exit) -> Result<()> {
|
||||
self.compute_rest_part1(starting_indexes, exit)
|
||||
|
||||
@@ -50,9 +50,9 @@ impl SupplyBase {
|
||||
self.total.sats.height.len()
|
||||
}
|
||||
|
||||
pub(crate) fn truncate_push(&mut self, height: Height, state: &CohortState<impl RealizedOps, impl CostBasisOps>) -> Result<()> {
|
||||
self.total.sats.height.truncate_push(height, state.supply.value)?;
|
||||
Ok(())
|
||||
#[inline(always)]
|
||||
pub(crate) fn push_state(&mut self, state: &CohortState<impl RealizedOps, impl CostBasisOps>) {
|
||||
self.total.sats.height.push(state.supply.value);
|
||||
}
|
||||
|
||||
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
|
||||
|
||||
@@ -43,20 +43,10 @@ impl SupplyCore {
|
||||
.min(self.in_loss.sats.height.len())
|
||||
}
|
||||
|
||||
pub(crate) fn truncate_push_profitability(
|
||||
&mut self,
|
||||
height: Height,
|
||||
state: &UnrealizedState,
|
||||
) -> Result<()> {
|
||||
self.in_profit
|
||||
.sats
|
||||
.height
|
||||
.truncate_push(height, state.supply_in_profit)?;
|
||||
self.in_loss
|
||||
.sats
|
||||
.height
|
||||
.truncate_push(height, state.supply_in_loss)?;
|
||||
Ok(())
|
||||
#[inline(always)]
|
||||
pub(crate) fn push_profitability(&mut self, state: &UnrealizedState) {
|
||||
self.in_profit.sats.height.push(state.supply_in_profit);
|
||||
self.in_loss.sats.height.push(state.supply_in_loss);
|
||||
}
|
||||
|
||||
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
|
||||
|
||||
@@ -55,31 +55,18 @@ impl UnrealizedBase {
|
||||
.min(self.investor_cap_in_loss_raw.len())
|
||||
}
|
||||
|
||||
pub(crate) fn truncate_push(
|
||||
&mut self,
|
||||
height: Height,
|
||||
height_state: &UnrealizedState,
|
||||
) -> Result<()> {
|
||||
self.core.truncate_push(height, height_state)?;
|
||||
#[inline(always)]
|
||||
pub(crate) fn push_state(&mut self, state: &UnrealizedState) {
|
||||
self.core.push_state(state);
|
||||
|
||||
self.invested_capital_in_profit_raw.truncate_push(
|
||||
height,
|
||||
CentsSats::new(height_state.invested_capital_in_profit_raw),
|
||||
)?;
|
||||
self.invested_capital_in_loss_raw.truncate_push(
|
||||
height,
|
||||
CentsSats::new(height_state.invested_capital_in_loss_raw),
|
||||
)?;
|
||||
self.investor_cap_in_profit_raw.truncate_push(
|
||||
height,
|
||||
CentsSquaredSats::new(height_state.investor_cap_in_profit_raw),
|
||||
)?;
|
||||
self.investor_cap_in_loss_raw.truncate_push(
|
||||
height,
|
||||
CentsSquaredSats::new(height_state.investor_cap_in_loss_raw),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
self.invested_capital_in_profit_raw
|
||||
.push(CentsSats::new(state.invested_capital_in_profit_raw));
|
||||
self.invested_capital_in_loss_raw
|
||||
.push(CentsSats::new(state.invested_capital_in_loss_raw));
|
||||
self.investor_cap_in_profit_raw
|
||||
.push(CentsSquaredSats::new(state.investor_cap_in_profit_raw));
|
||||
self.investor_cap_in_loss_raw
|
||||
.push(CentsSquaredSats::new(state.investor_cap_in_loss_raw));
|
||||
}
|
||||
|
||||
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
|
||||
@@ -134,8 +121,16 @@ impl UnrealizedBase {
|
||||
.map(|o| o.investor_cap_in_loss_raw.collect_range_at(start, end))
|
||||
.collect();
|
||||
|
||||
self.invested_capital_in_profit_raw
|
||||
.truncate_if_needed_at(start)?;
|
||||
self.invested_capital_in_loss_raw
|
||||
.truncate_if_needed_at(start)?;
|
||||
self.investor_cap_in_profit_raw
|
||||
.truncate_if_needed_at(start)?;
|
||||
self.investor_cap_in_loss_raw
|
||||
.truncate_if_needed_at(start)?;
|
||||
|
||||
for i in start..end {
|
||||
let height = Height::from(i);
|
||||
let local_i = i - start;
|
||||
|
||||
let mut sum_invested_profit = CentsSats::ZERO;
|
||||
@@ -151,13 +146,13 @@ impl UnrealizedBase {
|
||||
}
|
||||
|
||||
self.invested_capital_in_profit_raw
|
||||
.truncate_push(height, sum_invested_profit)?;
|
||||
.push(sum_invested_profit);
|
||||
self.invested_capital_in_loss_raw
|
||||
.truncate_push(height, sum_invested_loss)?;
|
||||
.push(sum_invested_loss);
|
||||
self.investor_cap_in_profit_raw
|
||||
.truncate_push(height, sum_investor_profit)?;
|
||||
.push(sum_investor_profit);
|
||||
self.investor_cap_in_loss_raw
|
||||
.truncate_push(height, sum_investor_loss)?;
|
||||
.push(sum_investor_loss);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -53,18 +53,18 @@ impl UnrealizedBasic {
|
||||
.min(self.loss.base.cents.height.len())
|
||||
}
|
||||
|
||||
pub(crate) fn truncate_push(&mut self, height: Height, state: &UnrealizedState) -> Result<()> {
|
||||
#[inline(always)]
|
||||
pub(crate) fn push_state(&mut self, state: &UnrealizedState) {
|
||||
self.profit
|
||||
.base
|
||||
.cents
|
||||
.height
|
||||
.truncate_push(height, state.unrealized_profit)?;
|
||||
.push(state.unrealized_profit);
|
||||
self.loss
|
||||
.base
|
||||
.cents
|
||||
.height
|
||||
.truncate_push(height, state.unrealized_loss)?;
|
||||
Ok(())
|
||||
.push(state.unrealized_loss);
|
||||
}
|
||||
|
||||
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Cents, CentsSigned, Height, Indexes, Version};
|
||||
use brk_types::{Cents, CentsSigned, Indexes, Version};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use vecdb::{AnyStoredVec, Exit, Rw, StorageMode};
|
||||
|
||||
@@ -39,13 +39,9 @@ impl UnrealizedCore {
|
||||
self.basic.min_stateful_len()
|
||||
}
|
||||
|
||||
pub(crate) fn truncate_push(
|
||||
&mut self,
|
||||
height: Height,
|
||||
height_state: &UnrealizedState,
|
||||
) -> Result<()> {
|
||||
self.basic.truncate_push(height, height_state)?;
|
||||
Ok(())
|
||||
#[inline(always)]
|
||||
pub(crate) fn push_state(&mut self, state: &UnrealizedState) {
|
||||
self.basic.push_state(state);
|
||||
}
|
||||
|
||||
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Cents, CentsSats, CentsSigned, Height, Indexes, Version};
|
||||
use brk_types::{Cents, CentsSats, CentsSigned, Indexes, Version};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use vecdb::{AnyStoredVec, Exit, Rw, StorageMode, WritableVec};
|
||||
|
||||
@@ -58,21 +58,17 @@ impl UnrealizedFull {
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn truncate_push_all(
|
||||
&mut self,
|
||||
height: Height,
|
||||
state: &UnrealizedState,
|
||||
) -> Result<()> {
|
||||
self.inner.truncate_push(height, state)?;
|
||||
#[inline(always)]
|
||||
pub(crate) fn push_state_all(&mut self, state: &UnrealizedState) {
|
||||
self.inner.push_state(state);
|
||||
self.invested_capital_in_profit
|
||||
.cents
|
||||
.height
|
||||
.truncate_push(height, state.invested_capital_in_profit)?;
|
||||
.push(state.invested_capital_in_profit);
|
||||
self.invested_capital_in_loss
|
||||
.cents
|
||||
.height
|
||||
.truncate_push(height, state.invested_capital_in_loss)?;
|
||||
Ok(())
|
||||
.push(state.invested_capital_in_loss);
|
||||
}
|
||||
|
||||
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
|
||||
|
||||
@@ -11,7 +11,7 @@ pub use full::UnrealizedFull;
|
||||
pub use minimal::UnrealizedMinimal;
|
||||
|
||||
use brk_error::Result;
|
||||
use brk_types::{Height, Indexes};
|
||||
use brk_types::Indexes;
|
||||
use vecdb::Exit;
|
||||
|
||||
use crate::{distribution::state::UnrealizedState, prices};
|
||||
@@ -20,7 +20,7 @@ pub trait UnrealizedLike: Send + Sync {
|
||||
fn as_base(&self) -> &UnrealizedBase;
|
||||
fn as_base_mut(&mut self) -> &mut UnrealizedBase;
|
||||
fn min_stateful_len(&self) -> usize;
|
||||
fn truncate_push(&mut self, height: Height, state: &UnrealizedState) -> Result<()>;
|
||||
fn push_state(&mut self, state: &UnrealizedState);
|
||||
fn compute_rest(
|
||||
&mut self,
|
||||
prices: &prices::Vecs,
|
||||
@@ -44,8 +44,9 @@ impl UnrealizedLike for UnrealizedBase {
|
||||
fn min_stateful_len(&self) -> usize {
|
||||
self.min_stateful_len()
|
||||
}
|
||||
fn truncate_push(&mut self, height: Height, state: &UnrealizedState) -> Result<()> {
|
||||
self.truncate_push(height, state)
|
||||
#[inline(always)]
|
||||
fn push_state(&mut self, state: &UnrealizedState) {
|
||||
self.push_state(state);
|
||||
}
|
||||
fn compute_rest(
|
||||
&mut self,
|
||||
@@ -74,8 +75,9 @@ impl UnrealizedLike for UnrealizedFull {
|
||||
fn min_stateful_len(&self) -> usize {
|
||||
self.inner.min_stateful_len()
|
||||
}
|
||||
fn truncate_push(&mut self, height: Height, state: &UnrealizedState) -> Result<()> {
|
||||
self.truncate_push_all(height, state)
|
||||
#[inline(always)]
|
||||
fn push_state(&mut self, state: &UnrealizedState) {
|
||||
self.push_state_all(state);
|
||||
}
|
||||
fn compute_rest(
|
||||
&mut self,
|
||||
|
||||
@@ -89,6 +89,17 @@ where
|
||||
});
|
||||
|
||||
let start = index.to_usize();
|
||||
|
||||
// Truncate all vecs to start once, so the loop only pushes
|
||||
macro_rules! truncate_vec {
|
||||
($($vec:ident),*) => {
|
||||
$(if let Some(ref mut v) = $vec {
|
||||
v.truncate_if_needed_at(start)?;
|
||||
})*
|
||||
};
|
||||
}
|
||||
truncate_vec!(first, last, min, max, average, sum, cumulative, median, pct10, pct25, pct75, pct90);
|
||||
|
||||
let fi_len = first_indexes.len();
|
||||
let first_indexes_batch: Vec<A> = first_indexes.collect_range_at(start, fi_len);
|
||||
let count_indexes_batch: Vec<StoredU64> = count_indexes.collect_range_at(start, fi_len);
|
||||
@@ -97,8 +108,7 @@ where
|
||||
.into_iter()
|
||||
.zip(count_indexes_batch)
|
||||
.enumerate()
|
||||
.try_for_each(|(j, (first_index, count_index))| -> Result<()> {
|
||||
let idx = start + j;
|
||||
.try_for_each(|(_, (first_index, count_index))| -> Result<()> {
|
||||
let count = u64::from(count_index) as usize;
|
||||
|
||||
// Effective count after skipping (e.g., skip coinbase for fee calculations)
|
||||
@@ -113,17 +123,15 @@ where
|
||||
} else {
|
||||
T::from(0_usize)
|
||||
};
|
||||
first_vec.truncate_push_at(idx, f)?;
|
||||
first_vec.push(f);
|
||||
}
|
||||
|
||||
if let Some(ref mut last_vec) = last {
|
||||
if effective_count == 0 {
|
||||
// If all items skipped, use zero
|
||||
last_vec.truncate_push_at(idx, T::from(0_usize))?;
|
||||
last_vec.push(T::from(0_usize));
|
||||
} else {
|
||||
let last_index = first_index + (count - 1);
|
||||
let v = source.collect_one_at(last_index.to_usize()).unwrap();
|
||||
last_vec.truncate_push_at(idx, v)?;
|
||||
last_vec.push(source.collect_one_at(last_index.to_usize()).unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,12 +151,10 @@ where
|
||||
});
|
||||
|
||||
if let Some(ref mut min_vec) = min {
|
||||
let v = min_val.or(max_val).unwrap_or_else(|| T::from(0_usize));
|
||||
min_vec.truncate_push_at(idx, v)?;
|
||||
min_vec.push(min_val.or(max_val).unwrap_or_else(|| T::from(0_usize)));
|
||||
}
|
||||
if let Some(ref mut max_vec) = max {
|
||||
let v = max_val.or(min_val).unwrap_or_else(|| T::from(0_usize));
|
||||
max_vec.truncate_push_at(idx, v)?;
|
||||
max_vec.push(max_val.or(min_val).unwrap_or_else(|| T::from(0_usize)));
|
||||
}
|
||||
} else if needs_percentiles || needs_minmax {
|
||||
let mut values: Vec<T> = source.collect_range_at(
|
||||
@@ -157,21 +163,18 @@ where
|
||||
);
|
||||
|
||||
if values.is_empty() {
|
||||
// Handle edge case where all items were skipped
|
||||
macro_rules! push_zero {
|
||||
($($vec:ident),*) => {
|
||||
$(if let Some(ref mut v) = $vec {
|
||||
v.truncate_push_at(idx, T::from(0_usize))?;
|
||||
v.push(T::from(0_usize));
|
||||
})*
|
||||
};
|
||||
}
|
||||
push_zero!(max, pct90, pct75, median, pct25, pct10, min, average, sum);
|
||||
if let Some(ref mut cumulative_vec) = cumulative {
|
||||
let t = cumulative_val.unwrap();
|
||||
cumulative_vec.truncate_push_at(idx, t)?;
|
||||
cumulative_vec.push(cumulative_val.unwrap());
|
||||
}
|
||||
} else if needs_percentiles {
|
||||
// Compute aggregates from unsorted values first to avoid clone
|
||||
let aggregate_result = if needs_aggregates {
|
||||
let len = values.len();
|
||||
let sum_val = values.iter().copied().fold(T::from(0), |a, b| a + b);
|
||||
@@ -180,53 +183,52 @@ where
|
||||
None
|
||||
};
|
||||
|
||||
// Sort in-place — no clone needed
|
||||
values.sort_unstable();
|
||||
|
||||
if let Some(ref mut max_vec) = max {
|
||||
max_vec.truncate_push_at(idx, *values.last().unwrap())?;
|
||||
max_vec.push(*values.last().unwrap());
|
||||
}
|
||||
if let Some(ref mut pct90_vec) = pct90 {
|
||||
pct90_vec.truncate_push_at(idx, get_percentile(&values, 0.90))?;
|
||||
pct90_vec.push(get_percentile(&values, 0.90));
|
||||
}
|
||||
if let Some(ref mut pct75_vec) = pct75 {
|
||||
pct75_vec.truncate_push_at(idx, get_percentile(&values, 0.75))?;
|
||||
pct75_vec.push(get_percentile(&values, 0.75));
|
||||
}
|
||||
if let Some(ref mut median_vec) = median {
|
||||
median_vec.truncate_push_at(idx, get_percentile(&values, 0.50))?;
|
||||
median_vec.push(get_percentile(&values, 0.50));
|
||||
}
|
||||
if let Some(ref mut pct25_vec) = pct25 {
|
||||
pct25_vec.truncate_push_at(idx, get_percentile(&values, 0.25))?;
|
||||
pct25_vec.push(get_percentile(&values, 0.25));
|
||||
}
|
||||
if let Some(ref mut pct10_vec) = pct10 {
|
||||
pct10_vec.truncate_push_at(idx, get_percentile(&values, 0.10))?;
|
||||
pct10_vec.push(get_percentile(&values, 0.10));
|
||||
}
|
||||
if let Some(ref mut min_vec) = min {
|
||||
min_vec.truncate_push_at(idx, *values.first().unwrap())?;
|
||||
min_vec.push(*values.first().unwrap());
|
||||
}
|
||||
|
||||
if let Some((len, sum_val)) = aggregate_result {
|
||||
if let Some(ref mut average_vec) = average {
|
||||
average_vec.truncate_push_at(idx, sum_val / len)?;
|
||||
average_vec.push(sum_val / len);
|
||||
}
|
||||
|
||||
if needs_sum_or_cumulative {
|
||||
if let Some(ref mut sum_vec) = sum {
|
||||
sum_vec.truncate_push_at(idx, sum_val)?;
|
||||
sum_vec.push(sum_val);
|
||||
}
|
||||
if let Some(ref mut cumulative_vec) = cumulative {
|
||||
let t = cumulative_val.unwrap() + sum_val;
|
||||
cumulative_val.replace(t);
|
||||
cumulative_vec.truncate_push_at(idx, t)?;
|
||||
cumulative_vec.push(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if needs_minmax {
|
||||
if let Some(ref mut min_vec) = min {
|
||||
min_vec.truncate_push_at(idx, *values.iter().min().unwrap())?;
|
||||
min_vec.push(*values.iter().min().unwrap());
|
||||
}
|
||||
if let Some(ref mut max_vec) = max {
|
||||
max_vec.truncate_push_at(idx, *values.iter().max().unwrap())?;
|
||||
max_vec.push(*values.iter().max().unwrap());
|
||||
}
|
||||
|
||||
if needs_aggregates {
|
||||
@@ -234,23 +236,22 @@ where
|
||||
let sum_val = values.into_iter().fold(T::from(0), |a, b| a + b);
|
||||
|
||||
if let Some(ref mut average_vec) = average {
|
||||
average_vec.truncate_push_at(idx, sum_val / len)?;
|
||||
average_vec.push(sum_val / len);
|
||||
}
|
||||
|
||||
if needs_sum_or_cumulative {
|
||||
if let Some(ref mut sum_vec) = sum {
|
||||
sum_vec.truncate_push_at(idx, sum_val)?;
|
||||
sum_vec.push(sum_val);
|
||||
}
|
||||
if let Some(ref mut cumulative_vec) = cumulative {
|
||||
let t = cumulative_val.unwrap() + sum_val;
|
||||
cumulative_val.replace(t);
|
||||
cumulative_vec.truncate_push_at(idx, t)?;
|
||||
cumulative_vec.push(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if needs_aggregates {
|
||||
// Aggregates only (sum/average/cumulative) — no Vec allocation needed
|
||||
let efi = effective_first_index.to_usize();
|
||||
let (sum_val, len) = source.fold_range_at(
|
||||
efi,
|
||||
@@ -265,17 +266,17 @@ where
|
||||
} else {
|
||||
T::from(0_usize)
|
||||
};
|
||||
average_vec.truncate_push_at(idx, avg)?;
|
||||
average_vec.push(avg);
|
||||
}
|
||||
|
||||
if needs_sum_or_cumulative {
|
||||
if let Some(ref mut sum_vec) = sum {
|
||||
sum_vec.truncate_push_at(idx, sum_val)?;
|
||||
sum_vec.push(sum_val);
|
||||
}
|
||||
if let Some(ref mut cumulative_vec) = cumulative {
|
||||
let t = cumulative_val.unwrap() + sum_val;
|
||||
cumulative_val.replace(t);
|
||||
cumulative_vec.truncate_push_at(idx, t)?;
|
||||
cumulative_vec.push(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -348,6 +349,19 @@ where
|
||||
let zero = T::from(0_usize);
|
||||
let mut values: Vec<T> = Vec::new();
|
||||
|
||||
for vec in [
|
||||
&mut *min,
|
||||
&mut *max,
|
||||
&mut *average,
|
||||
&mut *median,
|
||||
&mut *pct10,
|
||||
&mut *pct25,
|
||||
&mut *pct75,
|
||||
&mut *pct90,
|
||||
] {
|
||||
vec.truncate_if_needed_at(start)?;
|
||||
}
|
||||
|
||||
count_indexes_batch
|
||||
.iter()
|
||||
.enumerate()
|
||||
@@ -378,7 +392,7 @@ where
|
||||
&mut *pct75,
|
||||
&mut *pct90,
|
||||
] {
|
||||
vec.truncate_push_at(idx, zero)?;
|
||||
vec.push(zero);
|
||||
}
|
||||
} else {
|
||||
source.collect_range_into_at(range_start_usize, range_end_usize, &mut values);
|
||||
@@ -390,14 +404,14 @@ where
|
||||
|
||||
values.sort_unstable();
|
||||
|
||||
max.truncate_push_at(idx, *values.last().unwrap())?;
|
||||
pct90.truncate_push_at(idx, get_percentile(&values, 0.90))?;
|
||||
pct75.truncate_push_at(idx, get_percentile(&values, 0.75))?;
|
||||
median.truncate_push_at(idx, get_percentile(&values, 0.50))?;
|
||||
pct25.truncate_push_at(idx, get_percentile(&values, 0.25))?;
|
||||
pct10.truncate_push_at(idx, get_percentile(&values, 0.10))?;
|
||||
min.truncate_push_at(idx, *values.first().unwrap())?;
|
||||
average.truncate_push_at(idx, avg)?;
|
||||
max.push(*values.last().unwrap());
|
||||
pct90.push(get_percentile(&values, 0.90));
|
||||
pct75.push(get_percentile(&values, 0.75));
|
||||
median.push(get_percentile(&values, 0.50));
|
||||
pct25.push(get_percentile(&values, 0.25));
|
||||
pct10.push(get_percentile(&values, 0.10));
|
||||
min.push(*values.first().unwrap());
|
||||
average.push(avg);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -107,9 +107,21 @@ where
|
||||
|
||||
let starts_batch = window_starts.collect_range_at(skip, end);
|
||||
|
||||
for v in [
|
||||
&mut *average_out,
|
||||
&mut *min_out,
|
||||
&mut *max_out,
|
||||
&mut *p10_out,
|
||||
&mut *p25_out,
|
||||
&mut *median_out,
|
||||
&mut *p75_out,
|
||||
&mut *p90_out,
|
||||
] {
|
||||
v.truncate_if_needed_at(skip)?;
|
||||
}
|
||||
|
||||
for (j, start) in starts_batch.into_iter().enumerate() {
|
||||
let i = skip + j;
|
||||
let v = partial_values[i - range_start];
|
||||
let v = partial_values[skip + j - range_start];
|
||||
let start_usize = start.to_usize();
|
||||
window.advance(v, start_usize, partial_values, range_start);
|
||||
|
||||
@@ -125,19 +137,19 @@ where
|
||||
&mut *p75_out,
|
||||
&mut *p90_out,
|
||||
] {
|
||||
v.truncate_push_at(i, zero)?;
|
||||
v.push(zero);
|
||||
}
|
||||
} else {
|
||||
average_out.truncate_push_at(i, T::from(window.average()))?;
|
||||
min_out.truncate_push_at(i, T::from(window.min()))?;
|
||||
max_out.truncate_push_at(i, T::from(window.max()))?;
|
||||
average_out.push(T::from(window.average()));
|
||||
min_out.push(T::from(window.min()));
|
||||
max_out.push(T::from(window.max()));
|
||||
let [p10, p25, p50, p75, p90] =
|
||||
window.percentiles(&[0.10, 0.25, 0.50, 0.75, 0.90]);
|
||||
p10_out.truncate_push_at(i, T::from(p10))?;
|
||||
p25_out.truncate_push_at(i, T::from(p25))?;
|
||||
median_out.truncate_push_at(i, T::from(p50))?;
|
||||
p75_out.truncate_push_at(i, T::from(p75))?;
|
||||
p90_out.truncate_push_at(i, T::from(p90))?;
|
||||
p10_out.push(T::from(p10));
|
||||
p25_out.push(T::from(p25));
|
||||
median_out.push(T::from(p50));
|
||||
p75_out.push(T::from(p75));
|
||||
p90_out.push(T::from(p90));
|
||||
}
|
||||
|
||||
if average_out.batch_limit_reached() {
|
||||
|
||||
@@ -35,6 +35,7 @@ impl RollingDistributionSlot {
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn compute(
|
||||
&mut self,
|
||||
max_from: Height,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use brk_error::Result;
|
||||
use brk_traversable::{Traversable, TreeNode};
|
||||
use brk_types::{Cents, Height, Version};
|
||||
use brk_types::{Cents, Version};
|
||||
use vecdb::{AnyExportableVec, Database, ReadOnlyClone, Ro, Rw, StorageMode, WritableVec};
|
||||
|
||||
use crate::indexes;
|
||||
@@ -38,16 +38,12 @@ impl PercentilesVecs {
|
||||
Ok(Self { vecs })
|
||||
}
|
||||
|
||||
/// Push percentile prices at this height (in cents).
|
||||
pub(crate) fn truncate_push(
|
||||
&mut self,
|
||||
height: Height,
|
||||
percentile_prices: &[Cents; PERCENTILES_LEN],
|
||||
) -> Result<()> {
|
||||
/// Push percentile prices (in cents).
|
||||
#[inline(always)]
|
||||
pub(crate) fn push(&mut self, percentile_prices: &[Cents; PERCENTILES_LEN]) {
|
||||
for (i, v) in self.vecs.iter_mut().enumerate() {
|
||||
v.cents.height.truncate_push(height, percentile_prices[i])?;
|
||||
v.cents.height.push(percentile_prices[i]);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validate computed versions or reset if mismatched.
|
||||
|
||||
@@ -46,12 +46,7 @@ impl RatioPerBlockPercentiles {
|
||||
|
||||
macro_rules! import_ratio {
|
||||
($suffix:expr) => {
|
||||
RatioPerBlock::forced_import_raw(
|
||||
db,
|
||||
&format!("{name}_{}", $suffix),
|
||||
v,
|
||||
indexes,
|
||||
)?
|
||||
RatioPerBlock::forced_import_raw(db, &format!("{name}_{}", $suffix), v, indexes)?
|
||||
};
|
||||
}
|
||||
|
||||
@@ -126,12 +121,15 @@ impl RatioPerBlockPercentiles {
|
||||
const PCTS: [f64; 6] = [0.01, 0.02, 0.05, 0.95, 0.98, 0.99];
|
||||
let mut out = [0u32; 6];
|
||||
|
||||
for (offset, &ratio) in new_ratios.iter().enumerate() {
|
||||
for vec in pct_vecs.iter_mut() {
|
||||
vec.truncate_if_needed_at(start)?;
|
||||
}
|
||||
|
||||
for &ratio in new_ratios.iter() {
|
||||
self.expanding_pct.add(*ratio);
|
||||
self.expanding_pct.quantiles(&PCTS, &mut out);
|
||||
let idx = start + offset;
|
||||
for (vec, &val) in pct_vecs.iter_mut().zip(out.iter()) {
|
||||
vec.truncate_push_at(idx, BasisPoints32::from(val))?;
|
||||
vec.push(BasisPoints32::from(val));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,9 +79,9 @@ where
|
||||
let cached = cached_start.clone();
|
||||
let starts_version = cached.version();
|
||||
|
||||
// Change: source[h] - source[ago] as C (via f64)
|
||||
// Absolute change: source[h] - source[ago] as C (via f64)
|
||||
let change_vec = LazyDeltaVec::<Height, S, C, DeltaChange>::new(
|
||||
&format!("{full_name}_change"),
|
||||
&full_name,
|
||||
version,
|
||||
src.clone(),
|
||||
starts_version,
|
||||
@@ -91,7 +91,7 @@ where
|
||||
},
|
||||
);
|
||||
let change_resolutions = Resolutions::forced_import(
|
||||
&format!("{full_name}_change"),
|
||||
&full_name,
|
||||
change_vec.read_only_boxed_clone(),
|
||||
version,
|
||||
indexes,
|
||||
@@ -102,15 +102,16 @@ where
|
||||
};
|
||||
|
||||
// Rate BPS: (source[h] - source[ago]) / source[ago] as B (via f64)
|
||||
let rate_bps_name = format!("{full_name}_rate_bps");
|
||||
let rate_vec = LazyDeltaVec::<Height, S, B, DeltaRate>::new(
|
||||
&format!("{full_name}_rate_bps"),
|
||||
&rate_bps_name,
|
||||
version,
|
||||
src.clone(),
|
||||
starts_version,
|
||||
move || cached.get(),
|
||||
);
|
||||
let rate_resolutions = Resolutions::forced_import(
|
||||
&format!("{full_name}_rate_bps"),
|
||||
&rate_bps_name,
|
||||
rate_vec.read_only_boxed_clone(),
|
||||
version,
|
||||
indexes,
|
||||
@@ -121,28 +122,30 @@ where
|
||||
};
|
||||
|
||||
// Ratio: bps / 10000
|
||||
let rate_ratio_name = format!("{full_name}_rate_ratio");
|
||||
let ratio = LazyPerBlock {
|
||||
height: LazyVecFrom1::transformed::<B::ToRatio>(
|
||||
&format!("{full_name}_rate_ratio"),
|
||||
&rate_ratio_name,
|
||||
version,
|
||||
bps.height.read_only_boxed_clone(),
|
||||
),
|
||||
resolutions: Box::new(DerivedResolutions::from_derived_computed::<B::ToRatio>(
|
||||
&format!("{full_name}_rate_ratio"),
|
||||
&rate_ratio_name,
|
||||
version,
|
||||
&bps.resolutions,
|
||||
)),
|
||||
};
|
||||
|
||||
// Percent: bps / 100
|
||||
let rate_name = format!("{full_name}_rate");
|
||||
let percent = LazyPerBlock {
|
||||
height: LazyVecFrom1::transformed::<B::ToPercent>(
|
||||
&format!("{full_name}_rate"),
|
||||
&rate_name,
|
||||
version,
|
||||
bps.height.read_only_boxed_clone(),
|
||||
),
|
||||
resolutions: Box::new(DerivedResolutions::from_derived_computed::<B::ToPercent>(
|
||||
&format!("{full_name}_rate"),
|
||||
&rate_name,
|
||||
version,
|
||||
&bps.resolutions,
|
||||
)),
|
||||
@@ -214,9 +217,10 @@ where
|
||||
let cached = cached_start.clone();
|
||||
let starts_version = cached.version();
|
||||
|
||||
// Change cents: source[h] - source[ago] as C (via f64)
|
||||
// Absolute change (cents): source[h] - source[ago] as C (via f64)
|
||||
let cents_name = format!("{full_name}_cents");
|
||||
let change_vec = LazyDeltaVec::<Height, S, C, DeltaChange>::new(
|
||||
&format!("{full_name}_change"),
|
||||
¢s_name,
|
||||
version,
|
||||
src.clone(),
|
||||
starts_version,
|
||||
@@ -226,7 +230,7 @@ where
|
||||
},
|
||||
);
|
||||
let change_resolutions = Resolutions::forced_import(
|
||||
&format!("{full_name}_change"),
|
||||
¢s_name,
|
||||
change_vec.read_only_boxed_clone(),
|
||||
version,
|
||||
indexes,
|
||||
@@ -236,15 +240,15 @@ where
|
||||
resolutions: Box::new(change_resolutions),
|
||||
};
|
||||
|
||||
// Change USD: lazy from cents delta
|
||||
// Absolute change (usd): lazy from cents delta
|
||||
let usd = LazyPerBlock {
|
||||
height: LazyVecFrom1::transformed::<C::ToDollars>(
|
||||
&format!("{full_name}_change_usd"),
|
||||
&full_name,
|
||||
version,
|
||||
cents.height.read_only_boxed_clone(),
|
||||
),
|
||||
resolutions: Box::new(DerivedResolutions::from_derived_computed::<C::ToDollars>(
|
||||
&format!("{full_name}_change_usd"),
|
||||
&full_name,
|
||||
version,
|
||||
¢s.resolutions,
|
||||
)),
|
||||
@@ -253,15 +257,16 @@ where
|
||||
let absolute = LazyDeltaFiatFromHeight { usd, cents };
|
||||
|
||||
// Rate BPS: (source[h] - source[ago]) / source[ago] as B (via f64)
|
||||
let rate_bps_name = format!("{full_name}_rate_bps");
|
||||
let rate_vec = LazyDeltaVec::<Height, S, B, DeltaRate>::new(
|
||||
&format!("{full_name}_rate_bps"),
|
||||
&rate_bps_name,
|
||||
version,
|
||||
src.clone(),
|
||||
starts_version,
|
||||
move || cached.get(),
|
||||
);
|
||||
let rate_resolutions = Resolutions::forced_import(
|
||||
&format!("{full_name}_rate_bps"),
|
||||
&rate_bps_name,
|
||||
rate_vec.read_only_boxed_clone(),
|
||||
version,
|
||||
indexes,
|
||||
@@ -271,27 +276,29 @@ where
|
||||
resolutions: Box::new(rate_resolutions),
|
||||
};
|
||||
|
||||
let rate_ratio_name = format!("{full_name}_rate_ratio");
|
||||
let ratio = LazyPerBlock {
|
||||
height: LazyVecFrom1::transformed::<B::ToRatio>(
|
||||
&format!("{full_name}_rate_ratio"),
|
||||
&rate_ratio_name,
|
||||
version,
|
||||
bps.height.read_only_boxed_clone(),
|
||||
),
|
||||
resolutions: Box::new(DerivedResolutions::from_derived_computed::<B::ToRatio>(
|
||||
&format!("{full_name}_rate_ratio"),
|
||||
&rate_ratio_name,
|
||||
version,
|
||||
&bps.resolutions,
|
||||
)),
|
||||
};
|
||||
|
||||
let rate_name = format!("{full_name}_rate");
|
||||
let percent = LazyPerBlock {
|
||||
height: LazyVecFrom1::transformed::<B::ToPercent>(
|
||||
&format!("{full_name}_rate"),
|
||||
&rate_name,
|
||||
version,
|
||||
bps.height.read_only_boxed_clone(),
|
||||
),
|
||||
resolutions: Box::new(DerivedResolutions::from_derived_computed::<B::ToPercent>(
|
||||
&format!("{full_name}_rate"),
|
||||
&rate_name,
|
||||
version,
|
||||
&bps.resolutions,
|
||||
)),
|
||||
|
||||
@@ -163,11 +163,11 @@ impl StdDevPerBlockExtended {
|
||||
0.5, 1.0, 1.5, 2.0, 2.5, 3.0, -0.5, -1.0, -1.5, -2.0, -2.5, -3.0,
|
||||
];
|
||||
for (vec, mult) in self.mut_band_height_vecs().zip(MULTIPLIERS) {
|
||||
vec.truncate_if_needed_at(start)?;
|
||||
for (offset, _) in source_data.iter().enumerate() {
|
||||
let index = start + offset;
|
||||
let average = sma_data[offset];
|
||||
let sd = sd_data[offset];
|
||||
vec.truncate_push_at(index, average + StoredF32::from(mult * *sd))?;
|
||||
vec.push(average + StoredF32::from(mult * *sd));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -144,12 +144,14 @@ impl Vecs {
|
||||
first_tx_index_cursor.advance(min);
|
||||
let mut output_count_cursor = indexes.tx_index.output_count.cursor();
|
||||
|
||||
self.height_to_pool.truncate_if_needed_at(min)?;
|
||||
|
||||
indexer
|
||||
.stores
|
||||
.height_to_coinbase_tag
|
||||
.iter()
|
||||
.skip(min)
|
||||
.try_for_each(|(height, coinbase_tag)| -> Result<()> {
|
||||
.try_for_each(|(_, coinbase_tag)| -> Result<()> {
|
||||
let tx_index = first_tx_index_cursor.next().unwrap();
|
||||
let out_start = first_txout_index.get(tx_index.to_usize());
|
||||
|
||||
@@ -179,7 +181,7 @@ impl Vecs {
|
||||
.or_else(|| self.pools.find_from_coinbase_tag(&coinbase_tag))
|
||||
.unwrap_or(unknown);
|
||||
|
||||
self.height_to_pool.truncate_push(height, pool.slug)?;
|
||||
self.height_to_pool.push(pool.slug);
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ use brk_types::{BlkPosition, Height, Indexes, TxIndex, Version};
|
||||
use tracing::info;
|
||||
use vecdb::{
|
||||
AnyStoredVec, AnyVec, Database, Exit, ImportableVec, PcoVec, ReadableVec, Rw, StorageMode,
|
||||
VecIndex, WritableVec,
|
||||
WritableVec,
|
||||
};
|
||||
|
||||
use crate::internal::db_utils::{finalize_db, open_db};
|
||||
@@ -102,10 +102,15 @@ impl Vecs {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
// Cursor avoids per-height PcoVec page decompression.
|
||||
// Heights are sequential, so the cursor only advances forward.
|
||||
let mut first_tx_index_cursor = indexer.vecs.transactions.first_tx_index.cursor();
|
||||
first_tx_index_cursor.advance(min_height.to_usize());
|
||||
let first_tx_at_min_height = indexer
|
||||
.vecs
|
||||
.transactions
|
||||
.first_tx_index
|
||||
.collect_one(min_height)
|
||||
.unwrap();
|
||||
|
||||
self.block.truncate_if_needed(min_height)?;
|
||||
self.tx.truncate_if_needed(first_tx_at_min_height)?;
|
||||
|
||||
parser
|
||||
.read(
|
||||
@@ -114,23 +119,13 @@ impl Vecs {
|
||||
)
|
||||
.iter()
|
||||
.try_for_each(|block| -> Result<()> {
|
||||
let height = block.height();
|
||||
self.block.push(block.metadata().position());
|
||||
|
||||
self.block
|
||||
.truncate_push(height, block.metadata().position())?;
|
||||
block.tx_metadata().iter().for_each(|metadata| {
|
||||
self.tx.push(metadata.position());
|
||||
});
|
||||
|
||||
let tx_index = first_tx_index_cursor.next().unwrap();
|
||||
|
||||
block.tx_metadata().iter().enumerate().try_for_each(
|
||||
|(index, metadata)| -> Result<()> {
|
||||
let tx_index = tx_index + index;
|
||||
self.tx
|
||||
.truncate_push(tx_index, metadata.position())?;
|
||||
Ok(())
|
||||
},
|
||||
)?;
|
||||
|
||||
if *height % 1_000 == 0 {
|
||||
if *block.height() % 1_000 == 0 {
|
||||
let _lock = exit.lock();
|
||||
self.block.flush()?;
|
||||
self.tx.flush()?;
|
||||
|
||||
@@ -52,9 +52,10 @@ impl Vecs {
|
||||
let mut output_types_buf: Vec<OutputType> = Vec::new();
|
||||
let mut values_buf: Vec<Sats> = Vec::new();
|
||||
|
||||
height_vec.truncate_if_needed(starting_height)?;
|
||||
|
||||
// Iterate blocks
|
||||
for h in starting_height.to_usize()..=target_height.to_usize() {
|
||||
let height = Height::from(h);
|
||||
let local_idx = h - starting_height.to_usize();
|
||||
|
||||
// Get output range for this block
|
||||
@@ -88,7 +89,7 @@ impl Vecs {
|
||||
}
|
||||
}
|
||||
|
||||
height_vec.truncate_push(height, op_return_value)?;
|
||||
height_vec.push(op_return_value);
|
||||
}
|
||||
|
||||
height_vec.write()?;
|
||||
|
||||
@@ -37,6 +37,7 @@ impl Vecs {
|
||||
let start = starting_height.to_usize();
|
||||
let end = target_height.to_usize() + 1;
|
||||
let unclaimed_data = unclaimed_height.collect_range_at(start, end);
|
||||
height_vec.truncate_if_needed(starting_height)?;
|
||||
op_return_height.fold_range_at(start, end, start, |idx, op_return| {
|
||||
let unclaimed = unclaimed_data[idx - start];
|
||||
let genesis = if idx == 0 {
|
||||
@@ -45,9 +46,7 @@ impl Vecs {
|
||||
Sats::ZERO
|
||||
};
|
||||
let unspendable = genesis + op_return + unclaimed;
|
||||
height_vec
|
||||
.truncate_push(Height::from(idx), unspendable)
|
||||
.unwrap();
|
||||
height_vec.push(unspendable);
|
||||
idx + 1
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2968,10 +2968,10 @@ function create_1m1w1y2wPattern(client, acc) {
|
||||
*/
|
||||
function create_1m1w1y24hPattern3(client, acc) {
|
||||
return {
|
||||
_1m: createCentsUsdPattern(client, _m(acc, '1m_change')),
|
||||
_1w: createCentsUsdPattern(client, _m(acc, '1w_change')),
|
||||
_1y: createCentsUsdPattern(client, _m(acc, '1y_change')),
|
||||
_24h: createCentsUsdPattern(client, _m(acc, '24h_change')),
|
||||
_1m: createCentsUsdPattern(client, _m(acc, '1m')),
|
||||
_1w: createCentsUsdPattern(client, _m(acc, '1w')),
|
||||
_1y: createCentsUsdPattern(client, _m(acc, '1y')),
|
||||
_24h: createCentsUsdPattern(client, _m(acc, '24h')),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3910,8 +3910,8 @@ function createCentsUsdPattern2(client, acc) {
|
||||
*/
|
||||
function createCentsUsdPattern(client, acc) {
|
||||
return {
|
||||
cents: createMetricPattern1(client, acc),
|
||||
usd: createMetricPattern1(client, _m(acc, 'usd')),
|
||||
cents: createMetricPattern1(client, _m(acc, 'cents')),
|
||||
usd: createMetricPattern1(client, acc),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -2729,10 +2729,10 @@ class _1m1w1y24hPattern3:
|
||||
|
||||
def __init__(self, client: BrkClientBase, acc: str):
|
||||
"""Create pattern node with accumulated metric name."""
|
||||
self._1m: CentsUsdPattern = CentsUsdPattern(client, _m(acc, '1m_change'))
|
||||
self._1w: CentsUsdPattern = CentsUsdPattern(client, _m(acc, '1w_change'))
|
||||
self._1y: CentsUsdPattern = CentsUsdPattern(client, _m(acc, '1y_change'))
|
||||
self._24h: CentsUsdPattern = CentsUsdPattern(client, _m(acc, '24h_change'))
|
||||
self._1m: CentsUsdPattern = CentsUsdPattern(client, _m(acc, '1m'))
|
||||
self._1w: CentsUsdPattern = CentsUsdPattern(client, _m(acc, '1w'))
|
||||
self._1y: CentsUsdPattern = CentsUsdPattern(client, _m(acc, '1y'))
|
||||
self._24h: CentsUsdPattern = CentsUsdPattern(client, _m(acc, '24h'))
|
||||
|
||||
class _1m1w1y24hPattern4:
|
||||
"""Pattern struct for repeated tree structure."""
|
||||
@@ -3132,8 +3132,8 @@ class CentsUsdPattern:
|
||||
|
||||
def __init__(self, client: BrkClientBase, acc: str):
|
||||
"""Create pattern node with accumulated metric name."""
|
||||
self.cents: MetricPattern1[CentsSigned] = MetricPattern1(client, acc)
|
||||
self.usd: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'usd'))
|
||||
self.cents: MetricPattern1[CentsSigned] = MetricPattern1(client, _m(acc, 'cents'))
|
||||
self.usd: MetricPattern1[Dollars] = MetricPattern1(client, acc)
|
||||
|
||||
class CoindaysSentPattern:
|
||||
"""Pattern struct for repeated tree structure."""
|
||||
|
||||
@@ -2,7 +2,7 @@ import { colors } from "../utils/colors.js";
|
||||
import { brk } from "../client.js";
|
||||
import { Unit } from "../utils/units.js";
|
||||
import { dots, line, baseline, price, rollingWindowsTree, percentRatioDots } from "./series.js";
|
||||
import { satsBtcUsd } from "./shared.js";
|
||||
import { satsBtcUsd, priceRatioPercentilesTree } from "./shared.js";
|
||||
|
||||
/**
|
||||
* Create Cointime section
|
||||
@@ -173,57 +173,18 @@ export function createCointimeSection() {
|
||||
),
|
||||
],
|
||||
},
|
||||
...prices.map(({ pattern, name, color }) => {
|
||||
const p = pattern.percentiles;
|
||||
const pctUsd = /** @type {const} */ ([
|
||||
{ name: "pct95", prop: p.pct95.price, color: colors.ratioPct._95 },
|
||||
{ name: "pct5", prop: p.pct5.price, color: colors.ratioPct._5 },
|
||||
{ name: "pct98", prop: p.pct98.price, color: colors.ratioPct._98 },
|
||||
{ name: "pct2", prop: p.pct2.price, color: colors.ratioPct._2 },
|
||||
{ name: "pct99", prop: p.pct99.price, color: colors.ratioPct._99 },
|
||||
{ name: "pct1", prop: p.pct1.price, color: colors.ratioPct._1 },
|
||||
]);
|
||||
const pctRatio = /** @type {const} */ ([
|
||||
{ name: "pct95", prop: p.pct95.ratio, color: colors.ratioPct._95 },
|
||||
{ name: "pct5", prop: p.pct5.ratio, color: colors.ratioPct._5 },
|
||||
{ name: "pct98", prop: p.pct98.ratio, color: colors.ratioPct._98 },
|
||||
{ name: "pct2", prop: p.pct2.ratio, color: colors.ratioPct._2 },
|
||||
{ name: "pct99", prop: p.pct99.ratio, color: colors.ratioPct._99 },
|
||||
{ name: "pct1", prop: p.pct1.ratio, color: colors.ratioPct._1 },
|
||||
]);
|
||||
return {
|
||||
...prices.map(({ pattern, name, color }) => ({
|
||||
name,
|
||||
tree: [
|
||||
{
|
||||
name: "Price",
|
||||
tree: priceRatioPercentilesTree({
|
||||
pattern,
|
||||
title: `${name} Price`,
|
||||
top: [
|
||||
price({ metric: pattern, name, color }),
|
||||
price({
|
||||
metric: all.realized.price,
|
||||
name: "Realized",
|
||||
color: colors.realized,
|
||||
defaultActive: false,
|
||||
}),
|
||||
...pctUsd.map(({ name: pName, prop, color: pColor }) =>
|
||||
price({ metric: prop, name: pName, color: pColor, defaultActive: false, options: { lineStyle: 1 } }),
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Ratio",
|
||||
title: `${name} Price Ratio`,
|
||||
top: [price({ metric: pattern, name, color })],
|
||||
bottom: [
|
||||
baseline({ metric: pattern.ratio, name: "Ratio", unit: Unit.ratio, base: 1 }),
|
||||
...pctRatio.map(({ name: pName, prop, color: pColor }) =>
|
||||
line({ metric: prop, name: pName, color: pColor, defaultActive: false, unit: Unit.ratio, options: { lineStyle: 1 } }),
|
||||
),
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
legend: name,
|
||||
color,
|
||||
priceReferences: [
|
||||
price({ metric: all.realized.price, name: "Realized", color: colors.realized, defaultActive: false }),
|
||||
],
|
||||
}),
|
||||
})),
|
||||
],
|
||||
},
|
||||
|
||||
|
||||
@@ -33,6 +33,9 @@ export function buildCohortData() {
|
||||
AMOUNT_RANGE_NAMES,
|
||||
SPENDABLE_TYPE_NAMES,
|
||||
CLASS_NAMES,
|
||||
PROFITABILITY_RANGE_NAMES,
|
||||
PROFIT_NAMES,
|
||||
LOSS_NAMES,
|
||||
} = brk;
|
||||
|
||||
const cohortAll = {
|
||||
@@ -191,6 +194,28 @@ export function buildCohortData() {
|
||||
tree: utxoCohorts.class[key],
|
||||
}));
|
||||
|
||||
const { range, profit, loss } = utxoCohorts.profitability;
|
||||
|
||||
const profitabilityRange = entries(PROFITABILITY_RANGE_NAMES).map(
|
||||
([key, names], i, arr) => ({
|
||||
name: names.short,
|
||||
color: colors.at(i, arr.length),
|
||||
pattern: range[key],
|
||||
}),
|
||||
);
|
||||
|
||||
const profitabilityProfit = entries(PROFIT_NAMES).map(([key, names], i, arr) => ({
|
||||
name: names.short,
|
||||
color: colors.at(i, arr.length),
|
||||
pattern: profit[key],
|
||||
}));
|
||||
|
||||
const profitabilityLoss = entries(LOSS_NAMES).map(([key, names], i, arr) => ({
|
||||
name: names.short,
|
||||
color: colors.at(i, arr.length),
|
||||
pattern: loss[key],
|
||||
}));
|
||||
|
||||
return {
|
||||
cohortAll,
|
||||
termShort,
|
||||
@@ -208,5 +233,8 @@ export function buildCohortData() {
|
||||
typeAddressable,
|
||||
typeOther,
|
||||
class: class_,
|
||||
profitabilityRange,
|
||||
profitabilityProfit,
|
||||
profitabilityLoss,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
* - activity.js: SOPR, Volume, Lifespan
|
||||
*/
|
||||
|
||||
import { formatCohortTitle, satsBtcUsd } from "../shared.js";
|
||||
import { formatCohortTitle, satsBtcUsd, satsBtcUsdFullTree, simplePriceRatioTree, groupedSimplePriceRatioTree } from "../shared.js";
|
||||
import { ROLLING_WINDOWS, line, baseline, percentRatio, rollingWindowsTree, rollingPercentRatioTree } from "../series.js";
|
||||
import { Unit } from "../../utils/units.js";
|
||||
|
||||
// Section builders
|
||||
import {
|
||||
@@ -205,8 +207,11 @@ export function createCohortFolderAgeRangeWithMatured(cohort) {
|
||||
const title = formatCohortTitle(cohort.name);
|
||||
folder.tree.push({
|
||||
name: "Matured",
|
||||
tree: satsBtcUsdFullTree({
|
||||
pattern: cohort.matured,
|
||||
name: cohort.name,
|
||||
title: title("Matured Supply"),
|
||||
bottom: satsBtcUsd({ pattern: cohort.matured, name: cohort.name }),
|
||||
}),
|
||||
});
|
||||
return folder;
|
||||
}
|
||||
@@ -452,7 +457,7 @@ export function createGroupedCohortFolderAgeRangeWithMatured({
|
||||
name: "Matured",
|
||||
title: title("Matured Supply"),
|
||||
bottom: list.flatMap((cohort) =>
|
||||
satsBtcUsd({ pattern: cohort.matured, name: cohort.name, color: cohort.color }),
|
||||
satsBtcUsd({ pattern: cohort.matured.base, name: cohort.name, color: cohort.color }),
|
||||
),
|
||||
});
|
||||
return folder;
|
||||
@@ -580,3 +585,212 @@ export function createGroupedAddressCohortFolder({
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// UTXO Profitability Folder Builders
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @param {{ name: string, color: Color, pattern: RealizedSupplyPattern }} bucket
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
function singleBucketFolder({ name, color, pattern }) {
|
||||
return {
|
||||
name,
|
||||
tree: [
|
||||
{
|
||||
name: "Supply",
|
||||
tree: [
|
||||
{
|
||||
name: "All",
|
||||
title: `${name}: Supply`,
|
||||
bottom: satsBtcUsd({ pattern: pattern.supply.all, name, color }),
|
||||
},
|
||||
{
|
||||
name: "STH",
|
||||
title: `${name}: STH Supply`,
|
||||
bottom: satsBtcUsd({ pattern: pattern.supply.sth, name, color }),
|
||||
},
|
||||
{
|
||||
name: "Change",
|
||||
tree: [
|
||||
{ ...rollingWindowsTree({ windows: pattern.supply.all.delta.absolute, title: `${name}: Supply Change`, unit: Unit.sats, series: baseline }), name: "Absolute" },
|
||||
{ ...rollingPercentRatioTree({ windows: pattern.supply.all.delta.rate, title: `${name}: Supply Rate` }), name: "Rate" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Realized Cap",
|
||||
tree: [
|
||||
{
|
||||
name: "All",
|
||||
title: `${name}: Realized Cap`,
|
||||
bottom: [line({ metric: pattern.realizedCap.all, name, color, unit: Unit.usd })],
|
||||
},
|
||||
{
|
||||
name: "STH",
|
||||
title: `${name}: STH Realized Cap`,
|
||||
bottom: [line({ metric: pattern.realizedCap.sth, name, color, unit: Unit.usd })],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Realized Price",
|
||||
tree: simplePriceRatioTree({
|
||||
pattern: pattern.realizedPrice,
|
||||
title: `${name}: Realized Price`,
|
||||
legend: name,
|
||||
color,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "NUPL",
|
||||
title: `${name}: NUPL`,
|
||||
bottom: [line({ metric: pattern.nupl.ratio, name, color, unit: Unit.ratio })],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{ name: string, color: Color, pattern: RealizedSupplyPattern }[]} list
|
||||
* @param {string} titlePrefix
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function groupedBucketCharts(list, titlePrefix) {
|
||||
return [
|
||||
{
|
||||
name: "Supply",
|
||||
tree: [
|
||||
{
|
||||
name: "All",
|
||||
title: `${titlePrefix}: Supply`,
|
||||
bottom: list.flatMap(({ name, color, pattern }) =>
|
||||
satsBtcUsd({ pattern: pattern.supply.all, name, color }),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "STH",
|
||||
title: `${titlePrefix}: STH Supply`,
|
||||
bottom: list.flatMap(({ name, color, pattern }) =>
|
||||
satsBtcUsd({ pattern: pattern.supply.sth, name, color }),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Change",
|
||||
tree: [
|
||||
{
|
||||
name: "Absolute",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: `${titlePrefix}: Supply Change`,
|
||||
bottom: ROLLING_WINDOWS.flatMap((w) =>
|
||||
list.map(({ name, color, pattern }) =>
|
||||
baseline({ metric: pattern.supply.all.delta.absolute[w.key], name: `${name} ${w.name}`, color, unit: Unit.sats }),
|
||||
),
|
||||
),
|
||||
},
|
||||
...ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: `${titlePrefix}: Supply Change ${w.name}`,
|
||||
bottom: list.map(({ name, color, pattern }) =>
|
||||
baseline({ metric: pattern.supply.all.delta.absolute[w.key], name, color, unit: Unit.sats }),
|
||||
),
|
||||
})),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Rate",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: `${titlePrefix}: Supply Rate`,
|
||||
bottom: ROLLING_WINDOWS.flatMap((w) =>
|
||||
list.flatMap(({ name, color, pattern }) =>
|
||||
percentRatio({ pattern: pattern.supply.all.delta.rate[w.key], name: `${name} ${w.name}`, color }),
|
||||
),
|
||||
),
|
||||
},
|
||||
...ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: `${titlePrefix}: Supply Rate ${w.name}`,
|
||||
bottom: list.flatMap(({ name, color, pattern }) =>
|
||||
percentRatio({ pattern: pattern.supply.all.delta.rate[w.key], name, color }),
|
||||
),
|
||||
})),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Realized Cap",
|
||||
tree: [
|
||||
{
|
||||
name: "All",
|
||||
title: `${titlePrefix}: Realized Cap`,
|
||||
bottom: list.map(({ name, color, pattern }) =>
|
||||
line({ metric: pattern.realizedCap.all, name, color, unit: Unit.usd }),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "STH",
|
||||
title: `${titlePrefix}: STH Realized Cap`,
|
||||
bottom: list.map(({ name, color, pattern }) =>
|
||||
line({ metric: pattern.realizedCap.sth, name, color, unit: Unit.usd }),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Realized Price",
|
||||
tree: groupedSimplePriceRatioTree({
|
||||
list: list.map(({ name, color, pattern }) => ({ name, color, pattern: pattern.realizedPrice })),
|
||||
title: `${titlePrefix}: Realized Price`,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "NUPL",
|
||||
title: `${titlePrefix}: NUPL`,
|
||||
bottom: list.map(({ name, color, pattern }) =>
|
||||
line({ metric: pattern.nupl.ratio, name, color, unit: Unit.ratio }),
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{ range: { name: string, color: Color, pattern: RealizedSupplyPattern }[], profit: { name: string, color: Color, pattern: RealizedSupplyPattern }[], loss: { name: string, color: Color, pattern: RealizedSupplyPattern }[] }} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createUtxoProfitabilitySection({ range, profit, loss }) {
|
||||
return {
|
||||
name: "UTXO Profitability",
|
||||
tree: [
|
||||
{
|
||||
name: "Range",
|
||||
tree: [
|
||||
{ name: "Compare", tree: groupedBucketCharts(range, "Profitability Range") },
|
||||
...range.map(singleBucketFolder),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "In Profit",
|
||||
tree: [
|
||||
{ name: "Compare", tree: groupedBucketCharts(profit, "In Profit") },
|
||||
...profit.map(singleBucketFolder),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "In Loss",
|
||||
tree: [
|
||||
{ name: "Compare", tree: groupedBucketCharts(loss, "In Loss") },
|
||||
...loss.map(singleBucketFolder),
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
*/
|
||||
|
||||
import { colors } from "../../utils/colors.js";
|
||||
import { createPriceRatioCharts, mapCohortsWithAll } from "../shared.js";
|
||||
import { createPriceRatioCharts, mapCohortsWithAll, priceRatioPercentilesTree } from "../shared.js";
|
||||
import { baseline, price } from "../series.js";
|
||||
import { Unit } from "../../utils/units.js";
|
||||
|
||||
@@ -53,26 +53,12 @@ export function createPricesSectionFull({ cohort, title }) {
|
||||
},
|
||||
{
|
||||
name: "Investor",
|
||||
tree: [
|
||||
{
|
||||
name: "Price",
|
||||
tree: priceRatioPercentilesTree({
|
||||
pattern: tree.realized.investor.price,
|
||||
title: title("Investor Price"),
|
||||
top: [price({ metric: tree.realized.investor.price, name: "Investor", color })],
|
||||
},
|
||||
{
|
||||
name: "Ratio",
|
||||
title: title("Investor Price Ratio"),
|
||||
top: [price({ metric: tree.realized.investor.price, name: "Investor", color })],
|
||||
bottom: [
|
||||
baseline({
|
||||
metric: tree.realized.investor.price.ratio,
|
||||
name: "Ratio",
|
||||
unit: Unit.ratio,
|
||||
base: 1,
|
||||
legend: "Investor",
|
||||
color,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
/** UTXO Profitability section — range bands, cumulative profit/loss thresholds */
|
||||
|
||||
import { colors } from "../../utils/colors.js";
|
||||
import { entries } from "../../utils/array.js";
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { line, price } from "../series.js";
|
||||
import { brk } from "../../client.js";
|
||||
import { satsBtcUsd } from "../shared.js";
|
||||
|
||||
/**
|
||||
* @param {{ name: string, color: Color, pattern: RealizedSupplyPattern }[]} list
|
||||
* @param {string} titlePrefix
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function bucketCharts(list, titlePrefix) {
|
||||
return [
|
||||
{
|
||||
name: "Supply",
|
||||
title: `${titlePrefix}: Supply`,
|
||||
bottom: list.flatMap(({ name, color, pattern }) =>
|
||||
satsBtcUsd({ pattern: pattern.supply, name, color }),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Realized Cap",
|
||||
title: `${titlePrefix}: Realized Cap`,
|
||||
bottom: list.map(({ name, color, pattern }) =>
|
||||
line({ metric: pattern.realizedCap, name, color, unit: Unit.usd }),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Realized Price",
|
||||
title: `${titlePrefix}: Realized Price`,
|
||||
top: list.map(({ name, color, pattern }) =>
|
||||
price({ metric: pattern.realizedPrice, name, color }),
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createUtxoProfitabilitySection() {
|
||||
const { range, profit, loss } = brk.metrics.cohorts.utxo.profitability;
|
||||
const {
|
||||
PROFITABILITY_RANGE_NAMES,
|
||||
PROFIT_NAMES,
|
||||
LOSS_NAMES,
|
||||
} = brk;
|
||||
|
||||
const rangeList = entries(PROFITABILITY_RANGE_NAMES).map(
|
||||
([key, names], i, arr) => ({
|
||||
name: names.short,
|
||||
color: colors.at(i, arr.length),
|
||||
pattern: range[key],
|
||||
}),
|
||||
);
|
||||
|
||||
const profitList = entries(PROFIT_NAMES).map(([key, names], i, arr) => ({
|
||||
name: names.short,
|
||||
color: colors.at(i, arr.length),
|
||||
pattern: profit[key],
|
||||
}));
|
||||
|
||||
const lossList = entries(LOSS_NAMES).map(([key, names], i, arr) => ({
|
||||
name: names.short,
|
||||
color: colors.at(i, arr.length),
|
||||
pattern: loss[key],
|
||||
}));
|
||||
|
||||
return {
|
||||
name: "UTXO Profitability",
|
||||
tree: [
|
||||
{
|
||||
name: "Range",
|
||||
tree: bucketCharts(rangeList, "Profitability Range"),
|
||||
},
|
||||
{
|
||||
name: "In Profit",
|
||||
tree: bucketCharts(profitList, "In Profit"),
|
||||
},
|
||||
{
|
||||
name: "In Loss",
|
||||
tree: bucketCharts(lossList, "In Loss"),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
percentRatioBaseline,
|
||||
ROLLING_WINDOWS,
|
||||
} from "./series.js";
|
||||
import { simplePriceRatioTree } from "./shared.js";
|
||||
import { periodIdToName } from "./utils.js";
|
||||
|
||||
/**
|
||||
@@ -68,26 +69,12 @@ function createMaSubSection(label, averages) {
|
||||
/** @param {MaPeriod} a */
|
||||
const toFolder = (a) => ({
|
||||
name: periodIdToName(a.id, true),
|
||||
tree: [
|
||||
{
|
||||
name: "Price",
|
||||
tree: simplePriceRatioTree({
|
||||
pattern: a.ratio,
|
||||
title: `${periodIdToName(a.id, true)} ${label}`,
|
||||
top: [price({ metric: a.ratio, name: "average", color: a.color })],
|
||||
},
|
||||
{
|
||||
name: "Ratio",
|
||||
title: `${periodIdToName(a.id, true)} ${label} Ratio`,
|
||||
top: [price({ metric: a.ratio, name: "average", color: a.color })],
|
||||
bottom: [
|
||||
baseline({
|
||||
metric: a.ratio.ratio,
|
||||
name: "Ratio",
|
||||
legend: "average",
|
||||
color: a.color,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
@@ -19,8 +19,8 @@ import {
|
||||
createGroupedCohortFolderBasicWithoutMarketCap,
|
||||
createGroupedCohortFolderAddress,
|
||||
createGroupedAddressCohortFolder,
|
||||
createUtxoProfitabilitySection,
|
||||
} from "./distribution/index.js";
|
||||
import { createUtxoProfitabilitySection } from "./distribution/utxo-profitability.js";
|
||||
import { createMarketSection } from "./market.js";
|
||||
import { createNetworkSection } from "./network.js";
|
||||
import { createMiningSection } from "./mining.js";
|
||||
@@ -53,6 +53,9 @@ export function createPartialOptions() {
|
||||
typeAddressable,
|
||||
typeOther,
|
||||
class: class_,
|
||||
profitabilityRange,
|
||||
profitabilityProfit,
|
||||
profitabilityLoss,
|
||||
} = buildCohortData();
|
||||
|
||||
return [
|
||||
@@ -92,7 +95,7 @@ export function createPartialOptions() {
|
||||
|
||||
// Ages cohorts
|
||||
{
|
||||
name: "UTXO Ages",
|
||||
name: "UTXO Age",
|
||||
tree: [
|
||||
// Younger Than (< X old)
|
||||
{
|
||||
@@ -138,7 +141,7 @@ export function createPartialOptions() {
|
||||
|
||||
// Sizes cohorts (UTXO size)
|
||||
{
|
||||
name: "UTXO Sizes",
|
||||
name: "UTXO Size",
|
||||
tree: [
|
||||
// Less Than (< X sats)
|
||||
{
|
||||
@@ -184,7 +187,7 @@ export function createPartialOptions() {
|
||||
|
||||
// Balances cohorts (Address balance)
|
||||
{
|
||||
name: "Address Balances",
|
||||
name: "Address Balance",
|
||||
tree: [
|
||||
// Less Than (< X sats)
|
||||
{
|
||||
@@ -230,11 +233,11 @@ export function createPartialOptions() {
|
||||
|
||||
// Script Types - addressable types have addrCount, others don't
|
||||
{
|
||||
name: "Script Types",
|
||||
name: "Script Type",
|
||||
tree: [
|
||||
createGroupedCohortFolderAddress({
|
||||
name: "Compare",
|
||||
title: "Script Types",
|
||||
title: "Script Type",
|
||||
list: typeAddressable,
|
||||
all: cohortAll,
|
||||
}),
|
||||
@@ -245,11 +248,11 @@ export function createPartialOptions() {
|
||||
|
||||
// Epochs
|
||||
{
|
||||
name: "Epochs",
|
||||
name: "Epoch",
|
||||
tree: [
|
||||
createGroupedCohortFolderWithAdjusted({
|
||||
name: "Compare",
|
||||
title: "Epochs",
|
||||
title: "Epoch",
|
||||
list: epoch,
|
||||
all: cohortAll,
|
||||
}),
|
||||
@@ -257,13 +260,13 @@ export function createPartialOptions() {
|
||||
],
|
||||
},
|
||||
|
||||
// Years
|
||||
// Classes
|
||||
{
|
||||
name: "Years",
|
||||
name: "Class",
|
||||
tree: [
|
||||
createGroupedCohortFolderWithAdjusted({
|
||||
name: "Compare",
|
||||
title: "Years",
|
||||
title: "Class",
|
||||
list: class_,
|
||||
all: cohortAll,
|
||||
}),
|
||||
@@ -272,7 +275,11 @@ export function createPartialOptions() {
|
||||
},
|
||||
|
||||
// UTXO Profitability bands
|
||||
createUtxoProfitabilitySection(),
|
||||
createUtxoProfitabilitySection({
|
||||
range: profitabilityRange,
|
||||
profit: profitabilityProfit,
|
||||
loss: profitabilityLoss,
|
||||
}),
|
||||
],
|
||||
},
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/** Shared helpers for options */
|
||||
|
||||
import { Unit } from "../utils/units.js";
|
||||
import { line, baseline, price } from "./series.js";
|
||||
import { line, baseline, price, ROLLING_WINDOWS } from "./series.js";
|
||||
import { priceLine, priceLines } from "./constants.js";
|
||||
import { colors } from "../utils/colors.js";
|
||||
|
||||
@@ -234,6 +234,159 @@ export function satsBtcUsdRolling({ pattern, name, color, defaultActive }) {
|
||||
return satsBtcUsd({ pattern, name, color, defaultActive });
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a full Sum / Rolling / Cumulative tree from a FullValuePattern
|
||||
* @param {Object} args
|
||||
* @param {FullValuePattern} args.pattern
|
||||
* @param {string} args.name
|
||||
* @param {string} args.title
|
||||
* @param {Color} [args.color]
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
export function satsBtcUsdFullTree({ pattern, name, title, color }) {
|
||||
return [
|
||||
{
|
||||
name: "Sum",
|
||||
title,
|
||||
bottom: satsBtcUsd({ pattern: pattern.base, name, color }),
|
||||
},
|
||||
{
|
||||
name: "Rolling",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: `${title} Rolling Sum`,
|
||||
bottom: ROLLING_WINDOWS.flatMap((w) =>
|
||||
satsBtcUsd({ pattern: pattern.sum[w.key], name: w.name, color: w.color }),
|
||||
),
|
||||
},
|
||||
...ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: `${title} ${w.name} Rolling Sum`,
|
||||
bottom: satsBtcUsd({ pattern: pattern.sum[w.key], name: w.name, color: w.color }),
|
||||
})),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: `${title} (Total)`,
|
||||
bottom: satsBtcUsd({ pattern: pattern.cumulative, name: "all-time", color }),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Price + Ratio charts from a simple price pattern (BpsCentsRatioSatsUsdPattern)
|
||||
* @param {Object} args
|
||||
* @param {AnyPricePattern & { ratio: AnyMetricPattern }} args.pattern
|
||||
* @param {string} args.title
|
||||
* @param {string} args.legend
|
||||
* @param {Color} [args.color]
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
export function simplePriceRatioTree({ pattern, title, legend, color }) {
|
||||
return [
|
||||
{
|
||||
name: "Price",
|
||||
title,
|
||||
top: [price({ metric: pattern, name: legend, color })],
|
||||
},
|
||||
{
|
||||
name: "Ratio",
|
||||
title: `${title} Ratio`,
|
||||
top: [price({ metric: pattern, name: legend, color })],
|
||||
bottom: [
|
||||
baseline({ metric: pattern.ratio, name: "Ratio", unit: Unit.ratio, base: 1 }),
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Price + Ratio charts with percentile bands (no SMAs/z-scores)
|
||||
* @param {Object} args
|
||||
* @param {PriceRatioPercentilesPattern} args.pattern
|
||||
* @param {string} args.title
|
||||
* @param {string} args.legend
|
||||
* @param {Color} [args.color]
|
||||
* @param {FetchedPriceSeriesBlueprint[]} [args.priceReferences]
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
export function priceRatioPercentilesTree({ pattern, title, legend, color, priceReferences }) {
|
||||
const p = pattern.percentiles;
|
||||
const pctUsd = [
|
||||
{ name: "pct95", prop: p.pct95.price, color: colors.ratioPct._95 },
|
||||
{ name: "pct5", prop: p.pct5.price, color: colors.ratioPct._5 },
|
||||
{ name: "pct98", prop: p.pct98.price, color: colors.ratioPct._98 },
|
||||
{ name: "pct2", prop: p.pct2.price, color: colors.ratioPct._2 },
|
||||
{ name: "pct99", prop: p.pct99.price, color: colors.ratioPct._99 },
|
||||
{ name: "pct1", prop: p.pct1.price, color: colors.ratioPct._1 },
|
||||
];
|
||||
const pctRatio = [
|
||||
{ name: "pct95", prop: p.pct95.ratio, color: colors.ratioPct._95 },
|
||||
{ name: "pct5", prop: p.pct5.ratio, color: colors.ratioPct._5 },
|
||||
{ name: "pct98", prop: p.pct98.ratio, color: colors.ratioPct._98 },
|
||||
{ name: "pct2", prop: p.pct2.ratio, color: colors.ratioPct._2 },
|
||||
{ name: "pct99", prop: p.pct99.ratio, color: colors.ratioPct._99 },
|
||||
{ name: "pct1", prop: p.pct1.ratio, color: colors.ratioPct._1 },
|
||||
];
|
||||
return [
|
||||
{
|
||||
name: "Price",
|
||||
title,
|
||||
top: [
|
||||
price({ metric: pattern, name: legend, color }),
|
||||
...(priceReferences ?? []),
|
||||
...pctUsd.map(({ name, prop, color }) =>
|
||||
price({ metric: prop, name, color, defaultActive: false, options: { lineStyle: 1 } }),
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Ratio",
|
||||
title: `${title} Ratio`,
|
||||
top: [
|
||||
price({ metric: pattern, name: legend, color }),
|
||||
...pctUsd.map(({ name, prop, color }) =>
|
||||
price({ metric: prop, name, color, defaultActive: false, options: { lineStyle: 1 } }),
|
||||
),
|
||||
],
|
||||
bottom: [
|
||||
baseline({ metric: pattern.ratio, name: "Ratio", unit: Unit.ratio, base: 1 }),
|
||||
...pctRatio.map(({ name, prop, color }) =>
|
||||
line({ metric: prop, name, color, defaultActive: false, unit: Unit.ratio, options: { lineStyle: 1 } }),
|
||||
),
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create grouped Price + Ratio charts overlaying multiple series
|
||||
* @param {Object} args
|
||||
* @param {{ name: string, color?: Color, pattern: AnyPricePattern & { ratio: AnyMetricPattern } }[]} args.list
|
||||
* @param {string} args.title
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
export function groupedSimplePriceRatioTree({ list, title }) {
|
||||
return [
|
||||
{
|
||||
name: "Price",
|
||||
title,
|
||||
top: list.map(({ name, color, pattern }) =>
|
||||
price({ metric: pattern, name, color }),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Ratio",
|
||||
title: `${title} Ratio`,
|
||||
bottom: list.map(({ name, color, pattern }) =>
|
||||
baseline({ metric: pattern.ratio, name, color, unit: Unit.ratio, base: 1 }),
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create coinbase/subsidy/fee rolling sum series from separate sources
|
||||
* @param {Object} args
|
||||
|
||||
@@ -234,7 +234,7 @@
|
||||
* @property {AgeRangePattern} tree
|
||||
*
|
||||
* Age range cohort with matured supply
|
||||
* @typedef {CohortAgeRange & { matured: AnyValuePattern }} CohortAgeRangeWithMatured
|
||||
* @typedef {CohortAgeRange & { matured: FullValuePattern }} CohortAgeRangeWithMatured
|
||||
*
|
||||
* Basic cohort WITH RelToMarketCap (geAmount.*, ltAmount.*)
|
||||
* @typedef {Object} CohortBasicWithMarketCap
|
||||
|
||||
@@ -26,6 +26,14 @@ function walkMetrics(node, map, path) {
|
||||
for (const [key, value] of Object.entries(node)) {
|
||||
const kn = key.toLowerCase();
|
||||
if (
|
||||
key === "lookback" ||
|
||||
key === "cumulativeMarketCap" ||
|
||||
key === "sd24h" ||
|
||||
key === "spot" ||
|
||||
key === "ohlc" ||
|
||||
key === "state" ||
|
||||
key === "emaSlow" ||
|
||||
key === "emaFast" ||
|
||||
key.endsWith("Raw") ||
|
||||
key.endsWith("Cents") ||
|
||||
key.endsWith("State") ||
|
||||
|
||||
@@ -56,6 +56,8 @@
|
||||
* @typedef {Brk.BaseCumulativeSumPattern4} CoinbasePattern
|
||||
* ActivePriceRatioPattern: ratio pattern with price (extended)
|
||||
* @typedef {Brk.BpsPriceRatioPattern} ActivePriceRatioPattern
|
||||
* PriceRatioPercentilesPattern: price pattern with ratio + percentiles (no SMAs/stdDev)
|
||||
* @typedef {Brk.BpsCentsPercentilesRatioSatsUsdPattern} PriceRatioPercentilesPattern
|
||||
* AnyRatioPattern: full ratio pattern with percentiles, SMAs, and std dev bands
|
||||
* @typedef {Brk.BpsCentsPercentilesRatioSatsSmaStdUsdPattern} AnyRatioPattern
|
||||
* ValuePattern: patterns with base + cumulative (no rolling)
|
||||
@@ -83,7 +85,7 @@
|
||||
* @typedef {Brk.GrossInvestedLossNetNuplProfitSentimentPattern2} UnrealizedPattern
|
||||
*
|
||||
* Profitability bucket pattern
|
||||
* @typedef {Brk.RealizedSupplyPattern} RealizedSupplyPattern
|
||||
* @typedef {Brk.MvrvNuplRealizedSupplyPattern} RealizedSupplyPattern
|
||||
*
|
||||
* Realized patterns
|
||||
* @typedef {Brk.CapGrossInvestorLossMvrvNetPeakPriceProfitSellSoprPattern} RealizedPattern
|
||||
|
||||
Reference in New Issue
Block a user