global: snapshot

This commit is contained in:
nym21
2026-03-10 18:10:50 +01:00
parent db1dce0f3b
commit d50c6e0a73
54 changed files with 2398 additions and 3239 deletions

View File

@@ -29,6 +29,7 @@ pub struct AddressCohortVecs<M: StorageMode = Rw> {
pub metrics: MinimalCohortMetrics<M>,
pub addr_count: ComputedPerBlock<StoredU64, M>,
#[traversable(wrap = "addr_count", rename = "delta")]
pub addr_count_delta: RollingDelta1m<StoredU64, StoredI64, M>,
}

View File

@@ -31,6 +31,18 @@ pub(super) struct CostBasisNode {
sth_usd: i128,
}
impl CostBasisNode {
#[inline]
fn new(sats: i64, usd: i128, is_sth: bool) -> Self {
Self {
all_sats: sats,
sth_sats: if is_sth { sats } else { 0 },
all_usd: usd,
sth_usd: if is_sth { usd } else { 0 },
}
}
}
impl FenwickNode for CostBasisNode {
#[inline(always)]
fn add_assign(&mut self, other: &Self) {
@@ -87,15 +99,13 @@ fn bucket_to_cents(bucket: usize) -> Cents {
/// Map a CentsCompact price to a bucket index.
#[inline]
fn price_to_bucket(price: CentsCompact) -> usize {
let rounded = Cents::from(price).round_to_dollar(COST_BASIS_PRICE_DIGITS);
dollars_to_bucket(u64::from(rounded) / 100)
cents_to_bucket(price.into())
}
/// Map a Cents price to a bucket index.
#[inline]
fn cents_to_bucket(price: Cents) -> usize {
let rounded = price.round_to_dollar(COST_BASIS_PRICE_DIGITS);
dollars_to_bucket(u64::from(rounded) / 100)
dollars_to_bucket(u64::from(price.round_to_dollar(COST_BASIS_PRICE_DIGITS)) / 100)
}
// ---------------------------------------------------------------------------
@@ -143,13 +153,7 @@ impl CostBasisFenwick {
return;
}
let bucket = price_to_bucket(price);
let net_usd = price.as_u128() as i128 * net_sats as i128;
let delta = CostBasisNode {
all_sats: net_sats,
sth_sats: if is_sth { net_sats } else { 0 },
all_usd: net_usd,
sth_usd: if is_sth { net_usd } else { 0 },
};
let delta = CostBasisNode::new(net_sats, price.as_u128() as i128 * net_sats as i128, is_sth);
self.tree.add(bucket, &delta);
self.totals.add_assign(&delta);
}
@@ -167,13 +171,7 @@ impl CostBasisFenwick {
for (&price, &sats) in map.iter() {
let bucket = price_to_bucket(price);
let s = u64::from(sats) as i64;
let usd = price.as_u128() as i128 * s as i128;
let node = CostBasisNode {
all_sats: s,
sth_sats: if is_sth { s } else { 0 },
all_usd: usd,
sth_usd: if is_sth { usd } else { 0 },
};
let node = CostBasisNode::new(s, price.as_u128() as i128 * s as i128, is_sth);
self.tree.add_raw(bucket, &node);
self.totals.add_assign(&node);
}
@@ -242,9 +240,9 @@ impl CostBasisFenwick {
.batch_kth(&sat_targets, &sat_field, &mut sat_buckets);
result.min_price = bucket_to_cents(sat_buckets[0]);
for i in 0..PERCENTILES_LEN {
(0..PERCENTILES_LEN).for_each(|i| {
result.sat_prices[i] = bucket_to_cents(sat_buckets[i + 1]);
}
});
result.max_price = bucket_to_cents(sat_buckets[PERCENTILES_LEN + 1]);
// USD-weighted percentiles (batch)
@@ -266,6 +264,51 @@ impl CostBasisFenwick {
result
}
// -----------------------------------------------------------------------
// Supply density queries (±5% of spot price)
// -----------------------------------------------------------------------
/// Compute supply density: % of supply with cost basis within ±5% of spot.
/// Returns (all_bps, sth_bps, lth_bps) as basis points (0-10000).
pub(super) fn density(&self, spot_price: Cents) -> (u16, u16, u16) {
if self.totals.all_sats <= 0 {
return (0, 0, 0);
}
let spot_f64 = u64::from(spot_price) as f64;
let low = Cents::from((spot_f64 * 0.95) as u64);
let high = Cents::from((spot_f64 * 1.05) as u64);
let low_bucket = cents_to_bucket(low);
let high_bucket = cents_to_bucket(high);
let cum_high = self.tree.prefix_sum(high_bucket);
let cum_low = if low_bucket > 0 {
self.tree.prefix_sum(low_bucket - 1)
} else {
CostBasisNode::default()
};
let all_range = (cum_high.all_sats - cum_low.all_sats).max(0);
let sth_range = (cum_high.sth_sats - cum_low.sth_sats).max(0);
let lth_range = all_range - sth_range;
let to_bps = |range: i64, total: i64| -> u16 {
if total <= 0 {
0
} else {
(range as f64 / total as f64 * 10000.0) as u16
}
};
let lth_total = self.totals.all_sats - self.totals.sth_sats;
(
to_bps(all_range, self.totals.all_sats),
to_bps(sth_range, self.totals.sth_sats),
to_bps(lth_range, lth_total),
)
}
// -----------------------------------------------------------------------
// Profitability queries (all cohort only)
// -----------------------------------------------------------------------

View File

@@ -2,7 +2,7 @@ use std::{cmp::Reverse, collections::BinaryHeap, fs, path::Path};
use brk_cohort::{Filtered, PROFITABILITY_RANGE_COUNT, PROFIT_COUNT, TERM_NAMES};
use brk_error::Result;
use brk_types::{Cents, CentsCompact, CostBasisDistribution, Date, Dollars, Height, Sats};
use brk_types::{BasisPoints16, Cents, CentsCompact, CostBasisDistribution, Date, Dollars, Height, Sats};
use crate::distribution::metrics::{CostBasis, ProfitabilityMetrics};
@@ -24,12 +24,7 @@ impl UTXOCohorts {
states_path: &Path,
) -> Result<()> {
if self.fenwick.is_initialized() {
// Per-block accurate percentiles from Fenwick tree
self.push_percentiles_from_fenwick(height)?;
// Per-block accurate profitability from Fenwick tree with current spot_price
let prof = self.fenwick.profitability(spot_price);
push_profitability(height, &prof, &mut self.profitability)?;
self.push_fenwick_results(height, spot_price)?;
}
// Disk distributions only at day boundaries
@@ -40,18 +35,21 @@ impl UTXOCohorts {
Ok(())
}
/// Push Fenwick-computed percentiles for all/sth/lth to vecs.
fn push_percentiles_from_fenwick(&mut self, height: Height) -> Result<()> {
/// Push all Fenwick-derived per-block results: percentiles, density, profitability.
fn push_fenwick_results(&mut self, height: Height, spot_price: Cents) -> Result<()> {
let (all_d, sth_d, lth_d) = self.fenwick.density(spot_price);
let all = self.fenwick.percentiles_all();
push_percentile_result(height, &all, &mut self.all.metrics.cost_basis)?;
push_cost_basis(height, &all, all_d, &mut self.all.metrics.cost_basis)?;
let sth = self.fenwick.percentiles_sth();
push_percentile_result(height, &sth, &mut self.sth.metrics.cost_basis)?;
push_cost_basis(height, &sth, sth_d, &mut self.sth.metrics.cost_basis)?;
let lth = self.fenwick.percentiles_lth();
push_percentile_result(height, &lth, &mut self.lth.metrics.cost_basis)?;
push_cost_basis(height, &lth, lth_d, &mut self.lth.metrics.cost_basis)?;
Ok(())
let prof = self.fenwick.profitability(spot_price);
push_profitability(height, &prof, &mut self.profitability)
}
/// K-way merge only for writing daily cost basis distributions to disk.
@@ -93,14 +91,16 @@ impl UTXOCohorts {
}
}
/// Push a PercentileResult to cost basis vecs.
fn push_percentile_result(
/// Push percentiles + density to cost basis vecs.
fn push_cost_basis(
height: Height,
result: &PercentileResult,
percentiles: &PercentileResult,
density_bps: u16,
cost_basis: &mut CostBasis,
) -> Result<()> {
cost_basis.truncate_push_minmax(height, result.min_price, result.max_price)?;
cost_basis.truncate_push_percentiles(height, &result.sat_prices, &result.usd_prices)
cost_basis.truncate_push_minmax(height, percentiles.min_price, percentiles.max_price)?;
cost_basis.truncate_push_percentiles(height, &percentiles.sat_prices, &percentiles.usd_prices)?;
cost_basis.truncate_push_density(height, BasisPoints16::from(density_bps))
}
/// Convert raw (cents × sats) accumulator to Dollars (÷ 100 for cents→dollars, ÷ 1e8 for sats).

View File

@@ -6,13 +6,18 @@ use vecdb::{AnyStoredVec, AnyVec, Exit, Rw, StorageMode, WritableVec};
use crate::{
blocks,
distribution::{metrics::ImportConfig, state::{CohortState, CostBasisOps, RealizedOps}},
internal::PerBlockWithSum24h,
internal::{AmountPerBlockWithSum24h, PerBlockWithSum24h},
prices,
};
#[derive(Traversable)]
pub struct ActivityCore<M: StorageMode = Rw> {
pub sent: PerBlockWithSum24h<Sats, M>,
pub coindays_destroyed: PerBlockWithSum24h<StoredF64, M>,
#[traversable(wrap = "sent", rename = "in_profit")]
pub sent_in_profit: AmountPerBlockWithSum24h<M>,
#[traversable(wrap = "sent", rename = "in_loss")]
pub sent_in_loss: AmountPerBlockWithSum24h<M>,
}
impl ActivityCore {
@@ -21,6 +26,8 @@ impl ActivityCore {
Ok(Self {
sent: cfg.import("sent", v1)?,
coindays_destroyed: cfg.import("coindays_destroyed", v1)?,
sent_in_profit: cfg.import("sent_in_profit", v1)?,
sent_in_loss: cfg.import("sent_in_loss", v1)?,
})
}
@@ -30,6 +37,8 @@ impl ActivityCore {
.height
.len()
.min(self.coindays_destroyed.raw.height.len())
.min(self.sent_in_profit.raw.sats.height.len())
.min(self.sent_in_loss.raw.sats.height.len())
}
pub(crate) fn truncate_push(
@@ -42,6 +51,16 @@ impl ActivityCore {
height,
StoredF64::from(Bitcoin::from(state.satdays_destroyed)),
)?;
self.sent_in_profit
.raw
.sats
.height
.truncate_push(height, state.realized.sent_in_profit())?;
self.sent_in_loss
.raw
.sats
.height
.truncate_push(height, state.realized.sent_in_loss())?;
Ok(())
}
@@ -49,6 +68,10 @@ impl ActivityCore {
vec![
&mut self.sent.raw.height as &mut dyn AnyStoredVec,
&mut self.coindays_destroyed.raw.height,
&mut self.sent_in_profit.raw.sats.height,
&mut self.sent_in_profit.raw.cents.height,
&mut self.sent_in_loss.raw.sats.height,
&mut self.sent_in_loss.raw.cents.height,
]
}
@@ -72,6 +95,10 @@ impl ActivityCore {
)?;
sum_others!(self, starting_indexes, others, exit; coindays_destroyed.raw.height);
sum_others!(self, starting_indexes, others, exit; sent_in_profit.raw.sats.height);
sum_others!(self, starting_indexes, others, exit; sent_in_profit.raw.cents.height);
sum_others!(self, starting_indexes, others, exit; sent_in_loss.raw.sats.height);
sum_others!(self, starting_indexes, others, exit; sent_in_loss.raw.cents.height);
Ok(())
}
@@ -96,4 +123,36 @@ impl ActivityCore {
)?;
Ok(())
}
pub(crate) fn compute_sent_profitability(
&mut self,
blocks: &blocks::Vecs,
prices: &prices::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
self.sent_in_profit
.raw
.compute(prices, starting_indexes.height, exit)?;
self.sent_in_loss
.raw
.compute(prices, starting_indexes.height, exit)?;
self.sent_in_profit.sum.compute_rolling_sum(
starting_indexes.height,
&blocks.lookback.height_24h_ago,
&self.sent_in_profit.raw.sats.height,
&self.sent_in_profit.raw.cents.height,
exit,
)?;
self.sent_in_loss.sum.compute_rolling_sum(
starting_indexes.height,
&blocks.lookback.height_24h_ago,
&self.sent_in_loss.raw.sats.height,
&self.sent_in_loss.raw.cents.height,
exit,
)?;
Ok(())
}
}

View File

@@ -24,6 +24,10 @@ pub struct ActivityFull<M: StorageMode = Rw> {
#[traversable(wrap = "sent", rename = "sum")]
pub sent_sum_extended: RollingWindowsFrom1w<Sats, M>,
#[traversable(wrap = "sent/in_profit", rename = "sum")]
pub sent_in_profit_sum_extended: RollingWindowsFrom1w<Sats, M>,
#[traversable(wrap = "sent/in_loss", rename = "sum")]
pub sent_in_loss_sum_extended: RollingWindowsFrom1w<Sats, M>,
pub coinyears_destroyed: LazyPerBlock<StoredF64, StoredF64>,
@@ -49,6 +53,8 @@ impl ActivityFull {
coindays_destroyed_cumulative: cfg.import("coindays_destroyed_cumulative", v1)?,
coindays_destroyed_sum,
sent_sum_extended: cfg.import("sent", v1)?,
sent_in_profit_sum_extended: cfg.import("sent_in_profit", v1)?,
sent_in_loss_sum_extended: cfg.import("sent_in_loss", v1)?,
coinyears_destroyed,
dormancy: cfg.import("dormancy", v1)?,
velocity: cfg.import("velocity", v1)?,
@@ -116,6 +122,19 @@ impl ActivityFull {
exit,
)?;
self.sent_in_profit_sum_extended.compute_rolling_sum(
starting_indexes.height,
&window_starts,
&self.inner.sent_in_profit.raw.sats.height,
exit,
)?;
self.sent_in_loss_sum_extended.compute_rolling_sum(
starting_indexes.height,
&window_starts,
&self.inner.sent_in_loss.raw.sats.height,
exit,
)?;
Ok(())
}

View File

@@ -37,7 +37,7 @@ pub struct AllCohortMetrics<M: StorageMode = Rw> {
#[traversable(wrap = "supply", rename = "delta")]
pub supply_delta_extended: RollingDeltaExcept1m<Sats, SatsSigned, M>,
#[traversable(wrap = "outputs", rename = "utxo_count_delta")]
#[traversable(wrap = "outputs/utxo_count", rename = "delta")]
pub utxo_count_delta_extended: RollingDeltaExcept1m<StoredU64, StoredI64, M>,
}

View File

@@ -114,6 +114,8 @@ impl CoreCohortMetrics {
self.activity
.compute_rest_part1(blocks, starting_indexes, exit)?;
self.activity
.compute_sent_profitability(blocks, prices, starting_indexes, exit)?;
self.realized
.compute_rest_part1(blocks, starting_indexes, exit)?;

View File

@@ -33,7 +33,7 @@ pub struct ExtendedCohortMetrics<M: StorageMode = Rw> {
#[traversable(wrap = "supply", rename = "delta")]
pub supply_delta_extended: RollingDeltaExcept1m<Sats, SatsSigned, M>,
#[traversable(wrap = "outputs", rename = "utxo_count_delta")]
#[traversable(wrap = "outputs/utxo_count", rename = "delta")]
pub utxo_count_delta_extended: RollingDeltaExcept1m<StoredU64, StoredI64, M>,
}

View File

@@ -9,7 +9,7 @@ use crate::{
internal::{
AmountPerBlock, AmountPerBlockCumulative, AmountPerBlockWithSum24h, CentsType, ComputedPerBlock,
ComputedPerBlockCumulative, ComputedPerBlockCumulativeSum, FiatPerBlockWithSum24h,
PerBlockWithSum24h, RatioPerBlock, RollingWindow24hAmountPerBlock,
PerBlockWithSum24h, PriceWithRatioExtendedPerBlock, PriceWithRatioPerBlock, RatioPerBlock, RollingWindow24hAmountPerBlock,
RollingWindow24hFiatPerBlock, RollingWindow24hPerBlock,
FiatPerBlock, FiatRollingDelta1m, FiatRollingDeltaExcept1m, NumericValue,
PercentPerBlock, PercentRollingWindows, Price, RollingDelta1m, RollingDeltaExcept1m,
@@ -40,6 +40,8 @@ impl_config_import!(
AmountPerBlock,
AmountPerBlockCumulative,
RollingWindow24hAmountPerBlock,
PriceWithRatioPerBlock,
PriceWithRatioExtendedPerBlock,
RatioPerBlock<BasisPoints32>,
RatioPerBlock<BasisPointsSigned32>,
PercentPerBlock<BasisPoints16>,

View File

@@ -1,13 +1,13 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Cents, Height, Version};
use brk_types::{BasisPoints16, Cents, Height, Version};
use vecdb::{AnyStoredVec, AnyVec, Rw, StorageMode, WritableVec};
use crate::internal::{ComputedPerBlock, PercentilesVecs, Price, PERCENTILES_LEN};
use crate::internal::{ComputedPerBlock, PercentPerBlock, PercentilesVecs, Price, PERCENTILES_LEN};
use super::ImportConfig;
/// Cost basis metrics: min/max + percentiles.
/// Cost basis metrics: min/max + percentiles + supply density.
/// Used by all/sth/lth cohorts only.
#[derive(Traversable)]
pub struct CostBasis<M: StorageMode = Rw> {
@@ -15,6 +15,7 @@ pub struct CostBasis<M: StorageMode = Rw> {
pub max: Price<ComputedPerBlock<Cents, M>>,
pub percentiles: PercentilesVecs<M>,
pub invested_capital: PercentilesVecs<M>,
pub supply_density: PercentPerBlock<BasisPoints16, M>,
}
impl CostBasis {
@@ -34,11 +35,22 @@ impl CostBasis {
cfg.version,
cfg.indexes,
)?,
supply_density: PercentPerBlock::forced_import(
cfg.db,
&cfg.name("supply_density"),
cfg.version,
cfg.indexes,
)?,
})
}
pub(crate) fn min_stateful_height_len(&self) -> usize {
self.min.cents.height.len().min(self.max.cents.height.len())
self.min
.cents
.height
.len()
.min(self.max.cents.height.len())
.min(self.supply_density.bps.height.len())
}
pub(crate) fn truncate_push_minmax(
@@ -63,6 +75,14 @@ impl CostBasis {
Ok(())
}
pub(crate) fn truncate_push_density(
&mut self,
height: Height,
density_bps: BasisPoints16,
) -> Result<()> {
Ok(self.supply_density.bps.height.truncate_push(height, density_bps)?)
}
pub(crate) fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> {
self.percentiles
.validate_computed_version_or_reset(base_version)?;
@@ -75,6 +95,7 @@ impl CostBasis {
let mut vecs: Vec<&mut dyn AnyStoredVec> = vec![
&mut self.min.cents.height,
&mut self.max.cents.height,
&mut self.supply_density.bps.height,
];
vecs.extend(
self.percentiles

View File

@@ -188,6 +188,8 @@ pub trait CohortMetricsBase: CohortMetricsState<Realized = RealizedState, CostBa
.compute_rest(blocks, starting_indexes, exit)?;
self.activity_mut()
.compute_rest_part1(blocks, starting_indexes, exit)?;
self.activity_core_mut()
.compute_sent_profitability(blocks, prices, starting_indexes, exit)?;
self.realized_mut()
.compute_rest_part1(blocks, starting_indexes, exit)?;

View File

@@ -18,6 +18,7 @@ pub struct OutputsFull<M: StorageMode = Rw> {
#[traversable(flatten)]
pub base: OutputsBase<M>,
#[traversable(wrap = "utxo_count", rename = "delta")]
pub utxo_count_delta: RollingDelta1m<StoredU64, StoredI64, M>,
}

View File

@@ -3,14 +3,14 @@ use brk_traversable::Traversable;
use brk_types::{Bitcoin, Cents, CentsSigned, Dollars, Height, Indexes, StoredF64, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{
AnyStoredVec, AnyVec, Exit, ReadableCloneableVec, ReadableVec, Rw, StorageMode, WritableVec,
AnyStoredVec, Exit, ReadableCloneableVec, ReadableVec, Rw, StorageMode,
};
use crate::{
blocks,
distribution::state::{CohortState, CostBasisOps, RealizedOps},
internal::{
AmountPerBlockWithSum24h, ComputedPerBlock, FiatRollingDelta1m, LazyPerBlock,
ComputedPerBlock, FiatRollingDelta1m, LazyPerBlock,
NegCentsUnsignedToDollars, PerBlockWithSum24h, RatioCents64,
RollingWindow24hPerBlock,
},
@@ -26,12 +26,6 @@ pub struct RealizedSoprCore<M: StorageMode = Rw> {
pub ratio: RollingWindow24hPerBlock<StoredF64, M>,
}
#[derive(Traversable)]
pub struct RealizedSentCore<M: StorageMode = Rw> {
pub in_profit: AmountPerBlockWithSum24h<M>,
pub in_loss: AmountPerBlockWithSum24h<M>,
}
#[derive(Deref, DerefMut, Traversable)]
pub struct RealizedCore<M: StorageMode = Rw> {
#[deref]
@@ -51,7 +45,6 @@ pub struct RealizedCore<M: StorageMode = Rw> {
pub neg_loss: LazyPerBlock<Dollars, Cents>,
pub net_pnl: PerBlockWithSum24h<CentsSigned, M>,
pub sopr: RealizedSoprCore<M>,
pub sent: RealizedSentCore<M>,
}
impl RealizedCore {
@@ -78,44 +71,20 @@ impl RealizedCore {
sopr: RealizedSoprCore {
ratio: cfg.import("sopr", v1)?,
},
sent: RealizedSentCore {
in_profit: cfg.import("sent_in_profit", v1)?,
in_loss: cfg.import("sent_in_loss", v1)?,
},
})
}
pub(crate) fn min_stateful_height_len(&self) -> usize {
self.minimal
.min_stateful_height_len()
.min(self.sent.in_profit.raw.sats.height.len())
.min(self.sent.in_loss.raw.sats.height.len())
self.minimal.min_stateful_height_len()
}
pub(crate) fn truncate_push(&mut self, height: Height, state: &CohortState<impl RealizedOps, impl CostBasisOps>) -> Result<()> {
self.minimal.truncate_push(height, state)?;
self.sent
.in_profit
.raw
.sats
.height
.truncate_push(height, state.realized.sent_in_profit())?;
self.sent
.in_loss
.raw
.sats
.height
.truncate_push(height, state.realized.sent_in_loss())?;
Ok(())
}
pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
let mut vecs = self.minimal.collect_vecs_mut();
vecs.push(&mut self.sent.in_profit.raw.sats.height as &mut dyn AnyStoredVec);
vecs.push(&mut self.sent.in_profit.raw.cents.height);
vecs.push(&mut self.sent.in_loss.raw.sats.height);
vecs.push(&mut self.sent.in_loss.raw.cents.height);
vecs
self.minimal.collect_vecs_mut()
}
pub(crate) fn compute_from_stateful(
@@ -128,11 +97,6 @@ impl RealizedCore {
self.minimal
.compute_from_stateful(starting_indexes, &minimal_refs, exit)?;
sum_others!(self, starting_indexes, others, exit; sent.in_profit.raw.sats.height);
sum_others!(self, starting_indexes, others, exit; sent.in_profit.raw.cents.height);
sum_others!(self, starting_indexes, others, exit; sent.in_loss.raw.sats.height);
sum_others!(self, starting_indexes, others, exit; sent.in_loss.raw.cents.height);
Ok(())
}
@@ -207,30 +171,6 @@ impl RealizedCore {
exit,
)?;
self.sent
.in_profit
.raw
.compute(prices, starting_indexes.height, exit)?;
self.sent
.in_loss
.raw
.compute(prices, starting_indexes.height, exit)?;
self.sent.in_profit.sum.compute_rolling_sum(
starting_indexes.height,
&blocks.lookback.height_24h_ago,
&self.sent.in_profit.raw.sats.height,
&self.sent.in_profit.raw.cents.height,
exit,
)?;
self.sent.in_loss.sum.compute_rolling_sum(
starting_indexes.height,
&blocks.lookback.height_24h_ago,
&self.sent.in_loss.raw.sats.height,
&self.sent.in_loss.raw.cents.height,
exit,
)?;
Ok(())
}
}

View File

@@ -2,7 +2,7 @@ use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{
BasisPoints32, BasisPointsSigned32, Bitcoin, Cents, CentsSats, CentsSigned, CentsSquaredSats,
Dollars, Height, Indexes, Sats, StoredF64, Version,
Dollars, Height, Indexes, StoredF64, Version,
};
use derive_more::{Deref, DerefMut};
use vecdb::{
@@ -16,9 +16,10 @@ use crate::{
internal::{
CentsUnsignedToDollars, ComputedPerBlock, ComputedPerBlockCumulative, FiatPerBlock,
FiatRollingDelta1m, FiatRollingDeltaExcept1m, LazyPerBlock, PercentPerBlock,
PercentRollingWindows, Price, RatioCents64, RatioCentsBp32, RatioCentsSignedCentsBps32,
RatioCentsSignedDollarsBps32, RatioDollarsBp32, RatioPerBlock, RatioPerBlockPercentiles,
RatioPerBlockStdDevBands, RollingWindows, RollingWindowsFrom1w,
PercentRollingWindows, Price, PriceWithRatioExtendedPerBlock, RatioCents64, RatioCentsBp32,
RatioCentsSignedCentsBps32, RatioCentsSignedDollarsBps32, RatioDollarsBp32,
RatioPerBlockPercentiles, RatioPerBlockStdDevBands, RatioSma, RollingWindows,
RollingWindowsFrom1w,
},
prices,
};
@@ -87,14 +88,6 @@ pub struct RealizedSopr<M: StorageMode = Rw> {
pub ratio_extended: RollingWindowsFrom1w<StoredF64, M>,
}
#[derive(Traversable)]
pub struct RealizedSentFull<M: StorageMode = Rw> {
#[traversable(wrap = "in_profit", rename = "sum")]
pub in_profit_sum_extended: RollingWindowsFrom1w<Sats, M>,
#[traversable(wrap = "in_loss", rename = "sum")]
pub in_loss_sum_extended: RollingWindowsFrom1w<Sats, M>,
}
#[derive(Traversable)]
pub struct RealizedPeakRegret<M: StorageMode = Rw> {
#[traversable(flatten)]
@@ -104,13 +97,11 @@ pub struct RealizedPeakRegret<M: StorageMode = Rw> {
#[derive(Traversable)]
pub struct RealizedInvestor<M: StorageMode = Rw> {
pub price: Price<ComputedPerBlock<Cents, M>>,
pub price_ratio: RatioPerBlock<BasisPoints32, M>,
pub price: PriceWithRatioExtendedPerBlock<M>,
pub lower_price_band: Price<ComputedPerBlock<Cents, M>>,
pub upper_price_band: Price<ComputedPerBlock<Cents, M>>,
#[traversable(wrap = "cap", rename = "raw")]
pub cap_raw: M::Stored<BytesVec<Height, CentsSquaredSats>>,
pub price_ratio_percentiles: RatioPerBlockPercentiles<M>,
}
#[derive(Deref, DerefMut, Traversable)]
@@ -125,7 +116,6 @@ pub struct RealizedFull<M: StorageMode = Rw> {
pub gross_pnl: RealizedGrossPnl<M>,
pub net_pnl: RealizedNetPnl<M>,
pub sopr: RealizedSopr<M>,
pub sent: RealizedSentFull<M>,
pub peak_regret: RealizedPeakRegret<M>,
pub investor: RealizedInvestor<M>,
@@ -139,9 +129,11 @@ pub struct RealizedFull<M: StorageMode = Rw> {
#[traversable(wrap = "cap", rename = "rel_to_own_mcap")]
pub cap_rel_to_own_mcap: PercentPerBlock<BasisPoints32, M>,
#[traversable(wrap = "price_ratio", rename = "percentiles")]
#[traversable(wrap = "price", rename = "percentiles")]
pub price_ratio_percentiles: RatioPerBlockPercentiles<M>,
#[traversable(wrap = "price_ratio", rename = "std_dev")]
#[traversable(wrap = "price", rename = "sma")]
pub price_ratio_sma: RatioSma<M>,
#[traversable(wrap = "price", rename = "std_dev")]
pub price_ratio_std_dev: RatioPerBlockStdDevBands<M>,
}
@@ -218,12 +210,6 @@ impl RealizedFull {
ratio_extended: cfg.import("sopr", v1)?,
};
// Sent
let sent = RealizedSentFull {
in_profit_sum_extended: cfg.import("sent_in_profit", v1)?,
in_loss_sum_extended: cfg.import("sent_in_loss", v1)?,
};
// Peak regret
let peak_regret = RealizedPeakRegret {
value: cfg.import("realized_peak_regret", Version::new(2))?,
@@ -234,16 +220,9 @@ impl RealizedFull {
// Investor
let investor = RealizedInvestor {
price: cfg.import("investor_price", v0)?,
price_ratio: cfg.import("investor_price", v0)?,
lower_price_band: cfg.import("lower_price_band", v0)?,
upper_price_band: cfg.import("upper_price_band", v0)?,
cap_raw: cfg.import("investor_cap_raw", v0)?,
price_ratio_percentiles: RatioPerBlockPercentiles::forced_import(
cfg.db,
&cfg.name("investor_price"),
cfg.version,
cfg.indexes,
)?,
};
// Price ratio stats
@@ -257,7 +236,6 @@ impl RealizedFull {
gross_pnl,
net_pnl,
sopr,
sent,
peak_regret,
investor,
profit_to_loss_ratio: cfg.import("realized_profit_to_loss_ratio", v1)?,
@@ -270,6 +248,12 @@ impl RealizedFull {
realized_price_version,
cfg.indexes,
)?,
price_ratio_sma: RatioSma::forced_import(
cfg.db,
&realized_price_name,
realized_price_version,
cfg.indexes,
)?,
price_ratio_std_dev: RatioPerBlockStdDevBands::forced_import(
cfg.db,
&realized_price_name,
@@ -513,20 +497,6 @@ impl RealizedFull {
exit,
)?;
// Sent rolling sums (1w, 1m, 1y)
self.sent.in_profit_sum_extended.compute_rolling_sum(
starting_indexes.height,
&window_starts,
&self.core.sent.in_profit.raw.sats.height,
exit,
)?;
self.sent.in_loss_sum_extended.compute_rolling_sum(
starting_indexes.height,
&window_starts,
&self.core.sent.in_loss.raw.sats.height,
exit,
)?;
// Profit/loss value created/destroyed rolling sums
self.profit.value_created_sum.compute_rolling_sum(
starting_indexes.height,
@@ -616,11 +586,10 @@ impl RealizedFull {
exit,
)?;
// Investor price ratio and bands
self.investor.price_ratio.compute_ratio(
// Investor price ratio, percentiles and bands
self.investor.price.compute_rest(
prices,
starting_indexes,
&prices.price.cents.height,
&self.investor.price.cents.height,
exit,
)?;
@@ -727,31 +696,28 @@ impl RealizedFull {
)?;
}
// Price ratio: percentiles and std dev bands
// Price ratio: percentiles, sma and std dev bands
self.price_ratio_percentiles.compute(
starting_indexes,
exit,
&self.core.minimal.price.ratio.height,
&self.core.minimal.price.cents.height,
)?;
self.price_ratio_sma.compute(
blocks,
starting_indexes,
exit,
&self.core.minimal.price_ratio.ratio.height,
&self.core.minimal.price.cents.height,
&self.core.minimal.price.ratio.height,
)?;
self.price_ratio_std_dev.compute(
blocks,
starting_indexes,
exit,
&self.core.minimal.price_ratio.ratio.height,
&self.core.minimal.price.ratio.height,
&self.core.minimal.price.cents.height,
)?;
// Investor price ratio: percentiles
let investor_price = &self.investor.price.cents.height;
self.investor.price_ratio_percentiles.compute(
blocks,
starting_indexes,
exit,
&self.investor.price_ratio.ratio.height,
investor_price,
&self.price_ratio_sma,
)?;
Ok(())

View File

@@ -12,8 +12,8 @@ use crate::{
blocks,
distribution::state::{CohortState, CostBasisOps, RealizedOps},
internal::{
ComputedPerBlock, FiatPerBlock, FiatPerBlockWithSum24h, Identity, LazyPerBlock,
PerBlockWithSum24h, Price, RatioPerBlock,
FiatPerBlock, FiatPerBlockWithSum24h, Identity, LazyPerBlock,
PerBlockWithSum24h, PriceWithRatioPerBlock, RatioPerBlock,
},
prices,
};
@@ -33,8 +33,7 @@ pub struct RealizedMinimal<M: StorageMode = Rw> {
pub cap: FiatPerBlock<Cents, M>,
pub profit: FiatPerBlockWithSum24h<Cents, M>,
pub loss: FiatPerBlockWithSum24h<Cents, M>,
pub price: Price<ComputedPerBlock<Cents, M>>,
pub price_ratio: RatioPerBlock<BasisPoints32, M>,
pub price: PriceWithRatioPerBlock<M>,
pub mvrv: LazyPerBlock<StoredF32>,
pub nupl: RatioPerBlock<BasisPointsSigned32, M>,
@@ -47,12 +46,11 @@ impl RealizedMinimal {
let cap: FiatPerBlock<Cents> = cfg.import("realized_cap", Version::ZERO)?;
let realized_price = cfg.import("realized_price", v1)?;
let realized_price_ratio: RatioPerBlock = cfg.import("realized_price", v1)?;
let price: PriceWithRatioPerBlock = cfg.import("realized_price", v1)?;
let mvrv = LazyPerBlock::from_lazy::<Identity<StoredF32>, BasisPoints32>(
&cfg.name("mvrv"),
cfg.version,
&realized_price_ratio.ratio,
&price.ratio,
);
let nupl = cfg.import("nupl", v1)?;
@@ -61,8 +59,7 @@ impl RealizedMinimal {
cap,
profit: cfg.import("realized_profit", v1)?,
loss: cfg.import("realized_loss", v1)?,
price: realized_price,
price_ratio: realized_price_ratio,
price,
mvrv,
nupl,
sopr: RealizedSoprMinimal {
@@ -164,28 +161,24 @@ impl RealizedMinimal {
height_to_supply: &impl ReadableVec<Height, Bitcoin>,
exit: &Exit,
) -> Result<()> {
self.price.cents.height.compute_transform2(
starting_indexes.height,
&self.cap.cents.height,
height_to_supply,
|(i, cap_cents, supply, ..)| {
let cap = cap_cents.as_u128();
let supply_sats = Sats::from(supply).as_u128();
if supply_sats == 0 {
(i, Cents::ZERO)
} else {
(i, Cents::from(cap * Sats::ONE_BTC_U128 / supply_sats))
}
},
exit,
)?;
self.price_ratio.compute_ratio(
starting_indexes,
&prices.price.cents.height,
&self.price.cents.height,
exit,
)?;
let cap = &self.cap.cents.height;
self.price.compute_all(prices, starting_indexes, exit, |v| {
Ok(v.compute_transform2(
starting_indexes.height,
cap,
height_to_supply,
|(i, cap_cents, supply, ..)| {
let cap = cap_cents.as_u128();
let supply_sats = Sats::from(supply).as_u128();
if supply_sats == 0 {
(i, Cents::ZERO)
} else {
(i, Cents::from(cap * Sats::ONE_BTC_U128 / supply_sats))
}
},
exit,
)?)
})?;
self.nupl.bps.height.compute_transform2(
starting_indexes.height,