use brk_error::Result; use brk_indexer::Lengths; use brk_traversable::Traversable; use brk_types::{ BasisPoints32, BasisPointsSigned32, Bitcoin, Cents, CentsSats, CentsSigned, CentsSquaredSats, Dollars, Height, StoredF64, Version, }; use derive_more::{Deref, DerefMut}; use vecdb::{AnyStoredVec, AnyVec, BytesVec, Exit, ReadableVec, Rw, StorageMode, WritableVec}; use crate::{ blocks, distribution::state::{CohortState, CostBasisData, RealizedState, WithCapital}, internal::{ FiatPerBlockCumulativeWithSums, PercentPerBlock, PercentRollingWindows, PriceWithRatioExtendedPerBlock, RatioCents64, RatioCentsBp32, RatioCentsSignedCentsBps32, RatioCentsSignedDollarsBps32, RatioDollarsBp32, RatioPerBlockPercentiles, RatioPerBlockStdDevBands, RatioSma, RollingWindows, RollingWindowsFrom1w, ValuePerBlockCumulativeRolling, }, price, }; use crate::distribution::metrics::ImportConfig; use super::RealizedCore; #[derive(Traversable)] pub struct RealizedNetPnl { #[traversable(wrap = "change_1m", rename = "to_rcap")] pub change_1m_to_rcap: PercentPerBlock, #[traversable(wrap = "change_1m", rename = "to_mcap")] pub change_1m_to_mcap: PercentPerBlock, } #[derive(Traversable)] pub struct RealizedSopr { #[traversable(rename = "ratio")] pub ratio_extended: RollingWindowsFrom1w, } #[derive(Traversable)] pub struct RealizedPeakRegret { #[traversable(flatten)] pub value: FiatPerBlockCumulativeWithSums, } #[derive(Traversable)] pub struct RealizedCapitalized { pub price: PriceWithRatioExtendedPerBlock, #[traversable(hidden)] pub cap_raw: M::Stored>, } #[derive(Deref, DerefMut, Traversable)] pub struct RealizedFull { #[deref] #[deref_mut] #[traversable(flatten)] pub core: RealizedCore, pub gross_pnl: FiatPerBlockCumulativeWithSums, pub sell_side_risk_ratio: PercentRollingWindows, pub net_pnl: RealizedNetPnl, pub sopr: RealizedSopr, pub peak_regret: RealizedPeakRegret, pub capitalized: RealizedCapitalized, pub profit_to_loss_ratio: RollingWindows, #[traversable(hidden)] pub cap_raw: M::Stored>, #[traversable(wrap = "cap", rename = "to_own_mcap")] pub cap_to_own_mcap: PercentPerBlock, #[traversable(wrap = "price", rename = "percentiles")] pub price_ratio_percentiles: RatioPerBlockPercentiles, #[traversable(wrap = "price", rename = "sma")] pub price_ratio_sma: RatioSma, #[traversable(wrap = "price", rename = "std_dev")] pub price_ratio_std_dev: RatioPerBlockStdDevBands, } impl RealizedFull { pub(crate) fn forced_import(cfg: &ImportConfig) -> Result { let v0 = Version::ZERO; let v1 = Version::ONE; let core = RealizedCore::forced_import(cfg)?; // Gross PnL let gross_pnl: FiatPerBlockCumulativeWithSums = cfg.import("realized_gross_pnl", v1)?; let sell_side_risk_ratio = cfg.import("sell_side_risk_ratio", Version::new(2))?; // Net PnL let net_pnl = RealizedNetPnl { change_1m_to_rcap: cfg.import("net_pnl_change_1m_to_rcap", Version::new(4))?, change_1m_to_mcap: cfg.import("net_pnl_change_1m_to_mcap", Version::new(4))?, }; // SOPR let sopr = RealizedSopr { ratio_extended: cfg.import("sopr", v1)?, }; // Peak regret let peak_regret = RealizedPeakRegret { value: cfg.import("realized_peak_regret", Version::new(3))?, }; // Capitalized let capitalized = RealizedCapitalized { price: cfg.import("capitalized_price", v0)?, cap_raw: cfg.import("capitalized_cap_raw", v0)?, }; // Price ratio stats let realized_price_name = cfg.name("realized_price"); let realized_price_version = cfg.version + v1; Ok(Self { core, gross_pnl, sell_side_risk_ratio, net_pnl, sopr, peak_regret, capitalized, profit_to_loss_ratio: cfg.import("realized_profit_to_loss_ratio", v1)?, cap_raw: cfg.import("cap_raw", v0)?, cap_to_own_mcap: cfg.import("realized_cap_to_own_mcap", v1)?, price_ratio_percentiles: RatioPerBlockPercentiles::forced_import( cfg.db, &realized_price_name, 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, realized_price_version, cfg.indexes, )?, }) } pub(crate) fn min_stateful_len(&self) -> usize { self.capitalized .price .cents .height .len() .min(self.cap_raw.len()) .min(self.capitalized.cap_raw.len()) .min(self.peak_regret.value.block.cents.len()) } #[inline(always)] pub(crate) fn push_state( &mut self, state: &CohortState>, ) { self.core.push_state(state); self.capitalized .price .cents .height .push(state.realized.capitalized_price()); self.cap_raw.push(state.realized.cap_raw()); self.capitalized .cap_raw .push(state.realized.capitalized_cap_raw()); self.peak_regret .value .block .cents .push(state.realized.peak_regret()); } pub(crate) fn collect_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> { let mut vecs = self.core.collect_vecs_mut(); vecs.push(&mut self.capitalized.price.cents.height); vecs.push(&mut self.cap_raw as &mut dyn AnyStoredVec); vecs.push(&mut self.capitalized.cap_raw as &mut dyn AnyStoredVec); vecs.push(&mut self.peak_regret.value.block.cents); vecs } pub(crate) fn compute_from_stateful( &mut self, starting_lengths: &Lengths, others: &[&RealizedCore], exit: &Exit, ) -> Result<()> { self.core .compute_from_stateful(starting_lengths, others, exit)?; Ok(()) } #[inline(always)] pub(crate) fn push_accum(&mut self, accum: &RealizedFullAccum) -> Cents { self.cap_raw.push(accum.cap_raw); self.capitalized.cap_raw.push(accum.capitalized_cap_raw); let capitalized_price = { let cap = accum.cap_raw.as_u128(); if cap == 0 { Cents::ZERO } else { Cents::new((accum.capitalized_cap_raw / cap) as u64) } }; self.capitalized.price.cents.height.push(capitalized_price); self.peak_regret.value.block.cents.push(accum.peak_regret()); capitalized_price } pub(crate) fn compute_rest_part1( &mut self, starting_lengths: &Lengths, exit: &Exit, ) -> Result<()> { self.core.compute_rest_part1(starting_lengths, exit)?; self.peak_regret .value .compute_rest(starting_lengths.height, exit)?; Ok(()) } #[allow(clippy::too_many_arguments)] pub(crate) fn compute_rest_part2( &mut self, blocks: &blocks::Vecs, prices: &price::Vecs, starting_lengths: &Lengths, height_to_supply: &impl ReadableVec, height_to_market_cap: &impl ReadableVec, activity_transfer_volume: &ValuePerBlockCumulativeRolling, exit: &Exit, ) -> Result<()> { self.core.compute_rest_part2( prices, starting_lengths, height_to_supply, &activity_transfer_volume.sum._24h.cents.height, exit, )?; // SOPR ratios from lazy rolling sums (1w, 1m, 1y) for ((sopr, vc), vd) in self .sopr .ratio_extended .as_mut_array() .into_iter() .zip(activity_transfer_volume.sum.0.as_array()[1..].iter()) .zip(self.core.sopr.value_destroyed.sum.as_array()[1..].iter()) { sopr.compute_binary::( starting_lengths.height, &vc.cents.height, &vd.height, exit, )?; } // Gross PnL self.gross_pnl.block.cents.compute_add( starting_lengths.height, &self.core.minimal.profit.block.cents, &self.core.minimal.loss.block.cents, exit, )?; self.gross_pnl.compute_rest(starting_lengths.height, exit)?; // Net PnL 1m change relative to rcap and mcap self.net_pnl .change_1m_to_rcap .compute_binary::( starting_lengths.height, &self.core.net_pnl.delta.absolute._1m.cents.height, &self.core.minimal.cap.cents.height, exit, )?; self.net_pnl .change_1m_to_mcap .compute_binary::( starting_lengths.height, &self.core.net_pnl.delta.absolute._1m.cents.height, height_to_market_cap, exit, )?; // Capitalized price ratio, percentiles and bands self.capitalized .price .compute_rest(prices, starting_lengths, exit)?; // Sell-side risk ratios for (ssrr, rv) in self .sell_side_risk_ratio .as_mut_array() .into_iter() .zip(self.gross_pnl.sum.as_array()) { ssrr.compute_binary::( starting_lengths.height, &rv.cents.height, &self.core.minimal.cap.cents.height, exit, )?; } // Realized cap relative to own market cap self.cap_to_own_mcap .compute_binary::( starting_lengths.height, &self.core.minimal.cap.usd.height, height_to_market_cap, exit, )?; // Realized profit to loss ratios for ((ratio, profit), loss) in self .profit_to_loss_ratio .as_mut_array() .into_iter() .zip(self.core.minimal.profit.sum.as_array()) .zip(self.core.minimal.loss.sum.as_array()) { ratio.compute_binary::( starting_lengths.height, &profit.cents.height, &loss.cents.height, exit, )?; } // Price ratio: percentiles, sma and std dev bands self.price_ratio_percentiles.compute( starting_lengths, exit, &self.core.minimal.price.ratio.height, &self.core.minimal.price.cents.height, )?; self.price_ratio_sma.compute( blocks, starting_lengths, exit, &self.core.minimal.price.ratio.height, )?; self.price_ratio_std_dev.compute( blocks, starting_lengths, exit, &self.core.minimal.price.ratio.height, &self.core.minimal.price.cents.height, &self.price_ratio_sma, )?; Ok(()) } } #[derive(Default)] pub struct RealizedFullAccum { pub(crate) cap_raw: CentsSats, pub(crate) capitalized_cap_raw: CentsSquaredSats, peak_regret: CentsSats, } impl RealizedFullAccum { pub(crate) fn add(&mut self, state: &RealizedState) { self.cap_raw += state.cap_raw(); self.capitalized_cap_raw += state.capitalized_cap_raw(); self.peak_regret += CentsSats::new(state.peak_regret_raw()); } pub(crate) fn peak_regret(&self) -> Cents { self.peak_regret.to_cents() } }