global: snapshot part 18

This commit is contained in:
nym21
2026-03-21 20:03:28 +01:00
parent 8859de5393
commit 926721c482
17 changed files with 15824 additions and 1443 deletions

View File

@@ -1,13 +1,15 @@
use std::{cmp::Reverse, collections::BinaryHeap, fs, path::Path};
use brk_cohort::{Filtered, PROFITABILITY_RANGE_COUNT, PROFIT_COUNT, TERM_NAMES};
use brk_cohort::{Filtered, PROFIT_COUNT, PROFITABILITY_RANGE_COUNT, TERM_NAMES};
use brk_error::Result;
use brk_types::{BasisPoints16, Cents, CentsCompact, CostBasisDistribution, Date, Dollars, Sats};
use crate::distribution::metrics::{CostBasis, ProfitabilityMetrics};
use super::fenwick::{PercentileResult, ProfitabilityRangeResult};
use super::groups::UTXOCohorts;
use super::{
fenwick::{PercentileResult, ProfitabilityRangeResult},
groups::UTXOCohorts,
};
use super::COST_BASIS_PRICE_DIGITS;
@@ -48,7 +50,7 @@ impl UTXOCohorts {
push_cost_basis(&lth, lth_d, &mut self.lth.metrics.cost_basis);
let prof = self.fenwick.profitability(spot_price);
push_profitability(&prof, &mut self.profitability);
push_profitability(&prof, spot_price, &mut self.profitability);
}
/// K-way merge only for writing daily cost basis distributions to disk.
@@ -92,11 +94,7 @@ impl UTXOCohorts {
/// Push percentiles + density to cost basis vecs.
#[inline(always)]
fn push_cost_basis(
percentiles: &PercentileResult,
density_bps: u16,
cost_basis: &mut CostBasis,
) {
fn push_cost_basis(percentiles: &PercentileResult, density_bps: u16, cost_basis: &mut CostBasis) {
cost_basis.push_minmax(percentiles.min_price, percentiles.max_price);
cost_basis.push_percentiles(&percentiles.sat_prices, &percentiles.usd_prices);
cost_basis.push_density(BasisPoints16::from(density_bps));
@@ -108,19 +106,41 @@ fn raw_usd_to_dollars(raw: u128) -> Dollars {
Dollars::from(raw as f64 / 1e10)
}
/// Number of profit ranges (0..=14 are profit, 15..=24 are loss).
const PROFIT_RANGE_COUNT: usize = 15;
/// Compute unrealized P&L from raw sats/usd for a given range.
/// Profit ranges: market_value - cost_basis. Loss ranges: cost_basis - market_value.
#[inline(always)]
fn compute_unrealized_pnl(spot_cents: u128, sats: u64, usd: u128, is_profit: bool) -> Dollars {
let market_value = spot_cents * sats as u128;
let raw = if is_profit {
market_value.saturating_sub(usd)
} else {
usd.saturating_sub(market_value)
};
raw_usd_to_dollars(raw)
}
/// Push profitability range + profit/loss aggregate values to vecs.
fn push_profitability(
buckets: &[ProfitabilityRangeResult; PROFITABILITY_RANGE_COUNT],
spot_price: Cents,
metrics: &mut ProfitabilityMetrics,
) {
let spot_cents = spot_price.as_u128();
// Push 25 range buckets
for (i, bucket) in metrics.range.as_array_mut().into_iter().enumerate() {
let r = &buckets[i];
let is_profit = i < PROFIT_RANGE_COUNT;
bucket.push(
Sats::from(r.all_sats),
Sats::from(r.sth_sats),
raw_usd_to_dollars(r.all_usd),
raw_usd_to_dollars(r.sth_usd),
compute_unrealized_pnl(spot_cents, r.all_sats, r.all_usd, is_profit),
compute_unrealized_pnl(spot_cents, r.sth_sats, r.sth_usd, is_profit),
);
}
@@ -141,6 +161,8 @@ fn push_profitability(
Sats::from(cum_sth_sats),
raw_usd_to_dollars(cum_usd),
raw_usd_to_dollars(cum_sth_usd),
compute_unrealized_pnl(spot_cents, cum_sats, cum_usd, true),
compute_unrealized_pnl(spot_cents, cum_sth_sats, cum_sth_usd, true),
);
}
@@ -163,6 +185,8 @@ fn push_profitability(
Sats::from(cum_sth_sats),
raw_usd_to_dollars(cum_usd),
raw_usd_to_dollars(cum_sth_usd),
compute_unrealized_pnl(spot_cents, cum_sats, cum_usd, false),
compute_unrealized_pnl(spot_cents, cum_sth_sats, cum_sth_usd, false),
);
}
}

View File

@@ -1,9 +1,7 @@
use brk_cohort::Filter;
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{
Dollars, Height, Indexes, Sats, StoredU64, Version,
};
use brk_types::{Dollars, Height, Indexes, Sats, StoredU64, Version};
use vecdb::AnyStoredVec;
use vecdb::{Exit, ReadableVec, Rw, StorageMode};
@@ -87,6 +85,7 @@ impl ExtendedCohortMetrics {
})
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn compute_rest_part2(
&mut self,
blocks: &blocks::Vecs,
@@ -126,11 +125,8 @@ impl ExtendedCohortMetrics {
exit,
)?;
self.unrealized.compute_sentiment(
starting_indexes,
&prices.spot.cents.height,
exit,
)?;
self.unrealized
.compute_sentiment(starting_indexes, &prices.spot.cents.height, exit)?;
self.relative.compute(
starting_indexes.height,
@@ -142,9 +138,9 @@ impl ExtendedCohortMetrics {
exit,
)?;
self.outputs.compute_part2(starting_indexes.height, all_utxo_count, exit)?;
self.outputs
.compute_part2(starting_indexes.height, all_utxo_count, exit)?;
Ok(())
}
}

