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