mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-07-30 20:28:11 -07:00
global: snapshot
This commit is contained in:
@@ -4,17 +4,18 @@ use brk_error::Result;
|
||||
use brk_types::{Age, Cents, FundedAddressData, Sats, SupplyState};
|
||||
use vecdb::unlikely;
|
||||
|
||||
use super::{super::cost_basis::RealizedState, base::CohortState};
|
||||
use super::super::cost_basis::RealizedOps;
|
||||
use super::base::CohortState;
|
||||
|
||||
/// Significant digits for address cost basis prices (after rounding to dollars).
|
||||
const COST_BASIS_PRICE_DIGITS: i32 = 4;
|
||||
|
||||
pub struct AddressCohortState {
|
||||
pub struct AddressCohortState<R: RealizedOps> {
|
||||
pub addr_count: u64,
|
||||
pub inner: CohortState,
|
||||
pub inner: CohortState<R>,
|
||||
}
|
||||
|
||||
impl AddressCohortState {
|
||||
impl<R: RealizedOps> AddressCohortState<R> {
|
||||
pub(crate) fn new(path: &Path, name: &str) -> Self {
|
||||
Self {
|
||||
addr_count: 0,
|
||||
@@ -29,7 +30,7 @@ impl AddressCohortState {
|
||||
self.inner.sent = Sats::ZERO;
|
||||
self.inner.satblocks_destroyed = Sats::ZERO;
|
||||
self.inner.satdays_destroyed = Sats::ZERO;
|
||||
self.inner.realized = RealizedState::default();
|
||||
self.inner.realized = R::default();
|
||||
}
|
||||
|
||||
pub(crate) fn send(
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::{collections::BTreeMap, path::Path};
|
||||
use brk_error::Result;
|
||||
use brk_types::{Age, Cents, CentsCompact, CentsSats, CentsSquaredSats, CostBasisSnapshot, Height, Sats, SupplyState};
|
||||
|
||||
use super::super::cost_basis::{CostBasisData, Percentiles, RealizedState, UnrealizedState};
|
||||
use super::super::cost_basis::{CostBasisData, Percentiles, RealizedOps, UnrealizedState};
|
||||
|
||||
pub struct SendPrecomputed {
|
||||
pub sats: Sats,
|
||||
@@ -15,20 +15,54 @@ pub struct SendPrecomputed {
|
||||
pub prev_investor_cap: CentsSquaredSats,
|
||||
}
|
||||
|
||||
pub struct CohortState {
|
||||
impl SendPrecomputed {
|
||||
/// Pre-compute values for send_utxo when the same supply/prices are shared
|
||||
/// across multiple cohorts (age_range, epoch, class).
|
||||
pub(crate) fn new(
|
||||
supply: &SupplyState,
|
||||
current_price: Cents,
|
||||
prev_price: Cents,
|
||||
ath: Cents,
|
||||
age: Age,
|
||||
) -> Option<Self> {
|
||||
if supply.utxo_count == 0 || supply.value == Sats::ZERO {
|
||||
return None;
|
||||
}
|
||||
let sats = supply.value;
|
||||
let current_ps = CentsSats::from_price_sats(current_price, sats);
|
||||
let prev_ps = CentsSats::from_price_sats(prev_price, sats);
|
||||
let ath_ps = if ath == current_price {
|
||||
current_ps
|
||||
} else {
|
||||
CentsSats::from_price_sats(ath, sats)
|
||||
};
|
||||
let prev_investor_cap = prev_ps.to_investor_cap(prev_price);
|
||||
Some(Self {
|
||||
sats,
|
||||
prev_price,
|
||||
age,
|
||||
current_ps,
|
||||
prev_ps,
|
||||
ath_ps,
|
||||
prev_investor_cap,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CohortState<R: RealizedOps> {
|
||||
pub supply: SupplyState,
|
||||
pub realized: RealizedState,
|
||||
pub realized: R,
|
||||
pub sent: Sats,
|
||||
pub satblocks_destroyed: Sats,
|
||||
pub satdays_destroyed: Sats,
|
||||
cost_basis_data: CostBasisData,
|
||||
}
|
||||
|
||||
impl CohortState {
|
||||
impl<R: RealizedOps> CohortState<R> {
|
||||
pub(crate) fn new(path: &Path, name: &str) -> Self {
|
||||
Self {
|
||||
supply: SupplyState::default(),
|
||||
realized: RealizedState::default(),
|
||||
realized: R::default(),
|
||||
sent: Sats::ZERO,
|
||||
satblocks_destroyed: Sats::ZERO,
|
||||
satdays_destroyed: Sats::ZERO,
|
||||
@@ -47,7 +81,6 @@ impl CohortState {
|
||||
}
|
||||
|
||||
/// Restore realized cap from cost_basis_data after import.
|
||||
/// Uses the exact persisted values instead of recomputing from the map.
|
||||
pub(crate) fn restore_realized_cap(&mut self) {
|
||||
self.realized.set_cap_raw(self.cost_basis_data.cap_raw());
|
||||
self.realized
|
||||
@@ -170,38 +203,6 @@ impl CohortState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Pre-computed values for send_utxo when the same supply/prices are shared
|
||||
/// across multiple cohorts (age_range, epoch, year).
|
||||
pub(crate) fn precompute_send(
|
||||
supply: &SupplyState,
|
||||
current_price: Cents,
|
||||
prev_price: Cents,
|
||||
ath: Cents,
|
||||
age: Age,
|
||||
) -> Option<SendPrecomputed> {
|
||||
if supply.utxo_count == 0 || supply.value == Sats::ZERO {
|
||||
return None;
|
||||
}
|
||||
let sats = supply.value;
|
||||
let current_ps = CentsSats::from_price_sats(current_price, sats);
|
||||
let prev_ps = CentsSats::from_price_sats(prev_price, sats);
|
||||
let ath_ps = if ath == current_price {
|
||||
current_ps
|
||||
} else {
|
||||
CentsSats::from_price_sats(ath, sats)
|
||||
};
|
||||
let prev_investor_cap = prev_ps.to_investor_cap(prev_price);
|
||||
Some(SendPrecomputed {
|
||||
sats,
|
||||
prev_price,
|
||||
age,
|
||||
current_ps,
|
||||
prev_ps,
|
||||
ath_ps,
|
||||
prev_investor_cap,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn send_utxo_precomputed(
|
||||
&mut self,
|
||||
supply: &SupplyState,
|
||||
@@ -227,7 +228,7 @@ impl CohortState {
|
||||
ath: Cents,
|
||||
age: Age,
|
||||
) {
|
||||
if let Some(pre) = Self::precompute_send(supply, current_price, prev_price, ath, age) {
|
||||
if let Some(pre) = SendPrecomputed::new(supply, current_price, prev_price, ath, age) {
|
||||
self.send_utxo_precomputed(supply, &pre);
|
||||
} else if supply.utxo_count > 0 {
|
||||
self.supply -= supply;
|
||||
|
||||
@@ -4,12 +4,13 @@ use brk_error::Result;
|
||||
use brk_types::{Sats, SupplyState};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
|
||||
use super::{super::cost_basis::RealizedState, base::CohortState};
|
||||
use super::super::cost_basis::RealizedOps;
|
||||
use super::base::CohortState;
|
||||
|
||||
#[derive(Deref, DerefMut)]
|
||||
pub struct UTXOCohortState(CohortState);
|
||||
pub struct UTXOCohortState<R: RealizedOps>(pub(crate) CohortState<R>);
|
||||
|
||||
impl UTXOCohortState {
|
||||
impl<R: RealizedOps> UTXOCohortState<R> {
|
||||
pub(crate) fn new(path: &Path, name: &str) -> Self {
|
||||
Self(CohortState::new(path, name))
|
||||
}
|
||||
@@ -24,6 +25,6 @@ impl UTXOCohortState {
|
||||
self.0.sent = Sats::ZERO;
|
||||
self.0.satblocks_destroyed = Sats::ZERO;
|
||||
self.0.satdays_destroyed = Sats::ZERO;
|
||||
self.0.realized = RealizedState::default();
|
||||
self.0.realized = R::default();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,19 +2,131 @@ use std::cmp::Ordering;
|
||||
|
||||
use brk_types::{Cents, CentsSats, CentsSquaredSats, Sats};
|
||||
|
||||
/// Realized state using u128 for raw cent*sat values internally.
|
||||
/// This avoids overflow and defers division to output time for efficiency.
|
||||
/// Trait for realized state operations, implemented by both Core and Full variants.
|
||||
/// Core skips extra fields (value_created/destroyed, peak_regret, sent_in_profit/loss, investor_cap).
|
||||
pub trait RealizedOps: Default + Clone + Send + Sync + 'static {
|
||||
fn cap(&self) -> Cents;
|
||||
fn profit(&self) -> Cents;
|
||||
fn loss(&self) -> Cents;
|
||||
fn set_cap_raw(&mut self, cap_raw: CentsSats);
|
||||
fn set_investor_cap_raw(&mut self, investor_cap_raw: CentsSquaredSats);
|
||||
fn reset_single_iteration_values(&mut self);
|
||||
fn increment(&mut self, price: Cents, sats: Sats);
|
||||
fn increment_snapshot(&mut self, price_sats: CentsSats, investor_cap: CentsSquaredSats);
|
||||
fn decrement_snapshot(&mut self, price_sats: CentsSats, investor_cap: CentsSquaredSats);
|
||||
fn receive(&mut self, price: Cents, sats: Sats) {
|
||||
self.increment(price, sats);
|
||||
}
|
||||
fn send(
|
||||
&mut self,
|
||||
sats: Sats,
|
||||
current_ps: CentsSats,
|
||||
prev_ps: CentsSats,
|
||||
ath_ps: CentsSats,
|
||||
prev_investor_cap: CentsSquaredSats,
|
||||
);
|
||||
}
|
||||
|
||||
/// Core realized state: only cap, profit, loss (48 bytes).
|
||||
/// Used by CoreCohortMetrics and MinimalCohortMetrics cohorts
|
||||
/// (epoch, class, amount_range, type_ — ~50 separate cohorts).
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct CoreRealizedState {
|
||||
cap_raw: u128,
|
||||
profit_raw: u128,
|
||||
loss_raw: u128,
|
||||
}
|
||||
|
||||
impl RealizedOps for CoreRealizedState {
|
||||
#[inline]
|
||||
fn cap(&self) -> Cents {
|
||||
if self.cap_raw == 0 {
|
||||
return Cents::ZERO;
|
||||
}
|
||||
Cents::new((self.cap_raw / Sats::ONE_BTC_U128) as u64)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn profit(&self) -> Cents {
|
||||
if self.profit_raw == 0 {
|
||||
return Cents::ZERO;
|
||||
}
|
||||
Cents::new((self.profit_raw / Sats::ONE_BTC_U128) as u64)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn loss(&self) -> Cents {
|
||||
if self.loss_raw == 0 {
|
||||
return Cents::ZERO;
|
||||
}
|
||||
Cents::new((self.loss_raw / Sats::ONE_BTC_U128) as u64)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_cap_raw(&mut self, cap_raw: CentsSats) {
|
||||
self.cap_raw = cap_raw.inner();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_investor_cap_raw(&mut self, _investor_cap_raw: CentsSquaredSats) {
|
||||
// no-op for Core
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn reset_single_iteration_values(&mut self) {
|
||||
self.profit_raw = 0;
|
||||
self.loss_raw = 0;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn increment(&mut self, price: Cents, sats: Sats) {
|
||||
if sats.is_zero() {
|
||||
return;
|
||||
}
|
||||
let price_sats = CentsSats::from_price_sats(price, sats);
|
||||
self.cap_raw += price_sats.as_u128();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn increment_snapshot(&mut self, price_sats: CentsSats, _investor_cap: CentsSquaredSats) {
|
||||
self.cap_raw += price_sats.as_u128();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn decrement_snapshot(&mut self, price_sats: CentsSats, _investor_cap: CentsSquaredSats) {
|
||||
self.cap_raw -= price_sats.as_u128();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn send(
|
||||
&mut self,
|
||||
_sats: Sats,
|
||||
current_ps: CentsSats,
|
||||
prev_ps: CentsSats,
|
||||
_ath_ps: CentsSats,
|
||||
_prev_investor_cap: CentsSquaredSats,
|
||||
) {
|
||||
match current_ps.cmp(&prev_ps) {
|
||||
Ordering::Greater => {
|
||||
self.profit_raw += (current_ps - prev_ps).as_u128();
|
||||
}
|
||||
Ordering::Less => {
|
||||
self.loss_raw += (prev_ps - current_ps).as_u128();
|
||||
}
|
||||
Ordering::Equal => {}
|
||||
}
|
||||
self.cap_raw -= prev_ps.as_u128();
|
||||
}
|
||||
}
|
||||
|
||||
/// Full realized state (~160 bytes).
|
||||
/// Used by BasicCohortMetrics and CompleteCohortMetrics cohorts
|
||||
/// (age_range — 21 separate cohorts).
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct RealizedState {
|
||||
/// Raw realized cap: Σ(price × sats)
|
||||
cap_raw: u128,
|
||||
core: CoreRealizedState,
|
||||
/// Raw investor cap: Σ(price² × sats)
|
||||
/// investor_price = investor_cap_raw / cap_raw (gives cents directly)
|
||||
investor_cap_raw: CentsSquaredSats,
|
||||
/// Raw realized profit (cents * sats)
|
||||
profit_raw: u128,
|
||||
/// Raw realized loss (cents * sats)
|
||||
loss_raw: u128,
|
||||
/// sell_price × sats for profit cases
|
||||
profit_value_created_raw: u128,
|
||||
/// cost_basis × sats for profit cases
|
||||
@@ -31,43 +143,120 @@ pub struct RealizedState {
|
||||
sent_in_loss: Sats,
|
||||
}
|
||||
|
||||
impl RealizedState {
|
||||
/// Get realized cap as CentsUnsigned (divides by ONE_BTC).
|
||||
impl RealizedOps for RealizedState {
|
||||
#[inline]
|
||||
pub(crate) fn cap(&self) -> Cents {
|
||||
if self.cap_raw == 0 {
|
||||
return Cents::ZERO;
|
||||
}
|
||||
Cents::new((self.cap_raw / Sats::ONE_BTC_U128) as u64)
|
||||
fn cap(&self) -> Cents {
|
||||
self.core.cap()
|
||||
}
|
||||
|
||||
/// Set cap_raw directly from persisted value.
|
||||
#[inline]
|
||||
pub(crate) fn set_cap_raw(&mut self, cap_raw: CentsSats) {
|
||||
self.cap_raw = cap_raw.inner();
|
||||
fn profit(&self) -> Cents {
|
||||
self.core.profit()
|
||||
}
|
||||
|
||||
/// Set investor_cap_raw directly from persisted value.
|
||||
#[inline]
|
||||
pub(crate) fn set_investor_cap_raw(&mut self, investor_cap_raw: CentsSquaredSats) {
|
||||
fn loss(&self) -> Cents {
|
||||
self.core.loss()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_cap_raw(&mut self, cap_raw: CentsSats) {
|
||||
self.core.set_cap_raw(cap_raw);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_investor_cap_raw(&mut self, investor_cap_raw: CentsSquaredSats) {
|
||||
self.investor_cap_raw = investor_cap_raw;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn reset_single_iteration_values(&mut self) {
|
||||
self.core.reset_single_iteration_values();
|
||||
self.profit_value_created_raw = 0;
|
||||
self.profit_value_destroyed_raw = 0;
|
||||
self.loss_value_created_raw = 0;
|
||||
self.loss_value_destroyed_raw = 0;
|
||||
self.peak_regret_raw = 0;
|
||||
self.sent_in_profit = Sats::ZERO;
|
||||
self.sent_in_loss = Sats::ZERO;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn increment(&mut self, price: Cents, sats: Sats) {
|
||||
if sats.is_zero() {
|
||||
return;
|
||||
}
|
||||
let price_sats = CentsSats::from_price_sats(price, sats);
|
||||
self.core.cap_raw += price_sats.as_u128();
|
||||
self.investor_cap_raw += price_sats.to_investor_cap(price);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn increment_snapshot(&mut self, price_sats: CentsSats, investor_cap: CentsSquaredSats) {
|
||||
self.core.cap_raw += price_sats.as_u128();
|
||||
self.investor_cap_raw += investor_cap;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn decrement_snapshot(&mut self, price_sats: CentsSats, investor_cap: CentsSquaredSats) {
|
||||
self.core.cap_raw -= price_sats.as_u128();
|
||||
self.investor_cap_raw -= investor_cap;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn send(
|
||||
&mut self,
|
||||
sats: Sats,
|
||||
current_ps: CentsSats,
|
||||
prev_ps: CentsSats,
|
||||
ath_ps: CentsSats,
|
||||
prev_investor_cap: CentsSquaredSats,
|
||||
) {
|
||||
match current_ps.cmp(&prev_ps) {
|
||||
Ordering::Greater => {
|
||||
self.core.profit_raw += (current_ps - prev_ps).as_u128();
|
||||
self.profit_value_created_raw += current_ps.as_u128();
|
||||
self.profit_value_destroyed_raw += prev_ps.as_u128();
|
||||
self.sent_in_profit += sats;
|
||||
}
|
||||
Ordering::Less => {
|
||||
self.core.loss_raw += (prev_ps - current_ps).as_u128();
|
||||
self.loss_value_created_raw += current_ps.as_u128();
|
||||
self.loss_value_destroyed_raw += prev_ps.as_u128();
|
||||
self.sent_in_loss += sats;
|
||||
}
|
||||
Ordering::Equal => {
|
||||
// Break-even: count as profit side (arbitrary but consistent)
|
||||
self.profit_value_created_raw += current_ps.as_u128();
|
||||
self.profit_value_destroyed_raw += prev_ps.as_u128();
|
||||
self.sent_in_profit += sats;
|
||||
}
|
||||
}
|
||||
|
||||
// Track peak regret: (peak - sell_price) × sats
|
||||
self.peak_regret_raw += (ath_ps - current_ps).as_u128();
|
||||
|
||||
// Inline decrement to avoid recomputation
|
||||
self.core.cap_raw -= prev_ps.as_u128();
|
||||
self.investor_cap_raw -= prev_investor_cap;
|
||||
}
|
||||
}
|
||||
|
||||
impl RealizedState {
|
||||
/// Get investor price as CentsUnsigned.
|
||||
/// investor_price = Σ(price² × sats) / Σ(price × sats)
|
||||
/// This is the dollar-weighted average acquisition price.
|
||||
#[inline]
|
||||
pub(crate) fn investor_price(&self) -> Cents {
|
||||
if self.cap_raw == 0 {
|
||||
if self.core.cap_raw == 0 {
|
||||
return Cents::ZERO;
|
||||
}
|
||||
Cents::new((self.investor_cap_raw / self.cap_raw) as u64)
|
||||
Cents::new((self.investor_cap_raw / self.core.cap_raw) as u64)
|
||||
}
|
||||
|
||||
/// Get raw realized cap for aggregation.
|
||||
#[inline]
|
||||
pub(crate) fn cap_raw(&self) -> CentsSats {
|
||||
CentsSats::new(self.cap_raw)
|
||||
CentsSats::new(self.core.cap_raw)
|
||||
}
|
||||
|
||||
/// Get raw investor cap for aggregation.
|
||||
@@ -76,24 +265,6 @@ impl RealizedState {
|
||||
self.investor_cap_raw
|
||||
}
|
||||
|
||||
/// Get realized profit as CentsUnsigned.
|
||||
#[inline]
|
||||
pub(crate) fn profit(&self) -> Cents {
|
||||
if self.profit_raw == 0 {
|
||||
return Cents::ZERO;
|
||||
}
|
||||
Cents::new((self.profit_raw / Sats::ONE_BTC_U128) as u64)
|
||||
}
|
||||
|
||||
/// Get realized loss as CentsUnsigned.
|
||||
#[inline]
|
||||
pub(crate) fn loss(&self) -> Cents {
|
||||
if self.loss_raw == 0 {
|
||||
return Cents::ZERO;
|
||||
}
|
||||
Cents::new((self.loss_raw / Sats::ONE_BTC_U128) as u64)
|
||||
}
|
||||
|
||||
/// Get profit value created as CentsUnsigned (sell_price × sats for profit cases).
|
||||
#[inline]
|
||||
pub(crate) fn profit_value_created(&self) -> Cents {
|
||||
@@ -104,7 +275,6 @@ impl RealizedState {
|
||||
}
|
||||
|
||||
/// Get profit value destroyed as CentsUnsigned (cost_basis × sats for profit cases).
|
||||
/// This is also known as profit_flow.
|
||||
#[inline]
|
||||
pub(crate) fn profit_value_destroyed(&self) -> Cents {
|
||||
if self.profit_value_destroyed_raw == 0 {
|
||||
@@ -123,7 +293,6 @@ impl RealizedState {
|
||||
}
|
||||
|
||||
/// Get loss value destroyed as CentsUnsigned (cost_basis × sats for loss cases).
|
||||
/// This is also known as capitulation_flow.
|
||||
#[inline]
|
||||
pub(crate) fn loss_value_destroyed(&self) -> Cents {
|
||||
if self.loss_value_destroyed_raw == 0 {
|
||||
@@ -133,8 +302,6 @@ impl RealizedState {
|
||||
}
|
||||
|
||||
/// Get realized peak regret as CentsUnsigned.
|
||||
/// This is Σ((peak - sell_price) × sats) - how much more could have been made
|
||||
/// by selling at peak instead of when actually sold.
|
||||
#[inline]
|
||||
pub(crate) fn peak_regret(&self) -> Cents {
|
||||
if self.peak_regret_raw == 0 {
|
||||
@@ -154,93 +321,4 @@ impl RealizedState {
|
||||
pub(crate) fn sent_in_loss(&self) -> Sats {
|
||||
self.sent_in_loss
|
||||
}
|
||||
|
||||
pub(crate) fn reset_single_iteration_values(&mut self) {
|
||||
self.profit_raw = 0;
|
||||
self.loss_raw = 0;
|
||||
self.profit_value_created_raw = 0;
|
||||
self.profit_value_destroyed_raw = 0;
|
||||
self.loss_value_created_raw = 0;
|
||||
self.loss_value_destroyed_raw = 0;
|
||||
self.peak_regret_raw = 0;
|
||||
self.sent_in_profit = Sats::ZERO;
|
||||
self.sent_in_loss = Sats::ZERO;
|
||||
}
|
||||
|
||||
/// Increment using pre-computed values (for UTXO path)
|
||||
#[inline]
|
||||
pub(crate) fn increment(&mut self, price: Cents, sats: Sats) {
|
||||
if sats.is_zero() {
|
||||
return;
|
||||
}
|
||||
let price_sats = CentsSats::from_price_sats(price, sats);
|
||||
self.cap_raw += price_sats.as_u128();
|
||||
self.investor_cap_raw += price_sats.to_investor_cap(price);
|
||||
}
|
||||
|
||||
/// Increment using pre-computed snapshot values (for address path)
|
||||
#[inline]
|
||||
pub(crate) fn increment_snapshot(
|
||||
&mut self,
|
||||
price_sats: CentsSats,
|
||||
investor_cap: CentsSquaredSats,
|
||||
) {
|
||||
self.cap_raw += price_sats.as_u128();
|
||||
self.investor_cap_raw += investor_cap;
|
||||
}
|
||||
|
||||
/// Decrement using pre-computed snapshot values (for address path)
|
||||
#[inline]
|
||||
pub(crate) fn decrement_snapshot(
|
||||
&mut self,
|
||||
price_sats: CentsSats,
|
||||
investor_cap: CentsSquaredSats,
|
||||
) {
|
||||
self.cap_raw -= price_sats.as_u128();
|
||||
self.investor_cap_raw -= investor_cap;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn receive(&mut self, price: Cents, sats: Sats) {
|
||||
self.increment(price, sats);
|
||||
}
|
||||
|
||||
/// Send with pre-computed typed values. Inlines decrement to avoid recomputation.
|
||||
#[inline]
|
||||
pub(crate) fn send(
|
||||
&mut self,
|
||||
sats: Sats,
|
||||
current_ps: CentsSats,
|
||||
prev_ps: CentsSats,
|
||||
ath_ps: CentsSats,
|
||||
prev_investor_cap: CentsSquaredSats,
|
||||
) {
|
||||
match current_ps.cmp(&prev_ps) {
|
||||
Ordering::Greater => {
|
||||
self.profit_raw += (current_ps - prev_ps).as_u128();
|
||||
self.profit_value_created_raw += current_ps.as_u128();
|
||||
self.profit_value_destroyed_raw += prev_ps.as_u128();
|
||||
self.sent_in_profit += sats;
|
||||
}
|
||||
Ordering::Less => {
|
||||
self.loss_raw += (prev_ps - current_ps).as_u128();
|
||||
self.loss_value_created_raw += current_ps.as_u128();
|
||||
self.loss_value_destroyed_raw += prev_ps.as_u128();
|
||||
self.sent_in_loss += sats;
|
||||
}
|
||||
Ordering::Equal => {
|
||||
// Break-even: count as profit side (arbitrary but consistent)
|
||||
self.profit_value_created_raw += current_ps.as_u128();
|
||||
self.profit_value_destroyed_raw += prev_ps.as_u128();
|
||||
self.sent_in_profit += sats;
|
||||
}
|
||||
}
|
||||
|
||||
// Track peak regret: (peak - sell_price) × sats
|
||||
self.peak_regret_raw += (ath_ps - current_ps).as_u128();
|
||||
|
||||
// Inline decrement to avoid recomputation
|
||||
self.cap_raw -= prev_ps.as_u128();
|
||||
self.investor_cap_raw -= prev_investor_cap;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user