View File

@@ -20,6 +20,7 @@ pub struct WithSth<All, Sth = All> {
pub struct ProfitabilityBucket<M: StorageMode = Rw> {
pub supply: WithSth<AmountPerBlockWithDeltas<M>, AmountPerBlock<M>>,
pub realized_cap: WithSth<PerBlock<Dollars, M>>,
pub unrealized_pnl: WithSth<PerBlock<Dollars, M>>,
pub nupl: RatioPerBlock<BasisPointsSigned32, M>,
}
@@ -31,6 +32,7 @@ impl<M: StorageMode> ProfitabilityBucket<M> {
.height
.len()
.min(self.realized_cap.all.height.len())
.min(self.unrealized_pnl.all.height.len())
}
}
@@ -72,6 +74,20 @@ impl ProfitabilityBucket {
indexes,
)?,
},
unrealized_pnl: WithSth {
all: PerBlock::forced_import(
db,
&format!("{name}_unrealized_pnl"),
version,
indexes,
)?,
sth: PerBlock::forced_import(
db,
&format!("{name}_sth_unrealized_pnl"),
version,
indexes,
)?,
},
nupl: RatioPerBlock::forced_import_raw(
db,
&format!("{name}_nupl"),
@@ -88,11 +104,15 @@ impl ProfitabilityBucket {
sth_supply: Sats,
realized_cap: Dollars,
sth_realized_cap: Dollars,
unrealized_pnl: Dollars,
sth_unrealized_pnl: Dollars,
) {
self.supply.all.sats.height.push(supply);
self.supply.sth.sats.height.push(sth_supply);
self.realized_cap.all.height.push(realized_cap);
self.realized_cap.sth.height.push(sth_realized_cap);
self.unrealized_pnl.all.height.push(unrealized_pnl);
self.unrealized_pnl.sth.height.push(sth_unrealized_pnl);
}
pub(crate) fn compute(
@@ -138,6 +158,8 @@ impl ProfitabilityBucket {
&mut self.supply.sth.cents.height,
&mut self.realized_cap.all.height,
&mut self.realized_cap.sth.height,
&mut self.unrealized_pnl.all.height,
&mut self.unrealized_pnl.sth.height,
&mut self.nupl.bps.height,
]
}