diff --git a/crates/brk_client/src/lib.rs b/crates/brk_client/src/lib.rs index 153b4cb15..0239ceb23 100644 --- a/crates/brk_client/src/lib.rs +++ b/crates/brk_client/src/lib.rs @@ -1596,7 +1596,7 @@ pub struct BaseChangeCumulativeDeltaRelSumPattern { pub base: CentsUsdPattern, pub change_1m: RelPattern, pub cumulative: CentsUsdPattern, - pub delta: ChangeRatePattern2, + pub delta: AbsoluteRatePattern2, pub rel_to_rcap: BpsPercentRatioPattern, pub sum: _1m1w1y24hPattern3, } @@ -1608,7 +1608,7 @@ impl BaseChangeCumulativeDeltaRelSumPattern { base: CentsUsdPattern::new(client.clone(), _m(&acc, "realized_pnl")), change_1m: RelPattern::new(client.clone(), _m(&acc, "pnl_change_1m_rel_to")), cumulative: CentsUsdPattern::new(client.clone(), _m(&acc, "realized_pnl_cumulative")), - delta: ChangeRatePattern2::new(client.clone(), _m(&acc, "realized_pnl_delta")), + delta: AbsoluteRatePattern2::new(client.clone(), _m(&acc, "realized_pnl_delta")), rel_to_rcap: BpsPercentRatioPattern::new(client.clone(), _m(&acc, "realized_pnl_rel_to_rcap")), sum: _1m1w1y24hPattern3::new(client.clone(), _m(&acc, "realized_pnl_sum")), } @@ -1713,7 +1713,7 @@ impl CapLossMvrvPriceProfitSoprPattern { /// Pattern struct for repeated tree structure. pub struct DeltaHalfInRelTotalPattern { - pub delta: ChangeRatePattern, + pub delta: AbsoluteRatePattern, pub half: BtcCentsSatsUsdPattern, pub in_loss: BtcCentsRelSatsUsdPattern, pub in_profit: BtcCentsRelSatsUsdPattern, @@ -1725,7 +1725,7 @@ impl DeltaHalfInRelTotalPattern { /// Create a new pattern node with accumulated metric name. pub fn new(client: Arc, acc: String) -> Self { Self { - delta: ChangeRatePattern::new(client.clone(), _m(&acc, "delta")), + delta: AbsoluteRatePattern::new(client.clone(), _m(&acc, "delta")), half: BtcCentsSatsUsdPattern::new(client.clone(), _m(&acc, "half")), in_loss: BtcCentsRelSatsUsdPattern::new(client.clone(), _m(&acc, "in_loss")), in_profit: BtcCentsRelSatsUsdPattern::new(client.clone(), _m(&acc, "in_profit")), @@ -1737,7 +1737,7 @@ impl DeltaHalfInRelTotalPattern { /// Pattern struct for repeated tree structure. pub struct DeltaHalfInRelTotalPattern2 { - pub delta: ChangeRatePattern, + pub delta: AbsoluteRatePattern, pub half: BtcCentsSatsUsdPattern, pub in_loss: BtcCentsRelSatsUsdPattern3, pub in_profit: BtcCentsRelSatsUsdPattern3, @@ -1749,7 +1749,7 @@ impl DeltaHalfInRelTotalPattern2 { /// Create a new pattern node with accumulated metric name. pub fn new(client: Arc, acc: String) -> Self { Self { - delta: ChangeRatePattern::new(client.clone(), _m(&acc, "delta")), + delta: AbsoluteRatePattern::new(client.clone(), _m(&acc, "delta")), half: BtcCentsSatsUsdPattern::new(client.clone(), _m(&acc, "half")), in_loss: BtcCentsRelSatsUsdPattern3::new(client.clone(), _m(&acc, "in_loss")), in_profit: BtcCentsRelSatsUsdPattern3::new(client.clone(), _m(&acc, "in_profit")), @@ -1917,7 +1917,7 @@ impl BtcCentsRelSatsUsdPattern2 { /// Pattern struct for repeated tree structure. pub struct DeltaHalfInTotalPattern2 { - pub delta: ChangeRatePattern, + pub delta: AbsoluteRatePattern, pub half: BtcCentsSatsUsdPattern, pub in_loss: BtcCentsSatsUsdPattern, pub in_profit: BtcCentsSatsUsdPattern, @@ -1928,7 +1928,7 @@ impl DeltaHalfInTotalPattern2 { /// Create a new pattern node with accumulated metric name. pub fn new(client: Arc, acc: String) -> Self { Self { - delta: ChangeRatePattern::new(client.clone(), _m(&acc, "delta")), + delta: AbsoluteRatePattern::new(client.clone(), _m(&acc, "delta")), half: BtcCentsSatsUsdPattern::new(client.clone(), _m(&acc, "half")), in_loss: BtcCentsSatsUsdPattern::new(client.clone(), _m(&acc, "in_loss")), in_profit: BtcCentsSatsUsdPattern::new(client.clone(), _m(&acc, "in_profit")), @@ -2189,7 +2189,7 @@ impl AdjustedRatioValuePattern { pub struct BaseCumulativeDeltaSumPattern { pub base: CentsUsdPattern, pub cumulative: CentsUsdPattern, - pub delta: ChangeRatePattern2, + pub delta: AbsoluteRatePattern2, pub sum: _1m1w1y24hPattern3, } @@ -2199,7 +2199,7 @@ impl BaseCumulativeDeltaSumPattern { Self { base: CentsUsdPattern::new(client.clone(), acc.clone()), cumulative: CentsUsdPattern::new(client.clone(), _m(&acc, "cumulative")), - delta: ChangeRatePattern2::new(client.clone(), _m(&acc, "delta")), + delta: AbsoluteRatePattern2::new(client.clone(), _m(&acc, "delta")), sum: _1m1w1y24hPattern3::new(client.clone(), _m(&acc, "sum")), } } @@ -2268,7 +2268,7 @@ impl BtcCentsSatsUsdPattern { /// Pattern struct for repeated tree structure. pub struct CentsDeltaRelUsdPattern { pub cents: MetricPattern1, - pub delta: ChangeRatePattern2, + pub delta: AbsoluteRatePattern2, pub rel_to_own_mcap: BpsPercentRatioPattern4, pub usd: MetricPattern1, } @@ -2278,7 +2278,7 @@ impl CentsDeltaRelUsdPattern { pub fn new(client: Arc, acc: String) -> Self { Self { cents: MetricPattern1::new(client.clone(), _m(&acc, "cents")), - delta: ChangeRatePattern2::new(client.clone(), _m(&acc, "delta")), + delta: AbsoluteRatePattern2::new(client.clone(), _m(&acc, "delta")), rel_to_own_mcap: BpsPercentRatioPattern4::new(client.clone(), _m(&acc, "rel_to_own_mcap")), usd: MetricPattern1::new(client.clone(), acc.clone()), } @@ -2606,7 +2606,7 @@ impl CentsSatsUsdPattern3 { /// Pattern struct for repeated tree structure. pub struct CentsDeltaUsdPattern { pub cents: MetricPattern1, - pub delta: ChangeRatePattern2, + pub delta: AbsoluteRatePattern2, pub usd: MetricPattern1, } @@ -2615,7 +2615,7 @@ impl CentsDeltaUsdPattern { pub fn new(client: Arc, acc: String) -> Self { Self { cents: MetricPattern1::new(client.clone(), _m(&acc, "cents")), - delta: ChangeRatePattern2::new(client.clone(), _m(&acc, "delta")), + delta: AbsoluteRatePattern2::new(client.clone(), _m(&acc, "delta")), usd: MetricPattern1::new(client.clone(), acc.clone()), } } @@ -2641,7 +2641,7 @@ impl CentsSatsUsdPattern { /// Pattern struct for repeated tree structure. pub struct DeltaHalfTotalPattern { - pub delta: ChangeRatePattern, + pub delta: AbsoluteRatePattern, pub half: BtcCentsSatsUsdPattern, pub total: BtcCentsSatsUsdPattern, } @@ -2650,7 +2650,7 @@ impl DeltaHalfTotalPattern { /// Create a new pattern node with accumulated metric name. pub fn new(client: Arc, acc: String) -> Self { Self { - delta: ChangeRatePattern::new(client.clone(), _m(&acc, "delta")), + delta: AbsoluteRatePattern::new(client.clone(), _m(&acc, "delta")), half: BtcCentsSatsUsdPattern::new(client.clone(), _m(&acc, "half")), total: BtcCentsSatsUsdPattern::new(client.clone(), acc.clone()), } @@ -2783,6 +2783,38 @@ impl BaseCumulativeSumPattern { } } +/// Pattern struct for repeated tree structure. +pub struct AbsoluteRatePattern { + pub absolute: _1m1w1y24hPattern, + pub rate: _1m1w1y24hPattern2, +} + +impl AbsoluteRatePattern { + /// Create a new pattern node with accumulated metric name. + pub fn new(client: Arc, acc: String) -> Self { + Self { + absolute: _1m1w1y24hPattern::new(client.clone(), acc.clone()), + rate: _1m1w1y24hPattern2::new(client.clone(), acc.clone()), + } + } +} + +/// Pattern struct for repeated tree structure. +pub struct AbsoluteRatePattern2 { + pub absolute: _1m1w1y24hPattern3, + pub rate: _1m1w1y24hPattern2, +} + +impl AbsoluteRatePattern2 { + /// Create a new pattern node with accumulated metric name. + pub fn new(client: Arc, acc: String) -> Self { + Self { + absolute: _1m1w1y24hPattern3::new(client.clone(), acc.clone()), + rate: _1m1w1y24hPattern2::new(client.clone(), acc.clone()), + } + } +} + /// Pattern struct for repeated tree structure. pub struct BlocksDominancePattern { pub blocks_mined: BaseCumulativeSumPattern2, @@ -2863,38 +2895,6 @@ impl CentsUsdPattern { } } -/// Pattern struct for repeated tree structure. -pub struct ChangeRatePattern { - pub change: _1m1w1y24hPattern, - pub rate: _1m1w1y24hPattern2, -} - -impl ChangeRatePattern { - /// Create a new pattern node with accumulated metric name. - pub fn new(client: Arc, acc: String) -> Self { - Self { - change: _1m1w1y24hPattern::new(client.clone(), acc.clone()), - rate: _1m1w1y24hPattern2::new(client.clone(), acc.clone()), - } - } -} - -/// Pattern struct for repeated tree structure. -pub struct ChangeRatePattern2 { - pub change: _1m1w1y24hPattern3, - pub rate: _1m1w1y24hPattern2, -} - -impl ChangeRatePattern2 { - /// Create a new pattern node with accumulated metric name. - pub fn new(client: Arc, acc: String) -> Self { - Self { - change: _1m1w1y24hPattern3::new(client.clone(), acc.clone()), - rate: _1m1w1y24hPattern2::new(client.clone(), acc.clone()), - } - } -} - /// Pattern struct for repeated tree structure. pub struct CoindaysSentPattern { pub coindays_destroyed: BaseCumulativeSumPattern, @@ -2913,7 +2913,7 @@ impl CoindaysSentPattern { /// Pattern struct for repeated tree structure. pub struct DeltaInnerPattern { - pub delta: ChangeRatePattern, + pub delta: AbsoluteRatePattern, pub inner: MetricPattern1, } @@ -2921,7 +2921,7 @@ impl DeltaInnerPattern { /// Create a new pattern node with accumulated metric name. pub fn new(client: Arc, acc: String) -> Self { Self { - delta: ChangeRatePattern::new(client.clone(), _m(&acc, "delta")), + delta: AbsoluteRatePattern::new(client.clone(), _m(&acc, "delta")), inner: MetricPattern1::new(client.clone(), acc.clone()), } } @@ -3937,29 +3937,29 @@ impl MetricsTree_Addresses_New { /// Metrics tree node. pub struct MetricsTree_Addresses_Delta { - pub all: ChangeRatePattern, - pub p2pk65: ChangeRatePattern, - pub p2pk33: ChangeRatePattern, - pub p2pkh: ChangeRatePattern, - pub p2sh: ChangeRatePattern, - pub p2wpkh: ChangeRatePattern, - pub p2wsh: ChangeRatePattern, - pub p2tr: ChangeRatePattern, - pub p2a: ChangeRatePattern, + pub all: AbsoluteRatePattern, + pub p2pk65: AbsoluteRatePattern, + pub p2pk33: AbsoluteRatePattern, + pub p2pkh: AbsoluteRatePattern, + pub p2sh: AbsoluteRatePattern, + pub p2wpkh: AbsoluteRatePattern, + pub p2wsh: AbsoluteRatePattern, + pub p2tr: AbsoluteRatePattern, + pub p2a: AbsoluteRatePattern, } impl MetricsTree_Addresses_Delta { pub fn new(client: Arc, base_path: String) -> Self { Self { - all: ChangeRatePattern::new(client.clone(), "address_count".to_string()), - p2pk65: ChangeRatePattern::new(client.clone(), "p2pk65_address_count".to_string()), - p2pk33: ChangeRatePattern::new(client.clone(), "p2pk33_address_count".to_string()), - p2pkh: ChangeRatePattern::new(client.clone(), "p2pkh_address_count".to_string()), - p2sh: ChangeRatePattern::new(client.clone(), "p2sh_address_count".to_string()), - p2wpkh: ChangeRatePattern::new(client.clone(), "p2wpkh_address_count".to_string()), - p2wsh: ChangeRatePattern::new(client.clone(), "p2wsh_address_count".to_string()), - p2tr: ChangeRatePattern::new(client.clone(), "p2tr_address_count".to_string()), - p2a: ChangeRatePattern::new(client.clone(), "p2a_address_count".to_string()), + all: AbsoluteRatePattern::new(client.clone(), "address_count".to_string()), + p2pk65: AbsoluteRatePattern::new(client.clone(), "p2pk65_address_count".to_string()), + p2pk33: AbsoluteRatePattern::new(client.clone(), "p2pk33_address_count".to_string()), + p2pkh: AbsoluteRatePattern::new(client.clone(), "p2pkh_address_count".to_string()), + p2sh: AbsoluteRatePattern::new(client.clone(), "p2sh_address_count".to_string()), + p2wpkh: AbsoluteRatePattern::new(client.clone(), "p2wpkh_address_count".to_string()), + p2wsh: AbsoluteRatePattern::new(client.clone(), "p2wsh_address_count".to_string()), + p2tr: AbsoluteRatePattern::new(client.clone(), "p2tr_address_count".to_string()), + p2a: AbsoluteRatePattern::new(client.clone(), "p2a_address_count".to_string()), } } } @@ -4435,16 +4435,16 @@ impl MetricsTree_Cointime_Prices { /// Metrics tree node. pub struct MetricsTree_Cointime_Adjusted { pub inflation_rate: BpsPercentRatioPattern, - pub tx_velocity_btc: MetricPattern1, - pub tx_velocity_usd: MetricPattern1, + pub tx_velocity_native: MetricPattern1, + pub tx_velocity_fiat: MetricPattern1, } impl MetricsTree_Cointime_Adjusted { pub fn new(client: Arc, base_path: String) -> Self { Self { inflation_rate: BpsPercentRatioPattern::new(client.clone(), "cointime_adj_inflation_rate".to_string()), - tx_velocity_btc: MetricPattern1::new(client.clone(), "cointime_adj_tx_velocity_btc".to_string()), - tx_velocity_usd: MetricPattern1::new(client.clone(), "cointime_adj_tx_velocity_usd".to_string()), + tx_velocity_native: MetricPattern1::new(client.clone(), "cointime_adj_tx_velocity".to_string()), + tx_velocity_fiat: MetricPattern1::new(client.clone(), "cointime_adj_tx_velocity_fiat".to_string()), } } } @@ -5434,8 +5434,8 @@ impl MetricsTree_Market_MovingAverage_Sma { /// Metrics tree node. pub struct MetricsTree_Market_MovingAverage_Sma_200d { - pub cents: MetricPattern1, pub usd: MetricPattern1, + pub cents: MetricPattern1, pub sats: MetricPattern1, pub bps: MetricPattern1, pub ratio: MetricPattern1, @@ -5446,8 +5446,8 @@ pub struct MetricsTree_Market_MovingAverage_Sma_200d { impl MetricsTree_Market_MovingAverage_Sma_200d { pub fn new(client: Arc, base_path: String) -> Self { Self { - cents: MetricPattern1::new(client.clone(), "price_sma_200d_cents".to_string()), usd: MetricPattern1::new(client.clone(), "price_sma_200d".to_string()), + cents: MetricPattern1::new(client.clone(), "price_sma_200d_cents".to_string()), sats: MetricPattern1::new(client.clone(), "price_sma_200d_sats".to_string()), bps: MetricPattern1::new(client.clone(), "price_sma_200d_ratio_bps".to_string()), ratio: MetricPattern1::new(client.clone(), "price_sma_200d_ratio".to_string()), @@ -5459,16 +5459,16 @@ impl MetricsTree_Market_MovingAverage_Sma_200d { /// Metrics tree node. pub struct MetricsTree_Market_MovingAverage_Sma_200d_X24 { - pub cents: MetricPattern1, pub usd: MetricPattern1, + pub cents: MetricPattern1, pub sats: MetricPattern1, } impl MetricsTree_Market_MovingAverage_Sma_200d_X24 { pub fn new(client: Arc, base_path: String) -> Self { Self { - cents: MetricPattern1::new(client.clone(), "price_sma_200d_x2_4_cents".to_string()), usd: MetricPattern1::new(client.clone(), "price_sma_200d_x2_4_usd".to_string()), + cents: MetricPattern1::new(client.clone(), "price_sma_200d_x2_4_cents".to_string()), sats: MetricPattern1::new(client.clone(), "price_sma_200d_x2_4_sats".to_string()), } } @@ -5476,16 +5476,16 @@ impl MetricsTree_Market_MovingAverage_Sma_200d_X24 { /// Metrics tree node. pub struct MetricsTree_Market_MovingAverage_Sma_200d_X08 { - pub cents: MetricPattern1, pub usd: MetricPattern1, + pub cents: MetricPattern1, pub sats: MetricPattern1, } impl MetricsTree_Market_MovingAverage_Sma_200d_X08 { pub fn new(client: Arc, base_path: String) -> Self { Self { - cents: MetricPattern1::new(client.clone(), "price_sma_200d_x0_8_cents".to_string()), usd: MetricPattern1::new(client.clone(), "price_sma_200d_x0_8_usd".to_string()), + cents: MetricPattern1::new(client.clone(), "price_sma_200d_x0_8_cents".to_string()), sats: MetricPattern1::new(client.clone(), "price_sma_200d_x0_8_sats".to_string()), } } @@ -5493,8 +5493,8 @@ impl MetricsTree_Market_MovingAverage_Sma_200d_X08 { /// Metrics tree node. pub struct MetricsTree_Market_MovingAverage_Sma_350d { - pub cents: MetricPattern1, pub usd: MetricPattern1, + pub cents: MetricPattern1, pub sats: MetricPattern1, pub bps: MetricPattern1, pub ratio: MetricPattern1, @@ -5504,8 +5504,8 @@ pub struct MetricsTree_Market_MovingAverage_Sma_350d { impl MetricsTree_Market_MovingAverage_Sma_350d { pub fn new(client: Arc, base_path: String) -> Self { Self { - cents: MetricPattern1::new(client.clone(), "price_sma_350d_cents".to_string()), usd: MetricPattern1::new(client.clone(), "price_sma_350d".to_string()), + cents: MetricPattern1::new(client.clone(), "price_sma_350d_cents".to_string()), sats: MetricPattern1::new(client.clone(), "price_sma_350d_sats".to_string()), bps: MetricPattern1::new(client.clone(), "price_sma_350d_ratio_bps".to_string()), ratio: MetricPattern1::new(client.clone(), "price_sma_350d_ratio".to_string()), @@ -5516,16 +5516,16 @@ impl MetricsTree_Market_MovingAverage_Sma_350d { /// Metrics tree node. pub struct MetricsTree_Market_MovingAverage_Sma_350d_X2 { - pub cents: MetricPattern1, pub usd: MetricPattern1, + pub cents: MetricPattern1, pub sats: MetricPattern1, } impl MetricsTree_Market_MovingAverage_Sma_350d_X2 { pub fn new(client: Arc, base_path: String) -> Self { Self { - cents: MetricPattern1::new(client.clone(), "price_sma_350d_x2_cents".to_string()), usd: MetricPattern1::new(client.clone(), "price_sma_350d_x2_usd".to_string()), + cents: MetricPattern1::new(client.clone(), "price_sma_350d_x2_cents".to_string()), sats: MetricPattern1::new(client.clone(), "price_sma_350d_x2_sats".to_string()), } } @@ -6387,16 +6387,16 @@ impl MetricsTree_Prices_Split { /// Metrics tree node. pub struct MetricsTree_Prices_Ohlc { - pub cents: MetricPattern2, pub usd: MetricPattern2, + pub cents: MetricPattern2, pub sats: MetricPattern2, } impl MetricsTree_Prices_Ohlc { pub fn new(client: Arc, base_path: String) -> Self { Self { - cents: MetricPattern2::new(client.clone(), "price_ohlc_cents".to_string()), usd: MetricPattern2::new(client.clone(), "price_ohlc".to_string()), + cents: MetricPattern2::new(client.clone(), "price_ohlc_cents".to_string()), sats: MetricPattern2::new(client.clone(), "price_ohlc_sats".to_string()), } } @@ -6404,16 +6404,16 @@ impl MetricsTree_Prices_Ohlc { /// Metrics tree node. pub struct MetricsTree_Prices_Spot { - pub cents: MetricPattern1, pub usd: MetricPattern1, + pub cents: MetricPattern1, pub sats: MetricPattern1, } impl MetricsTree_Prices_Spot { pub fn new(client: Arc, base_path: String) -> Self { Self { - cents: MetricPattern1::new(client.clone(), "price_cents".to_string()), usd: MetricPattern1::new(client.clone(), "price".to_string()), + cents: MetricPattern1::new(client.clone(), "price_cents".to_string()), sats: MetricPattern1::new(client.clone(), "price_sats".to_string()), } } @@ -6557,7 +6557,7 @@ impl MetricsTree_Cohorts_Utxo_All { pub struct MetricsTree_Cohorts_Utxo_All_Supply { pub total: BtcCentsSatsUsdPattern, pub half: BtcCentsSatsUsdPattern, - pub delta: ChangeRatePattern, + pub delta: AbsoluteRatePattern, pub in_profit: BtcCentsRelSatsUsdPattern2, pub in_loss: BtcCentsRelSatsUsdPattern2, } @@ -6567,7 +6567,7 @@ impl MetricsTree_Cohorts_Utxo_All_Supply { Self { total: BtcCentsSatsUsdPattern::new(client.clone(), "supply".to_string()), half: BtcCentsSatsUsdPattern::new(client.clone(), "supply_half".to_string()), - delta: ChangeRatePattern::new(client.clone(), "supply_delta".to_string()), + delta: AbsoluteRatePattern::new(client.clone(), "supply_delta".to_string()), in_profit: BtcCentsRelSatsUsdPattern2::new(client.clone(), "supply_in_profit".to_string()), in_loss: BtcCentsRelSatsUsdPattern2::new(client.clone(), "supply_in_loss".to_string()), } @@ -6645,16 +6645,16 @@ impl MetricsTree_Cohorts_Utxo_All_Unrealized_Loss { /// Metrics tree node. pub struct MetricsTree_Cohorts_Utxo_All_Unrealized_NetPnl { - pub cents: MetricPattern1, pub usd: MetricPattern1, + pub cents: MetricPattern1, pub rel_to_own_gross: BpsPercentRatioPattern, } impl MetricsTree_Cohorts_Utxo_All_Unrealized_NetPnl { pub fn new(client: Arc, base_path: String) -> Self { Self { - cents: MetricPattern1::new(client.clone(), "net_unrealized_pnl_cents".to_string()), usd: MetricPattern1::new(client.clone(), "net_unrealized_pnl".to_string()), + cents: MetricPattern1::new(client.clone(), "net_unrealized_pnl_cents".to_string()), rel_to_own_gross: BpsPercentRatioPattern::new(client.clone(), "net_unrealized_pnl_rel_to_own_gross_pnl".to_string()), } } diff --git a/crates/brk_cohort/src/by_epoch.rs b/crates/brk_cohort/src/by_epoch.rs index 2428a1c28..d1d71eca1 100644 --- a/crates/brk_cohort/src/by_epoch.rs +++ b/crates/brk_cohort/src/by_epoch.rs @@ -107,20 +107,20 @@ impl ByEpoch { .into_par_iter() } - pub fn mut_vec_from_height(&mut self, height: Height) -> &mut T { + pub fn mut_vec_from_height(&mut self, height: Height) -> Option<&mut T> { let epoch = Halving::from(height); if epoch == Halving::new(0) { - &mut self._0 + Some(&mut self._0) } else if epoch == Halving::new(1) { - &mut self._1 + Some(&mut self._1) } else if epoch == Halving::new(2) { - &mut self._2 + Some(&mut self._2) } else if epoch == Halving::new(3) { - &mut self._3 + Some(&mut self._3) } else if epoch == Halving::new(4) { - &mut self._4 + Some(&mut self._4) } else { - todo!("") + None } } } diff --git a/crates/brk_cohort/src/class.rs b/crates/brk_cohort/src/class.rs index 62173d19d..0a9383fab 100644 --- a/crates/brk_cohort/src/class.rs +++ b/crates/brk_cohort/src/class.rs @@ -231,32 +231,32 @@ impl Class { .into_par_iter() } - pub fn mut_vec_from_timestamp(&mut self, timestamp: Timestamp) -> &mut T { + pub fn mut_vec_from_timestamp(&mut self, timestamp: Timestamp) -> Option<&mut T> { let year = Year::from(timestamp); self.get_mut(year) } - pub fn get_mut(&mut self, year: Year) -> &mut T { + pub fn get_mut(&mut self, year: Year) -> Option<&mut T> { match u16::from(year) { - 2009 => &mut self._2009, - 2010 => &mut self._2010, - 2011 => &mut self._2011, - 2012 => &mut self._2012, - 2013 => &mut self._2013, - 2014 => &mut self._2014, - 2015 => &mut self._2015, - 2016 => &mut self._2016, - 2017 => &mut self._2017, - 2018 => &mut self._2018, - 2019 => &mut self._2019, - 2020 => &mut self._2020, - 2021 => &mut self._2021, - 2022 => &mut self._2022, - 2023 => &mut self._2023, - 2024 => &mut self._2024, - 2025 => &mut self._2025, - 2026 => &mut self._2026, - _ => todo!("Year {} not yet supported", u16::from(year)), + 2009 => Some(&mut self._2009), + 2010 => Some(&mut self._2010), + 2011 => Some(&mut self._2011), + 2012 => Some(&mut self._2012), + 2013 => Some(&mut self._2013), + 2014 => Some(&mut self._2014), + 2015 => Some(&mut self._2015), + 2016 => Some(&mut self._2016), + 2017 => Some(&mut self._2017), + 2018 => Some(&mut self._2018), + 2019 => Some(&mut self._2019), + 2020 => Some(&mut self._2020), + 2021 => Some(&mut self._2021), + 2022 => Some(&mut self._2022), + 2023 => Some(&mut self._2023), + 2024 => Some(&mut self._2024), + 2025 => Some(&mut self._2025), + 2026 => Some(&mut self._2026), + _ => None, } } } diff --git a/crates/brk_cohort/src/loss.rs b/crates/brk_cohort/src/loss.rs index daef084e0..ca14e48e1 100644 --- a/crates/brk_cohort/src/loss.rs +++ b/crates/brk_cohort/src/loss.rs @@ -4,7 +4,7 @@ use serde::Serialize; use super::CohortName; -/// "At least X% loss" threshold names (10 thresholds). +/// "At least X% loss" threshold names (9 thresholds). pub const LOSS_NAMES: Loss = Loss { breakeven: CohortName::new("utxos_in_loss", "<0%", "In Loss (Below Breakeven)"), _10pct: CohortName::new("utxos_over_10pct_in_loss", "≥10%L", "10%+ Loss"), @@ -26,7 +26,7 @@ impl Loss { } } -/// 10 "at least X% loss" aggregate thresholds. +/// 9 "at least X% loss" aggregate thresholds. /// /// Each is a suffix sum over the profitability ranges, from most loss-making up. #[derive(Default, Clone, Traversable, Serialize)] diff --git a/crates/brk_cohort/src/profit.rs b/crates/brk_cohort/src/profit.rs index 85a003704..e8416826f 100644 --- a/crates/brk_cohort/src/profit.rs +++ b/crates/brk_cohort/src/profit.rs @@ -4,7 +4,7 @@ use serde::Serialize; use super::CohortName; -/// "At least X% profit" threshold names (15 thresholds). +/// "At least X% profit" threshold names (14 thresholds). pub const PROFIT_NAMES: Profit = Profit { breakeven: CohortName::new("utxos_in_profit", "≥0%", "In Profit (Breakeven+)"), _10pct: CohortName::new("utxos_over_10pct_in_profit", "≥10%", "10%+ Profit"), @@ -31,7 +31,7 @@ impl Profit { } } -/// 15 "at least X% profit" aggregate thresholds. +/// 14 "at least X% profit" aggregate thresholds. /// /// Each is a prefix sum over the profitability ranges, from most profitable down. #[derive(Default, Clone, Traversable, Serialize)] diff --git a/crates/brk_cohort/src/time_filter.rs b/crates/brk_cohort/src/time_filter.rs index 6569a6ae3..b116abc45 100644 --- a/crates/brk_cohort/src/time_filter.rs +++ b/crates/brk_cohort/src/time_filter.rs @@ -8,11 +8,11 @@ pub enum TimeFilter { } impl TimeFilter { - pub fn contains(&self, days: usize) -> bool { + pub fn contains(&self, hours: usize) -> bool { match self { - TimeFilter::LowerThan(max) => days < *max, - TimeFilter::Range(r) => r.contains(&days), - TimeFilter::GreaterOrEqual(min) => days >= *min, + TimeFilter::LowerThan(max) => hours < *max, + TimeFilter::Range(r) => r.contains(&hours), + TimeFilter::GreaterOrEqual(min) => hours >= *min, } } diff --git a/crates/brk_computer/src/cointime/adjusted/compute.rs b/crates/brk_computer/src/cointime/adjusted/compute.rs index 09a0767e2..d38574333 100644 --- a/crates/brk_computer/src/cointime/adjusted/compute.rs +++ b/crates/brk_computer/src/cointime/adjusted/compute.rs @@ -27,14 +27,14 @@ impl Vecs { exit, )?; - self.tx_velocity_btc.height.compute_multiply( + self.tx_velocity_native.height.compute_multiply( starting_indexes.height, &activity.ratio.height, &supply.velocity.native.height, exit, )?; - self.tx_velocity_usd.height.compute_multiply( + self.tx_velocity_fiat.height.compute_multiply( starting_indexes.height, &activity.ratio.height, &supply.velocity.fiat.height, diff --git a/crates/brk_computer/src/cointime/adjusted/import.rs b/crates/brk_computer/src/cointime/adjusted/import.rs index e4b9353e7..4d29669e4 100644 --- a/crates/brk_computer/src/cointime/adjusted/import.rs +++ b/crates/brk_computer/src/cointime/adjusted/import.rs @@ -21,15 +21,15 @@ impl Vecs { version, indexes, )?, - tx_velocity_btc: PerBlock::forced_import( + tx_velocity_native: PerBlock::forced_import( db, - "cointime_adj_tx_velocity_btc", + "cointime_adj_tx_velocity", version, indexes, )?, - tx_velocity_usd: PerBlock::forced_import( + tx_velocity_fiat: PerBlock::forced_import( db, - "cointime_adj_tx_velocity_usd", + "cointime_adj_tx_velocity_fiat", version, indexes, )?, diff --git a/crates/brk_computer/src/cointime/adjusted/vecs.rs b/crates/brk_computer/src/cointime/adjusted/vecs.rs index 9509251f5..61673664a 100644 --- a/crates/brk_computer/src/cointime/adjusted/vecs.rs +++ b/crates/brk_computer/src/cointime/adjusted/vecs.rs @@ -7,6 +7,6 @@ use crate::internal::{PerBlock, PercentPerBlock}; #[derive(Traversable)] pub struct Vecs { pub inflation_rate: PercentPerBlock, - pub tx_velocity_btc: PerBlock, - pub tx_velocity_usd: PerBlock, + pub tx_velocity_native: PerBlock, + pub tx_velocity_fiat: PerBlock, } diff --git a/crates/brk_computer/src/distribution/block/cohort/sent.rs b/crates/brk_computer/src/distribution/block/cohort/sent.rs index a85a7c91f..267052215 100644 --- a/crates/brk_computer/src/distribution/block/cohort/sent.rs +++ b/crates/brk_computer/src/distribution/block/cohort/sent.rs @@ -2,7 +2,7 @@ use brk_cohort::{AmountBucket, ByAddressType}; use brk_error::Result; use brk_types::{Age, Cents, CheckedSub, Height, Sats, Timestamp, TypeIndex}; use rustc_hash::FxHashSet; -use vecdb::{VecIndex, unlikely}; +use vecdb::VecIndex; use crate::distribution::{ address::{AddressTypeToActivityCounts, HeightToAddressTypeToVec}, @@ -49,8 +49,7 @@ pub(crate) fn process_sent( let prev_timestamp = height_to_timestamp[receive_height.to_usize()]; let age = Age::new(current_timestamp, prev_timestamp); - // Compute peak price during holding period for peak regret - // This is the max HIGH price between receive and send heights + // Compute peak spot price during holding period for peak regret let peak_price = price_range_max.max_between(receive_height, current_height); for (output_type, vec) in by_type.unwrap().into_iter() { @@ -84,72 +83,33 @@ pub(crate) fn process_sent( let new_bucket = AmountBucket::from(new_balance); let crossing_boundary = prev_bucket != new_bucket; + let cohort_state = cohorts + .amount_range + .get_mut_by_bucket(prev_bucket) + .state + .as_mut() + .unwrap(); + + cohort_state.send(addr_data, value, current_price, prev_price, peak_price, age)?; + + // If crossing a bucket boundary, remove the (now-updated) address from old bucket if will_be_empty || crossing_boundary { - // Subtract from old cohort - let cohort_state = cohorts - .amount_range - .get_mut_by_bucket(prev_bucket) - .state - .as_mut() - .unwrap(); - - // Debug info for tracking down underflow issues - if unlikely( - cohort_state.inner.supply.utxo_count < addr_data.utxo_count() as u64, - ) { - panic!( - "process_sent: cohort underflow detected!\n\ - Block context: receive_height={:?}, output_type={:?}, type_index={:?}\n\ - prev_balance={}, new_balance={}, value={}\n\ - will_be_empty={}, crossing_boundary={}\n\ - Address: {:?}", - receive_height, - output_type, - type_index, - prev_balance, - new_balance, - value, - will_be_empty, - crossing_boundary, - addr_data - ); - } - cohort_state.subtract(addr_data); + } - // Update address data - addr_data.send(value, prev_price)?; - - if will_be_empty { - // Address becoming empty - invariant check - if new_balance.is_not_zero() { - unreachable!() - } - - *type_address_count -= 1; - *type_empty_count += 1; - - // Move from funded to empty - lookup.move_to_empty(output_type, type_index); - } else { - // Add to new cohort - cohorts - .amount_range - .get_mut_by_bucket(new_bucket) - .state - .as_mut() - .unwrap() - .add(addr_data); - } - } else { - // Address staying in same cohort - update in place + // Migrate address to new bucket or mark as empty + if will_be_empty { + *type_address_count -= 1; + *type_empty_count += 1; + lookup.move_to_empty(output_type, type_index); + } else if crossing_boundary { cohorts .amount_range .get_mut_by_bucket(new_bucket) .state .as_mut() .unwrap() - .send(addr_data, value, current_price, prev_price, peak_price, age)?; + .add(addr_data); } } } diff --git a/crates/brk_computer/src/distribution/cohorts/utxo/receive.rs b/crates/brk_computer/src/distribution/cohorts/utxo/receive.rs index 6a2f02f54..aee0173ae 100644 --- a/crates/brk_computer/src/distribution/cohorts/utxo/receive.rs +++ b/crates/brk_computer/src/distribution/cohorts/utxo/receive.rs @@ -33,18 +33,12 @@ impl UTXOCohorts { .as_mut() .unwrap() .receive_utxo_snapshot(&supply_state, &snapshot); - self.epoch - .mut_vec_from_height(height) - .state - .as_mut() - .unwrap() - .receive_utxo_snapshot(&supply_state, &snapshot); - self.class - .mut_vec_from_timestamp(timestamp) - .state - .as_mut() - .unwrap() - .receive_utxo_snapshot(&supply_state, &snapshot); + if let Some(v) = self.epoch.mut_vec_from_height(height) { + v.state.as_mut().unwrap().receive_utxo_snapshot(&supply_state, &snapshot); + } + if let Some(v) = self.class.mut_vec_from_timestamp(timestamp) { + v.state.as_mut().unwrap().receive_utxo_snapshot(&supply_state, &snapshot); + } // Update output type cohorts (skip types with no outputs this block) self.type_.iter_typed_mut().for_each(|(output_type, vecs)| { diff --git a/crates/brk_computer/src/distribution/cohorts/utxo/send.rs b/crates/brk_computer/src/distribution/cohorts/utxo/send.rs index b67c62e10..fd60e8f43 100644 --- a/crates/brk_computer/src/distribution/cohorts/utxo/send.rs +++ b/crates/brk_computer/src/distribution/cohorts/utxo/send.rs @@ -63,34 +63,22 @@ impl UTXOCohorts { .as_mut() .unwrap() .send_utxo_precomputed(&sent.spendable_supply, &pre); - self.epoch - .mut_vec_from_height(receive_height) - .state - .as_mut() - .unwrap() - .send_utxo_precomputed(&sent.spendable_supply, &pre); - self.class - .mut_vec_from_timestamp(block_state.timestamp) - .state - .as_mut() - .unwrap() - .send_utxo_precomputed(&sent.spendable_supply, &pre); + if let Some(v) = self.epoch.mut_vec_from_height(receive_height) { + v.state.as_mut().unwrap().send_utxo_precomputed(&sent.spendable_supply, &pre); + } + if let Some(v) = self.class.mut_vec_from_timestamp(block_state.timestamp) { + v.state.as_mut().unwrap().send_utxo_precomputed(&sent.spendable_supply, &pre); + } } else if sent.spendable_supply.utxo_count > 0 { // Zero-value UTXOs: just subtract supply self.age_range.get_mut(age).state.as_mut().unwrap().supply -= &sent.spendable_supply; - self.epoch - .mut_vec_from_height(receive_height) - .state - .as_mut() - .unwrap() - .supply -= &sent.spendable_supply; - self.class - .mut_vec_from_timestamp(block_state.timestamp) - .state - .as_mut() - .unwrap() - .supply -= &sent.spendable_supply; + if let Some(v) = self.epoch.mut_vec_from_height(receive_height) { + v.state.as_mut().unwrap().supply -= &sent.spendable_supply; + } + if let Some(v) = self.class.mut_vec_from_timestamp(block_state.timestamp) { + v.state.as_mut().unwrap().supply -= &sent.spendable_supply; + } } // Update output type cohorts (skip zero-supply entries) diff --git a/crates/brk_computer/src/distribution/metrics/realized/full.rs b/crates/brk_computer/src/distribution/metrics/realized/full.rs index 51a11a505..4843714a0 100644 --- a/crates/brk_computer/src/distribution/metrics/realized/full.rs +++ b/crates/brk_computer/src/distribution/metrics/realized/full.rs @@ -460,7 +460,7 @@ impl RealizedFull { .change_1m_rel_to_rcap .compute_binary::( starting_indexes.height, - &self.core.net_pnl.delta.change._1m.cents.height, + &self.core.net_pnl.delta.absolute._1m.cents.height, &self.core.minimal.cap.cents.height, exit, )?; @@ -468,7 +468,7 @@ impl RealizedFull { .change_1m_rel_to_mcap .compute_binary::( starting_indexes.height, - &self.core.net_pnl.delta.change._1m.cents.height, + &self.core.net_pnl.delta.absolute._1m.cents.height, height_to_market_cap, exit, )?; diff --git a/crates/brk_computer/src/distribution/state/cost_basis/data.rs b/crates/brk_computer/src/distribution/state/cost_basis/data.rs index 88b9fabf8..163cf684d 100644 --- a/crates/brk_computer/src/distribution/state/cost_basis/data.rs +++ b/crates/brk_computer/src/distribution/state/cost_basis/data.rs @@ -373,6 +373,7 @@ impl CostBasisOps for CostBasisData { let (base, rest) = CostBasisDistribution::deserialize_with_rest(&data)?; self.map = Some(base); self.raw.state = Some(RawState::deserialize(rest)?); + debug_assert!(rest.len() >= 32, "CostBasisData state too short: {} bytes", rest.len()); self.investor_cap_raw = CentsSquaredSats::from_bytes(&rest[16..32])?; self.pending.clear(); self.raw.pending_cap = PendingCapDelta::default(); @@ -431,6 +432,14 @@ impl CostBasisOps for CostBasisData { self.apply_map_pending(); self.raw.apply_pending_cap(); self.investor_cap_raw += self.pending_investor_cap.inc; + debug_assert!( + self.investor_cap_raw >= self.pending_investor_cap.dec, + "CostBasis investor_cap_raw underflow!\n\ + Path: {:?}\n\ + Current (after increments): {:?}\n\ + Trying to decrement by: {:?}", + self.raw.pathbuf, self.investor_cap_raw, self.pending_investor_cap.dec + ); self.investor_cap_raw -= self.pending_investor_cap.dec; self.pending_investor_cap = PendingInvestorCapDelta::default(); } diff --git a/crates/brk_computer/src/internal/algo/sliding_window.rs b/crates/brk_computer/src/internal/algo/sliding_window.rs index 3b707fbe7..cfb16966d 100644 --- a/crates/brk_computer/src/internal/algo/sliding_window.rs +++ b/crates/brk_computer/src/internal/algo/sliding_window.rs @@ -224,20 +224,27 @@ impl SlidingWindowSorted { let rank = p * last; let lo = rank.floor() as usize; let hi = rank.ceil() as usize; - let frac = rank - lo as f64; - lo_hi[i] = (lo, hi, frac); + lo_hi[i] = (lo, hi, rank - lo as f64); - // Insert unique ranks in sorted order (they're already ~sorted since ps is sorted) - if rank_count == 0 || rank_set[rank_count - 1] != lo { - rank_set[rank_count] = lo; - rank_count += 1; - } - if hi != lo && (rank_count == 0 || rank_set[rank_count - 1] != hi) { + rank_set[rank_count] = lo; + rank_count += 1; + if hi != lo { rank_set[rank_count] = hi; rank_count += 1; } } + // Sort and deduplicate (interleaved lo/hi values aren't necessarily sorted) + rank_set[..rank_count].sort_unstable(); + let mut w = 1; + for r in 1..rank_count { + if rank_set[r] != rank_set[w - 1] { + rank_set[w] = rank_set[r]; + w += 1; + } + } + rank_count = w; + // Single pass through blocks to get all values let ranks = &rank_set[..rank_count]; let mut values = [0.0f64; 10]; diff --git a/crates/brk_computer/src/internal/amount.rs b/crates/brk_computer/src/internal/amount.rs index f07cdaad1..4c0ade117 100644 --- a/crates/brk_computer/src/internal/amount.rs +++ b/crates/brk_computer/src/internal/amount.rs @@ -9,10 +9,10 @@ use crate::internal::AmountPerBlock; /// All fields are lazy transforms from existing sources - no storage. #[derive(Clone, Traversable)] pub struct LazyAmount { - pub sats: LazyVecFrom1, pub btc: LazyVecFrom1, - pub cents: LazyVecFrom1, + pub sats: LazyVecFrom1, pub usd: LazyVecFrom1, + pub cents: LazyVecFrom1, } impl LazyAmount { @@ -57,10 +57,10 @@ impl LazyAmount { ); Self { - sats, btc, - cents, + sats, usd, + cents, } } } diff --git a/crates/brk_computer/src/internal/per_block/amount/base.rs b/crates/brk_computer/src/internal/per_block/amount/base.rs index 29b54d695..545991e94 100644 --- a/crates/brk_computer/src/internal/per_block/amount/base.rs +++ b/crates/brk_computer/src/internal/per_block/amount/base.rs @@ -13,10 +13,10 @@ use crate::{ #[derive(Traversable)] pub struct AmountPerBlock { - pub sats: PerBlock, pub btc: LazyPerBlock, - pub cents: PerBlock, + pub sats: PerBlock, pub usd: LazyPerBlock, + pub cents: PerBlock, } impl AmountPerBlock { @@ -47,10 +47,10 @@ impl AmountPerBlock { ); Ok(Self { - sats, btc, - cents, + sats, usd, + cents, }) } diff --git a/crates/brk_computer/src/internal/per_block/amount/lazy_derived_resolutions.rs b/crates/brk_computer/src/internal/per_block/amount/lazy_derived_resolutions.rs index 8a8b74864..7682cff1d 100644 --- a/crates/brk_computer/src/internal/per_block/amount/lazy_derived_resolutions.rs +++ b/crates/brk_computer/src/internal/per_block/amount/lazy_derived_resolutions.rs @@ -6,10 +6,10 @@ use crate::internal::{AmountPerBlock, DerivedResolutions}; #[derive(Clone, Traversable)] pub struct LazyAmountDerivedResolutions { - pub sats: DerivedResolutions, pub btc: DerivedResolutions, - pub cents: DerivedResolutions, + pub sats: DerivedResolutions, pub usd: DerivedResolutions, + pub cents: DerivedResolutions, } impl LazyAmountDerivedResolutions { @@ -54,10 +54,10 @@ impl LazyAmountDerivedResolutions { ); Self { - sats, btc, - cents, + sats, usd, + cents, } } } diff --git a/crates/brk_computer/src/internal/per_block/amount/lazy_rolling_sum.rs b/crates/brk_computer/src/internal/per_block/amount/lazy_rolling_sum.rs index b23262ac8..163037a22 100644 --- a/crates/brk_computer/src/internal/per_block/amount/lazy_rolling_sum.rs +++ b/crates/brk_computer/src/internal/per_block/amount/lazy_rolling_sum.rs @@ -14,10 +14,10 @@ use crate::{ /// Single window slot: lazy rolling sum for Amount (sats + btc + cents + usd). #[derive(Clone, Traversable)] pub struct LazyRollingSumAmountFromHeight { - pub sats: LazyRollingSumFromHeight, pub btc: LazyPerBlock, - pub cents: LazyRollingSumFromHeight, + pub sats: LazyRollingSumFromHeight, pub usd: LazyPerBlock, + pub cents: LazyRollingSumFromHeight, } /// Lazy rolling sums for all 4 windows, for Amount (sats + btc + cents + usd). @@ -112,10 +112,10 @@ impl LazyRollingSumsAmountFromHeight { }; LazyRollingSumAmountFromHeight { - sats, btc, - cents, + sats, usd, + cents, } }; diff --git a/crates/brk_computer/src/internal/per_block/fiat/base.rs b/crates/brk_computer/src/internal/per_block/fiat/base.rs index 670166a57..471b6d839 100644 --- a/crates/brk_computer/src/internal/per_block/fiat/base.rs +++ b/crates/brk_computer/src/internal/per_block/fiat/base.rs @@ -29,8 +29,8 @@ impl CentsType for CentsSigned { /// Generic over `C` to support both `Cents` (unsigned) and `CentsSigned` (signed). #[derive(Traversable)] pub struct FiatPerBlock { - pub cents: PerBlock, pub usd: LazyPerBlock, + pub cents: PerBlock, } impl FiatPerBlock { @@ -48,6 +48,6 @@ impl FiatPerBlock { cents.height.read_only_boxed_clone(), ¢s, ); - Ok(Self { cents, usd }) + Ok(Self { usd, cents }) } } diff --git a/crates/brk_computer/src/internal/per_block/fiat/lazy.rs b/crates/brk_computer/src/internal/per_block/fiat/lazy.rs index 4a7b0f937..d9f815054 100644 --- a/crates/brk_computer/src/internal/per_block/fiat/lazy.rs +++ b/crates/brk_computer/src/internal/per_block/fiat/lazy.rs @@ -8,8 +8,8 @@ use crate::internal::{CentsType, PerBlock, Identity, LazyPerBlock, NumericValue} /// Zero extra stored vecs. #[derive(Clone, Traversable)] pub struct LazyFiatPerBlock { - pub cents: LazyPerBlock, pub usd: LazyPerBlock, + pub cents: LazyPerBlock, } impl LazyFiatPerBlock { @@ -33,6 +33,6 @@ impl LazyFiatPerBlock { source.height.read_only_boxed_clone(), source, ); - Self { cents, usd } + Self { usd, cents } } } diff --git a/crates/brk_computer/src/internal/per_block/fiat/lazy_rolling_sum.rs b/crates/brk_computer/src/internal/per_block/fiat/lazy_rolling_sum.rs index 39769dc85..aabcb4a15 100644 --- a/crates/brk_computer/src/internal/per_block/fiat/lazy_rolling_sum.rs +++ b/crates/brk_computer/src/internal/per_block/fiat/lazy_rolling_sum.rs @@ -13,8 +13,8 @@ use crate::{ #[derive(Clone, Traversable)] pub struct LazyRollingSumFiatFromHeight { - pub cents: LazyRollingSumFromHeight, pub usd: LazyPerBlock, + pub cents: LazyRollingSumFromHeight, } #[derive(Clone, Deref, DerefMut, Traversable)] @@ -69,7 +69,7 @@ impl LazyRollingSumsFiatFromHeight { )), }; - LazyRollingSumFiatFromHeight { cents, usd } + LazyRollingSumFiatFromHeight { usd, cents } }; Self(cached_starts.0.map_with_suffix(make_slot)) diff --git a/crates/brk_computer/src/internal/per_block/price.rs b/crates/brk_computer/src/internal/per_block/price.rs index 888c5c6cd..d3a854a64 100644 --- a/crates/brk_computer/src/internal/per_block/price.rs +++ b/crates/brk_computer/src/internal/per_block/price.rs @@ -19,8 +19,8 @@ use crate::{ /// Generic price metric with cents, USD, and sats representations. #[derive(Clone, Traversable)] pub struct Price { - pub cents: C, pub usd: LazyPerBlock, + pub cents: C, pub sats: LazyPerBlock, } @@ -45,7 +45,7 @@ impl Price> { version, &usd, ); - Ok(Self { cents, usd, sats }) + Ok(Self { usd, cents, sats }) } } @@ -75,6 +75,6 @@ where version, &usd, ); - Self { cents, usd, sats } + Self { usd, cents, sats } } } diff --git a/crates/brk_computer/src/internal/per_block/ratio/price_extended.rs b/crates/brk_computer/src/internal/per_block/ratio/price_extended.rs index c297134cf..d32c590de 100644 --- a/crates/brk_computer/src/internal/per_block/ratio/price_extended.rs +++ b/crates/brk_computer/src/internal/per_block/ratio/price_extended.rs @@ -11,8 +11,8 @@ use super::{RatioPerBlock, RatioPerBlockPercentiles}; #[derive(Traversable)] pub struct PriceWithRatioPerBlock { - pub cents: PerBlock, pub usd: LazyPerBlock, + pub cents: PerBlock, pub sats: LazyPerBlock, pub bps: PerBlock, pub ratio: LazyPerBlock, @@ -28,8 +28,8 @@ impl PriceWithRatioPerBlock { let price = Price::forced_import(db, name, version, indexes)?; let ratio = RatioPerBlock::forced_import(db, name, version, indexes)?; Ok(Self { - cents: price.cents, usd: price.usd, + cents: price.cents, sats: price.sats, bps: ratio.bps, ratio: ratio.ratio, diff --git a/crates/brk_computer/src/internal/per_block/rolling/delta.rs b/crates/brk_computer/src/internal/per_block/rolling/delta.rs index 68870b385..2cca11437 100644 --- a/crates/brk_computer/src/internal/per_block/rolling/delta.rs +++ b/crates/brk_computer/src/internal/per_block/rolling/delta.rs @@ -44,7 +44,7 @@ where /// Lazy rolling deltas for all 4 window durations (24h, 1w, 1m, 1y). /// -/// Tree shape: `change._24h/...`, `rate._24h/...` — matches old `RollingDelta`. +/// Tree shape: `absolute._24h/...`, `rate._24h/...` — matches old `RollingDelta`. /// /// Replaces `RollingDelta`, `RollingDelta1m`, and `RollingDeltaExcept1m` — since /// there is no storage cost, all 4 windows are always available. @@ -55,7 +55,7 @@ where C: NumericValue + JsonSchema, B: BpsType, { - pub change: Windows>, + pub absolute: Windows>, pub rate: Windows>, } @@ -96,7 +96,7 @@ where version, indexes, ); - let change = LazyDeltaFromHeight { + let absolute = LazyDeltaFromHeight { height: change_vec, resolutions: Box::new(change_resolutions), }; @@ -154,12 +154,12 @@ where percent, }; - (change, rate) + (absolute, rate) }; - let (change, rate) = cached_starts.0.map_with_suffix(make_slot).unzip(); + let (absolute, rate) = cached_starts.0.map_with_suffix(make_slot).unzip(); - Self { change, rate } + Self { absolute, rate } } } @@ -174,13 +174,13 @@ where S: VecValue, C: CentsType, { - pub cents: LazyDeltaFromHeight, pub usd: LazyPerBlock, + pub cents: LazyDeltaFromHeight, } /// Lazy fiat rolling deltas for all 4 windows. /// -/// Tree shape: `change._24h.{cents,usd}/...`, `rate._24h/...` — matches old `FiatRollingDelta`. +/// Tree shape: `absolute._24h.{cents,usd}/...`, `rate._24h/...` — matches old `FiatRollingDelta`. /// /// Replaces `FiatRollingDelta`, `FiatRollingDelta1m`, and `FiatRollingDeltaExcept1m`. #[derive(Clone, Traversable)] @@ -190,7 +190,7 @@ where C: CentsType, B: BpsType, { - pub change: Windows>, + pub absolute: Windows>, pub rate: Windows>, } @@ -250,7 +250,7 @@ where )), }; - let change = LazyDeltaFiatFromHeight { cents, usd }; + let absolute = LazyDeltaFiatFromHeight { usd, cents }; // Rate BPS: (source[h] - source[ago]) / source[ago] as B (via f64) let rate_vec = LazyDeltaVec::::new( @@ -303,11 +303,11 @@ where percent, }; - (change, rate) + (absolute, rate) }; - let (change, rate) = cached_starts.0.map_with_suffix(make_slot).unzip(); + let (absolute, rate) = cached_starts.0.map_with_suffix(make_slot).unzip(); - Self { change, rate } + Self { absolute, rate } } } diff --git a/crates/brk_computer/src/prices/by_unit.rs b/crates/brk_computer/src/prices/by_unit.rs index 4ba9eba60..a2be0f87e 100644 --- a/crates/brk_computer/src/prices/by_unit.rs +++ b/crates/brk_computer/src/prices/by_unit.rs @@ -18,28 +18,28 @@ pub struct SplitByUnit { #[derive(Traversable)] pub struct SplitIndexesByUnit { - pub cents: EagerIndexes, pub usd: LazyEagerIndexes, + pub cents: EagerIndexes, pub sats: LazyEagerIndexes, } #[derive(Clone, Traversable)] pub struct SplitCloseByUnit { - pub cents: Resolutions, pub usd: Resolutions, + pub cents: Resolutions, pub sats: Resolutions, } #[derive(Traversable)] pub struct OhlcByUnit { - pub cents: OhlcVecs, pub usd: LazyOhlcVecs, + pub cents: OhlcVecs, pub sats: LazyOhlcVecs, } #[derive(Traversable)] pub struct PriceByUnit { - pub cents: PerBlock, pub usd: LazyPerBlock, + pub cents: PerBlock, pub sats: LazyPerBlock, } diff --git a/crates/brk_computer/src/prices/mod.rs b/crates/brk_computer/src/prices/mod.rs index 5eb09509a..adb98a649 100644 --- a/crates/brk_computer/src/prices/mod.rs +++ b/crates/brk_computer/src/prices/mod.rs @@ -142,36 +142,36 @@ impl Vecs { let split = SplitByUnit { open: SplitIndexesByUnit { - cents: open_cents, usd: open_usd, + cents: open_cents, sats: open_sats, }, high: SplitIndexesByUnit { - cents: high_cents, usd: high_usd, + cents: high_cents, sats: high_sats, }, low: SplitIndexesByUnit { - cents: low_cents, usd: low_usd, + cents: low_cents, sats: low_sats, }, close: SplitCloseByUnit { - cents: close_cents, usd: close_usd, + cents: close_cents, sats: close_sats, }, }; let ohlc = OhlcByUnit { - cents: ohlc_cents, usd: ohlc_usd, + cents: ohlc_cents, sats: ohlc_sats, }; let spot = PriceByUnit { - cents: price_cents, usd: price_usd, + cents: price_cents, sats: price_sats, }; diff --git a/crates/brk_error/src/lib.rs b/crates/brk_error/src/lib.rs index 56e899c88..aa93b604b 100644 --- a/crates/brk_error/src/lib.rs +++ b/crates/brk_error/src/lib.rs @@ -142,6 +142,9 @@ pub enum Error { #[error("Request weight {requested} exceeds maximum {max}")] WeightExceeded { requested: usize, max: usize }, + #[error("Deserialization error: {0}")] + Deserialization(String), + #[error("Fetch failed after retries: {0}")] FetchFailed(String), diff --git a/crates/brk_fetcher/src/binance.rs b/crates/brk_fetcher/src/binance.rs index 6a7ef63d6..49cfe3428 100644 --- a/crates/brk_fetcher/src/binance.rs +++ b/crates/brk_fetcher/src/binance.rs @@ -39,8 +39,7 @@ impl Binance { previous_timestamp: Option, ) -> Result { // Try live API data first - if self._1mn.is_none() - || self._1mn.as_ref().unwrap().last_key_value().unwrap().0 <= ×tamp + if self._1mn.as_ref().and_then(|m| m.last_key_value()).is_none_or(|(k, _)| k <= ×tamp) { self._1mn.replace(Self::fetch_1mn()?); } @@ -80,7 +79,7 @@ impl Binance { } pub fn get_from_1d(&mut self, date: &Date) -> Result { - if self._1d.is_none() || self._1d.as_ref().unwrap().last_key_value().unwrap().0 <= date { + if self._1d.as_ref().and_then(|m| m.last_key_value()).is_none_or(|(k, _)| k <= date) { self._1d.replace(Self::fetch_1d()?); } diff --git a/crates/brk_fetcher/src/kraken.rs b/crates/brk_fetcher/src/kraken.rs index ee20f8920..fc455d601 100644 --- a/crates/brk_fetcher/src/kraken.rs +++ b/crates/brk_fetcher/src/kraken.rs @@ -22,8 +22,7 @@ impl Kraken { timestamp: Timestamp, previous_timestamp: Option, ) -> Result { - if self._1mn.is_none() - || self._1mn.as_ref().unwrap().last_key_value().unwrap().0 <= ×tamp + if self._1mn.as_ref().and_then(|m| m.last_key_value()).is_none_or(|(k, _)| k <= ×tamp) { self._1mn.replace(Self::fetch_1mn()?); } @@ -46,7 +45,7 @@ impl Kraken { } fn get_from_1d(&mut self, date: &Date) -> Result { - if self._1d.is_none() || self._1d.as_ref().unwrap().last_key_value().unwrap().0 <= date { + if self._1d.as_ref().and_then(|m| m.last_key_value()).is_none_or(|(k, _)| k <= date) { self._1d.replace(Self::fetch_1d()?); } self._1d diff --git a/crates/brk_fetcher/src/lib.rs b/crates/brk_fetcher/src/lib.rs index abacf1823..26516780d 100644 --- a/crates/brk_fetcher/src/lib.rs +++ b/crates/brk_fetcher/src/lib.rs @@ -4,7 +4,7 @@ use std::{path::Path, thread::sleep, time::Duration}; use brk_error::{Error, Result}; use brk_types::{Date, Height, OHLCCents, Timestamp}; -use tracing::info; +use tracing::{info, warn}; mod binance; mod brk; @@ -70,14 +70,20 @@ impl Fetcher { where F: FnMut(&mut dyn PriceSource) -> Option>, { - if let Some(Ok(ohlc)) = fetch(&mut self.binance) { - return Some(Ok(ohlc)); + match fetch(&mut self.binance) { + Some(Ok(ohlc)) => return Some(Ok(ohlc)), + Some(Err(e)) => warn!("Binance fetch failed: {e}"), + None => {} } - if let Some(Ok(ohlc)) = fetch(&mut self.kraken) { - return Some(Ok(ohlc)); + match fetch(&mut self.kraken) { + Some(Ok(ohlc)) => return Some(Ok(ohlc)), + Some(Err(e)) => warn!("Kraken fetch failed: {e}"), + None => {} } - if let Some(Ok(ohlc)) = fetch(&mut self.brk) { - return Some(Ok(ohlc)); + match fetch(&mut self.brk) { + Some(Ok(ohlc)) => return Some(Ok(ohlc)), + Some(Err(e)) => warn!("Brk fetch failed: {e}"), + None => {} } None } diff --git a/crates/brk_fetcher/src/ohlc.rs b/crates/brk_fetcher/src/ohlc.rs index 526fd1ffe..adaece80d 100644 --- a/crates/brk_fetcher/src/ohlc.rs +++ b/crates/brk_fetcher/src/ohlc.rs @@ -5,13 +5,18 @@ use brk_types::{Cents, Close, Date, Dollars, High, Low, OHLCCents, Open, Timesta /// Parse OHLC value from a JSON array element at given index pub fn parse_cents(array: &[serde_json::Value], index: usize) -> Cents { - Cents::from(Dollars::from( - array - .get(index) - .and_then(|v| v.as_str()) - .and_then(|s| s.parse::().ok()) - .unwrap_or(0.0), - )) + let value = array + .get(index) + .and_then(|v| v.as_str()) + .and_then(|s| s.parse::().ok()) + .unwrap_or_else(|| { + tracing::warn!( + "Failed to parse price at index {index}: {:?}", + array.get(index) + ); + 0.0 + }); + Cents::from(Dollars::from(value)) } /// Build OHLCCentsUnsigned from array indices 1-4 (open, high, low, close) diff --git a/crates/brk_query/src/impl/metrics.rs b/crates/brk_query/src/impl/metrics.rs index e23351c54..a7dedd620 100644 --- a/crates/brk_query/src/impl/metrics.rs +++ b/crates/brk_query/src/impl/metrics.rs @@ -186,6 +186,7 @@ impl Query { .unwrap_or(total), }; + let end = end.max(start); let weight = Self::weight(&vecs, Some(start as i64), Some(end as i64)); if weight > max_weight { return Err(Error::WeightExceeded { diff --git a/crates/brk_reader/src/lib.rs b/crates/brk_reader/src/lib.rs index 9b1363b29..29c45992f 100644 --- a/crates/brk_reader/src/lib.rs +++ b/crates/brk_reader/src/lib.rs @@ -20,7 +20,7 @@ use crossbeam::channel::bounded; use derive_more::Deref; use parking_lot::{RwLock, RwLockReadGuard}; use rayon::prelude::*; -use tracing::error; +use tracing::{error, warn}; mod blk_index_to_blk_path; mod decode; @@ -169,6 +169,10 @@ impl ReaderInner { }; i += offset; + if i + 4 > blk_bytes.len() { + warn!("Truncated blk file {blk_index}: not enough bytes for block length at offset {i}"); + break; + } let len = u32::from_le_bytes( xor_i .bytes(&mut blk_bytes[i..(i + 4)], xor_bytes) @@ -177,6 +181,10 @@ impl ReaderInner { ) as usize; i += 4; + if i + len > blk_bytes.len() { + warn!("Truncated blk file {blk_index}: block at offset {} claims {len} bytes but only {} remain", i - 4, blk_bytes.len() - i); + break; + } let position = BlkPosition::new(blk_index, i as u32); let metadata = BlkMetadata::new(position, len as u32); @@ -208,12 +216,20 @@ impl ReaderInner { .into_iter() .par_bridge() .try_for_each(|(metadata, bytes, xor_i)| { - if let Ok(Some(block)) = decode_block( + let position = metadata.position(); + match decode_block( bytes, metadata, &client, xor_i, xor_bytes, start, end, start_time, end_time, - ) && send_block.send(block).is_err() - { - return ControlFlow::Break(()); + ) { + Ok(Some(block)) => { + if send_block.send(block).is_err() { + return ControlFlow::Break(()); + } + } + Ok(None) => {} // Block filtered out (outside range, unconfirmed) + Err(e) => { + warn!("Failed to decode block at {position}: {e}"); + } } ControlFlow::Continue(()) }); @@ -247,7 +263,7 @@ impl ReaderInner { "Chain discontinuity detected at height {}: expected prev_hash {}, got {}. Stopping iteration.", *block.height(), expected_prev, - block.hash() + block.header.prev_blockhash ); return ControlFlow::Break(()); } diff --git a/crates/brk_types/src/bitcoin.rs b/crates/brk_types/src/bitcoin.rs index 6f04f65e2..25f758e93 100644 --- a/crates/brk_types/src/bitcoin.rs +++ b/crates/brk_types/src/bitcoin.rs @@ -144,7 +144,7 @@ impl std::fmt::Display for Bitcoin { impl Formattable for Bitcoin { #[inline(always)] fn write_to(&self, buf: &mut Vec) { - if !self.0.is_nan() { + if self.0.is_finite() { let mut b = ryu::Buffer::new(); buf.extend_from_slice(b.format(self.0).as_bytes()); } @@ -152,10 +152,10 @@ impl Formattable for Bitcoin { #[inline(always)] fn fmt_json(&self, buf: &mut Vec) { - if self.0.is_nan() { - buf.extend_from_slice(b"null"); - } else { + if self.0.is_finite() { self.write_to(buf); + } else { + buf.extend_from_slice(b"null"); } } } diff --git a/crates/brk_types/src/cost_basis_distribution.rs b/crates/brk_types/src/cost_basis_distribution.rs index 115c7adf8..af6052ee3 100644 --- a/crates/brk_types/src/cost_basis_distribution.rs +++ b/crates/brk_types/src/cost_basis_distribution.rs @@ -27,6 +27,12 @@ pub type CostBasisFormatted = BTreeMap; impl CostBasisDistribution { /// Deserialize from the pco-compressed format, returning remaining bytes. pub fn deserialize_with_rest(data: &[u8]) -> Result<(Self, &[u8])> { + if data.len() < 24 { + return Err(brk_error::Error::Deserialization(format!( + "CostBasisDistribution: data too short ({} bytes, need >= 24)", + data.len() + ))); + } let entry_count = usize::from_bytes(&data[0..8])?; let keys_len = usize::from_bytes(&data[8..16])?; let values_len = usize::from_bytes(&data[16..24])?; @@ -35,6 +41,13 @@ impl CostBasisDistribution { let values_start = keys_start + keys_len; let rest_start = values_start + values_len; + if data.len() < rest_start { + return Err(brk_error::Error::Deserialization(format!( + "CostBasisDistribution: data too short ({} bytes, need >= {})", + data.len(), rest_start + ))); + } + let keys: Vec = simple_decompress(&data[keys_start..values_start])?; let values: Vec = simple_decompress(&data[values_start..rest_start])?; @@ -44,7 +57,7 @@ impl CostBasisDistribution { .map(|(k, v)| (CentsCompact::new(k), Sats::from(v))) .collect(); - assert_eq!(map.len(), entry_count); + debug_assert_eq!(map.len(), entry_count); Ok((Self { map }, &data[rest_start..])) } diff --git a/crates/brk_types/src/date.rs b/crates/brk_types/src/date.rs index 485fd3a11..dee8be871 100644 --- a/crates/brk_types/src/date.rs +++ b/crates/brk_types/src/date.rs @@ -213,6 +213,10 @@ impl<'de> Deserialize<'de> for Date { .parse() .map_err(|_| E::invalid_value(serde::de::Unexpected::Str(v), &self))?; + if !(1..=12).contains(&month) || !(1..=31).contains(&day) { + return Err(E::invalid_value(serde::de::Unexpected::Str(v), &self)); + } + Ok(Date::new(year, month, day)) } } diff --git a/crates/brk_types/src/dollars.rs b/crates/brk_types/src/dollars.rs index e8ea0d8cd..d5adc61e6 100644 --- a/crates/brk_types/src/dollars.rs +++ b/crates/brk_types/src/dollars.rs @@ -196,10 +196,11 @@ impl Div for Dollars { impl Div for Dollars { type Output = Self; fn div(self, rhs: Bitcoin) -> Self::Output { - if self.is_nan() { - self + let rhs = f64::from(rhs); + if self.is_nan() || rhs == 0.0 { + Dollars::NAN } else { - Self(f64::from(self) / f64::from(rhs)) + Self(f64::from(self) / rhs) } } } @@ -435,7 +436,7 @@ impl std::fmt::Display for Dollars { impl Formattable for Dollars { #[inline(always)] fn write_to(&self, buf: &mut Vec) { - if !self.0.is_nan() { + if self.0.is_finite() { let mut b = ryu::Buffer::new(); buf.extend_from_slice(b.format(self.0).as_bytes()); } @@ -443,10 +444,10 @@ impl Formattable for Dollars { #[inline(always)] fn fmt_json(&self, buf: &mut Vec) { - if self.0.is_nan() { - buf.extend_from_slice(b"null"); - } else { + if self.0.is_finite() { self.write_to(buf); + } else { + buf.extend_from_slice(b"null"); } } } diff --git a/crates/brk_types/src/feerate.rs b/crates/brk_types/src/feerate.rs index 06feb26ee..c4263e09f 100644 --- a/crates/brk_types/src/feerate.rs +++ b/crates/brk_types/src/feerate.rs @@ -135,7 +135,18 @@ impl std::fmt::Display for FeeRate { impl Formattable for FeeRate { #[inline(always)] fn write_to(&self, buf: &mut Vec) { - let mut b = ryu::Buffer::new(); - buf.extend_from_slice(b.format(self.0).as_bytes()); + if self.0.is_finite() { + let mut b = ryu::Buffer::new(); + buf.extend_from_slice(b.format(self.0).as_bytes()); + } + } + + #[inline(always)] + fn fmt_json(&self, buf: &mut Vec) { + if self.0.is_finite() { + self.write_to(buf); + } else { + buf.extend_from_slice(b"null"); + } } } diff --git a/crates/brk_types/src/metrics.rs b/crates/brk_types/src/metrics.rs index 860b6a1c6..0b0cf4be8 100644 --- a/crates/brk_types/src/metrics.rs +++ b/crates/brk_types/src/metrics.rs @@ -67,7 +67,7 @@ impl<'de> Deserialize<'de> for Metrics { } else if let Some(vec) = value.as_array() { if vec.len() <= MAX_VECS { Ok(Self( - sanitize(vec.iter().map(|s| s.as_str().unwrap().to_string())) + sanitize(vec.iter().filter_map(|s| s.as_str().map(String::from))) .into_iter() .map(Metric::from) .collect(), diff --git a/crates/brk_types/src/pagination.rs b/crates/brk_types/src/pagination.rs index 6213b883f..20561b790 100644 --- a/crates/brk_types/src/pagination.rs +++ b/crates/brk_types/src/pagination.rs @@ -29,10 +29,14 @@ impl Pagination { } pub fn start(&self, len: usize) -> usize { - (self.page() * self.per_page()).clamp(0, len) + self.page() + .saturating_mul(self.per_page()) + .min(len) } pub fn end(&self, len: usize) -> usize { - ((self.page() + 1) * self.per_page()).clamp(0, len) + (self.page().saturating_add(1)) + .saturating_mul(self.per_page()) + .min(len) } } diff --git a/crates/brk_types/src/range_index.rs b/crates/brk_types/src/range_index.rs index d8865fe1b..d583d6f9b 100644 --- a/crates/brk_types/src/range_index.rs +++ b/crates/brk_types/src/range_index.rs @@ -56,7 +56,13 @@ impl<'de> Deserialize<'de> for RangeIndex { return Ok(Self::Date(date)); } if let Ok(ts) = s.parse::() { - return Ok(Self::Timestamp(Timestamp::new(ts.as_second() as u32))); + let secs = ts.as_second(); + if secs < 0 || secs > u32::MAX as i64 { + return Err(serde::de::Error::custom(format!( + "timestamp out of range: {s}" + ))); + } + return Ok(Self::Timestamp(Timestamp::new(secs as u32))); } Err(serde::de::Error::custom(format!( "expected integer, YYYY-MM-DD, or ISO 8601 timestamp: {s}" diff --git a/crates/brk_types/src/sats_fract.rs b/crates/brk_types/src/sats_fract.rs index 72d49723a..fa21dda0e 100644 --- a/crates/brk_types/src/sats_fract.rs +++ b/crates/brk_types/src/sats_fract.rs @@ -189,7 +189,7 @@ impl std::fmt::Display for SatsFract { impl Formattable for SatsFract { #[inline(always)] fn write_to(&self, buf: &mut Vec) { - if !self.0.is_nan() { + if self.0.is_finite() { let mut b = ryu::Buffer::new(); buf.extend_from_slice(b.format(self.0).as_bytes()); } @@ -197,10 +197,10 @@ impl Formattable for SatsFract { #[inline(always)] fn fmt_json(&self, buf: &mut Vec) { - if self.0.is_nan() { - buf.extend_from_slice(b"null"); - } else { + if self.0.is_finite() { self.write_to(buf); + } else { + buf.extend_from_slice(b"null"); } } } diff --git a/crates/brk_types/src/stored_bool.rs b/crates/brk_types/src/stored_bool.rs index 2f2b41321..b761310a1 100644 --- a/crates/brk_types/src/stored_bool.rs +++ b/crates/brk_types/src/stored_bool.rs @@ -1,5 +1,5 @@ use derive_more::Deref; -use schemars::JsonSchema; +use schemars::{JsonSchema, SchemaGenerator}; use serde::{Deserialize, Serialize}; use vecdb::{Formattable, Pco, PrintableIndex}; @@ -17,10 +17,19 @@ use vecdb::{Formattable, Pco, PrintableIndex}; Serialize, Deserialize, Pco, - JsonSchema, )] pub struct StoredBool(u8); +impl JsonSchema for StoredBool { + fn schema_name() -> std::borrow::Cow<'static, str> { + "StoredBool".into() + } + + fn json_schema(generator: &mut SchemaGenerator) -> schemars::Schema { + bool::json_schema(generator) + } +} + impl StoredBool { pub const FALSE: Self = Self(0); pub const TRUE: Self = Self(1); diff --git a/crates/brk_types/src/stored_f32.rs b/crates/brk_types/src/stored_f32.rs index 22238043e..16edcf54e 100644 --- a/crates/brk_types/src/stored_f32.rs +++ b/crates/brk_types/src/stored_f32.rs @@ -267,7 +267,7 @@ impl std::fmt::Display for StoredF32 { impl Formattable for StoredF32 { #[inline(always)] fn write_to(&self, buf: &mut Vec) { - if !self.0.is_nan() { + if self.0.is_finite() { let mut b = ryu::Buffer::new(); buf.extend_from_slice(b.format(self.0).as_bytes()); } @@ -275,10 +275,10 @@ impl Formattable for StoredF32 { #[inline(always)] fn fmt_json(&self, buf: &mut Vec) { - if self.0.is_nan() { - buf.extend_from_slice(b"null"); - } else { + if self.0.is_finite() { self.write_to(buf); + } else { + buf.extend_from_slice(b"null"); } } } diff --git a/crates/brk_types/src/stored_f64.rs b/crates/brk_types/src/stored_f64.rs index a42ca9238..e31a1f176 100644 --- a/crates/brk_types/src/stored_f64.rs +++ b/crates/brk_types/src/stored_f64.rs @@ -247,7 +247,7 @@ impl std::fmt::Display for StoredF64 { impl Formattable for StoredF64 { #[inline(always)] fn write_to(&self, buf: &mut Vec) { - if !self.0.is_nan() { + if self.0.is_finite() { let mut b = ryu::Buffer::new(); buf.extend_from_slice(b.format(self.0).as_bytes()); } @@ -255,10 +255,10 @@ impl Formattable for StoredF64 { #[inline(always)] fn fmt_json(&self, buf: &mut Vec) { - if self.0.is_nan() { - buf.extend_from_slice(b"null"); - } else { + if self.0.is_finite() { self.write_to(buf); + } else { + buf.extend_from_slice(b"null"); } } } diff --git a/modules/brk-client/index.js b/modules/brk-client/index.js index a36ce4ff7..3b619bcca 100644 --- a/modules/brk-client/index.js +++ b/modules/brk-client/index.js @@ -217,7 +217,7 @@ /** * Closing price value for a time period * - * @typedef {Cents} Close + * @typedef {Dollars} Close */ /** * Cohort identifier for cost basis distribution. @@ -417,7 +417,7 @@ /** * Highest price value for a time period * - * @typedef {Cents} High + * @typedef {Dollars} High */ /** @typedef {number} Hour1 */ /** @typedef {number} Hour12 */ @@ -443,7 +443,7 @@ /** * Lowest price value for a time period * - * @typedef {Cents} Low + * @typedef {Dollars} Low */ /** * Block info in a mempool.space like format for fee estimation. @@ -566,7 +566,7 @@ /** * Opening price value for a time period * - * @typedef {Cents} Open + * @typedef {Dollars} Open */ /** @typedef {number} OutPoint */ /** @@ -738,11 +738,7 @@ * @property {Metric} q - Search query string * @property {Limit=} limit - Maximum number of results */ -/** - * Fixed-size boolean value optimized for on-disk storage (stored as u8) - * - * @typedef {number} StoredBool - */ +/** @typedef {boolean} StoredBool */ /** * Stored 32-bit floating point value * @@ -2326,7 +2322,7 @@ function create_1m1w1y2y4yAllPattern(client, acc) { * @property {CentsUsdPattern} base * @property {RelPattern} change1m * @property {CentsUsdPattern} cumulative - * @property {ChangeRatePattern2} delta + * @property {AbsoluteRatePattern2} delta * @property {BpsPercentRatioPattern} relToRcap * @property {_1m1w1y24hPattern3} sum */ @@ -2342,7 +2338,7 @@ function createBaseChangeCumulativeDeltaRelSumPattern(client, acc) { base: createCentsUsdPattern(client, _m(acc, 'realized_pnl')), change1m: createRelPattern(client, _m(acc, 'pnl_change_1m_rel_to')), cumulative: createCentsUsdPattern(client, _m(acc, 'realized_pnl_cumulative')), - delta: createChangeRatePattern2(client, _m(acc, 'realized_pnl_delta')), + delta: createAbsoluteRatePattern2(client, _m(acc, 'realized_pnl_delta')), relToRcap: createBpsPercentRatioPattern(client, _m(acc, 'realized_pnl_rel_to_rcap')), sum: create_1m1w1y24hPattern3(client, _m(acc, 'realized_pnl_sum')), }; @@ -2458,7 +2454,7 @@ function createCapLossMvrvPriceProfitSoprPattern(client, acc) { /** * @typedef {Object} DeltaHalfInRelTotalPattern - * @property {ChangeRatePattern} delta + * @property {AbsoluteRatePattern} delta * @property {BtcCentsSatsUsdPattern} half * @property {BtcCentsRelSatsUsdPattern} inLoss * @property {BtcCentsRelSatsUsdPattern} inProfit @@ -2474,7 +2470,7 @@ function createCapLossMvrvPriceProfitSoprPattern(client, acc) { */ function createDeltaHalfInRelTotalPattern(client, acc) { return { - delta: createChangeRatePattern(client, _m(acc, 'delta')), + delta: createAbsoluteRatePattern(client, _m(acc, 'delta')), half: createBtcCentsSatsUsdPattern(client, _m(acc, 'half')), inLoss: createBtcCentsRelSatsUsdPattern(client, _m(acc, 'in_loss')), inProfit: createBtcCentsRelSatsUsdPattern(client, _m(acc, 'in_profit')), @@ -2485,7 +2481,7 @@ function createDeltaHalfInRelTotalPattern(client, acc) { /** * @typedef {Object} DeltaHalfInRelTotalPattern2 - * @property {ChangeRatePattern} delta + * @property {AbsoluteRatePattern} delta * @property {BtcCentsSatsUsdPattern} half * @property {BtcCentsRelSatsUsdPattern3} inLoss * @property {BtcCentsRelSatsUsdPattern3} inProfit @@ -2501,7 +2497,7 @@ function createDeltaHalfInRelTotalPattern(client, acc) { */ function createDeltaHalfInRelTotalPattern2(client, acc) { return { - delta: createChangeRatePattern(client, _m(acc, 'delta')), + delta: createAbsoluteRatePattern(client, _m(acc, 'delta')), half: createBtcCentsSatsUsdPattern(client, _m(acc, 'half')), inLoss: createBtcCentsRelSatsUsdPattern3(client, _m(acc, 'in_loss')), inProfit: createBtcCentsRelSatsUsdPattern3(client, _m(acc, 'in_profit')), @@ -2689,7 +2685,7 @@ function createBtcCentsRelSatsUsdPattern2(client, acc) { /** * @typedef {Object} DeltaHalfInTotalPattern2 - * @property {ChangeRatePattern} delta + * @property {AbsoluteRatePattern} delta * @property {BtcCentsSatsUsdPattern} half * @property {BtcCentsSatsUsdPattern} inLoss * @property {BtcCentsSatsUsdPattern} inProfit @@ -2704,7 +2700,7 @@ function createBtcCentsRelSatsUsdPattern2(client, acc) { */ function createDeltaHalfInTotalPattern2(client, acc) { return { - delta: createChangeRatePattern(client, _m(acc, 'delta')), + delta: createAbsoluteRatePattern(client, _m(acc, 'delta')), half: createBtcCentsSatsUsdPattern(client, _m(acc, 'half')), inLoss: createBtcCentsSatsUsdPattern(client, _m(acc, 'in_loss')), inProfit: createBtcCentsSatsUsdPattern(client, _m(acc, 'in_profit')), @@ -3002,7 +2998,7 @@ function createAdjustedRatioValuePattern(client, acc) { * @typedef {Object} BaseCumulativeDeltaSumPattern * @property {CentsUsdPattern} base * @property {CentsUsdPattern} cumulative - * @property {ChangeRatePattern2} delta + * @property {AbsoluteRatePattern2} delta * @property {_1m1w1y24hPattern3} sum */ @@ -3016,7 +3012,7 @@ function createBaseCumulativeDeltaSumPattern(client, acc) { return { base: createCentsUsdPattern(client, acc), cumulative: createCentsUsdPattern(client, _m(acc, 'cumulative')), - delta: createChangeRatePattern2(client, _m(acc, 'delta')), + delta: createAbsoluteRatePattern2(client, _m(acc, 'delta')), sum: create_1m1w1y24hPattern3(client, _m(acc, 'sum')), }; } @@ -3093,7 +3089,7 @@ function createBtcCentsSatsUsdPattern(client, acc) { /** * @typedef {Object} CentsDeltaRelUsdPattern * @property {MetricPattern1} cents - * @property {ChangeRatePattern2} delta + * @property {AbsoluteRatePattern2} delta * @property {BpsPercentRatioPattern4} relToOwnMcap * @property {MetricPattern1} usd */ @@ -3107,7 +3103,7 @@ function createBtcCentsSatsUsdPattern(client, acc) { function createCentsDeltaRelUsdPattern(client, acc) { return { cents: createMetricPattern1(client, _m(acc, 'cents')), - delta: createChangeRatePattern2(client, _m(acc, 'delta')), + delta: createAbsoluteRatePattern2(client, _m(acc, 'delta')), relToOwnMcap: createBpsPercentRatioPattern4(client, _m(acc, 'rel_to_own_mcap')), usd: createMetricPattern1(client, acc), }; @@ -3487,7 +3483,7 @@ function createCentsSatsUsdPattern3(client, acc) { /** * @typedef {Object} CentsDeltaUsdPattern * @property {MetricPattern1} cents - * @property {ChangeRatePattern2} delta + * @property {AbsoluteRatePattern2} delta * @property {MetricPattern1} usd */ @@ -3500,7 +3496,7 @@ function createCentsSatsUsdPattern3(client, acc) { function createCentsDeltaUsdPattern(client, acc) { return { cents: createMetricPattern1(client, _m(acc, 'cents')), - delta: createChangeRatePattern2(client, _m(acc, 'delta')), + delta: createAbsoluteRatePattern2(client, _m(acc, 'delta')), usd: createMetricPattern1(client, acc), }; } @@ -3528,7 +3524,7 @@ function createCentsSatsUsdPattern(client, acc) { /** * @typedef {Object} DeltaHalfTotalPattern - * @property {ChangeRatePattern} delta + * @property {AbsoluteRatePattern} delta * @property {BtcCentsSatsUsdPattern} half * @property {BtcCentsSatsUsdPattern} total */ @@ -3541,7 +3537,7 @@ function createCentsSatsUsdPattern(client, acc) { */ function createDeltaHalfTotalPattern(client, acc) { return { - delta: createChangeRatePattern(client, _m(acc, 'delta')), + delta: createAbsoluteRatePattern(client, _m(acc, 'delta')), half: createBtcCentsSatsUsdPattern(client, _m(acc, 'half')), total: createBtcCentsSatsUsdPattern(client, acc), }; @@ -3698,6 +3694,44 @@ function createBaseCumulativeSumPattern(client, acc) { }; } +/** + * @typedef {Object} AbsoluteRatePattern + * @property {_1m1w1y24hPattern} absolute + * @property {_1m1w1y24hPattern2} rate + */ + +/** + * Create a AbsoluteRatePattern pattern node + * @param {BrkClientBase} client + * @param {string} acc - Accumulated metric name + * @returns {AbsoluteRatePattern} + */ +function createAbsoluteRatePattern(client, acc) { + return { + absolute: create_1m1w1y24hPattern(client, acc), + rate: create_1m1w1y24hPattern2(client, acc), + }; +} + +/** + * @typedef {Object} AbsoluteRatePattern2 + * @property {_1m1w1y24hPattern3} absolute + * @property {_1m1w1y24hPattern2} rate + */ + +/** + * Create a AbsoluteRatePattern2 pattern node + * @param {BrkClientBase} client + * @param {string} acc - Accumulated metric name + * @returns {AbsoluteRatePattern2} + */ +function createAbsoluteRatePattern2(client, acc) { + return { + absolute: create_1m1w1y24hPattern3(client, acc), + rate: create_1m1w1y24hPattern2(client, acc), + }; +} + /** * @typedef {Object} BlocksDominancePattern * @property {BaseCumulativeSumPattern2} blocksMined @@ -3793,44 +3827,6 @@ function createCentsUsdPattern(client, acc) { }; } -/** - * @typedef {Object} ChangeRatePattern - * @property {_1m1w1y24hPattern} change - * @property {_1m1w1y24hPattern2} rate - */ - -/** - * Create a ChangeRatePattern pattern node - * @param {BrkClientBase} client - * @param {string} acc - Accumulated metric name - * @returns {ChangeRatePattern} - */ -function createChangeRatePattern(client, acc) { - return { - change: create_1m1w1y24hPattern(client, acc), - rate: create_1m1w1y24hPattern2(client, acc), - }; -} - -/** - * @typedef {Object} ChangeRatePattern2 - * @property {_1m1w1y24hPattern3} change - * @property {_1m1w1y24hPattern2} rate - */ - -/** - * Create a ChangeRatePattern2 pattern node - * @param {BrkClientBase} client - * @param {string} acc - Accumulated metric name - * @returns {ChangeRatePattern2} - */ -function createChangeRatePattern2(client, acc) { - return { - change: create_1m1w1y24hPattern3(client, acc), - rate: create_1m1w1y24hPattern2(client, acc), - }; -} - /** * @typedef {Object} CoindaysSentPattern * @property {BaseCumulativeSumPattern} coindaysDestroyed @@ -3852,7 +3848,7 @@ function createCoindaysSentPattern(client, acc) { /** * @typedef {Object} DeltaInnerPattern - * @property {ChangeRatePattern} delta + * @property {AbsoluteRatePattern} delta * @property {MetricPattern1} inner */ @@ -3864,7 +3860,7 @@ function createCoindaysSentPattern(client, acc) { */ function createDeltaInnerPattern(client, acc) { return { - delta: createChangeRatePattern(client, _m(acc, 'delta')), + delta: createAbsoluteRatePattern(client, _m(acc, 'delta')), inner: createMetricPattern1(client, acc), }; } @@ -4415,15 +4411,15 @@ function createUnspentPattern(client, acc) { /** * @typedef {Object} MetricsTree_Addresses_Delta - * @property {ChangeRatePattern} all - * @property {ChangeRatePattern} p2pk65 - * @property {ChangeRatePattern} p2pk33 - * @property {ChangeRatePattern} p2pkh - * @property {ChangeRatePattern} p2sh - * @property {ChangeRatePattern} p2wpkh - * @property {ChangeRatePattern} p2wsh - * @property {ChangeRatePattern} p2tr - * @property {ChangeRatePattern} p2a + * @property {AbsoluteRatePattern} all + * @property {AbsoluteRatePattern} p2pk65 + * @property {AbsoluteRatePattern} p2pk33 + * @property {AbsoluteRatePattern} p2pkh + * @property {AbsoluteRatePattern} p2sh + * @property {AbsoluteRatePattern} p2wpkh + * @property {AbsoluteRatePattern} p2wsh + * @property {AbsoluteRatePattern} p2tr + * @property {AbsoluteRatePattern} p2a */ /** @@ -4627,8 +4623,8 @@ function createUnspentPattern(client, acc) { /** * @typedef {Object} MetricsTree_Cointime_Adjusted * @property {BpsPercentRatioPattern} inflationRate - * @property {MetricPattern1} txVelocityBtc - * @property {MetricPattern1} txVelocityUsd + * @property {MetricPattern1} txVelocityNative + * @property {MetricPattern1} txVelocityFiat */ /** @@ -5051,8 +5047,8 @@ function createUnspentPattern(client, acc) { /** * @typedef {Object} MetricsTree_Market_MovingAverage_Sma_200d - * @property {MetricPattern1} cents * @property {MetricPattern1} usd + * @property {MetricPattern1} cents * @property {MetricPattern1} sats * @property {MetricPattern1} bps * @property {MetricPattern1} ratio @@ -5062,22 +5058,22 @@ function createUnspentPattern(client, acc) { /** * @typedef {Object} MetricsTree_Market_MovingAverage_Sma_200d_X24 - * @property {MetricPattern1} cents * @property {MetricPattern1} usd + * @property {MetricPattern1} cents * @property {MetricPattern1} sats */ /** * @typedef {Object} MetricsTree_Market_MovingAverage_Sma_200d_X08 - * @property {MetricPattern1} cents * @property {MetricPattern1} usd + * @property {MetricPattern1} cents * @property {MetricPattern1} sats */ /** * @typedef {Object} MetricsTree_Market_MovingAverage_Sma_350d - * @property {MetricPattern1} cents * @property {MetricPattern1} usd + * @property {MetricPattern1} cents * @property {MetricPattern1} sats * @property {MetricPattern1} bps * @property {MetricPattern1} ratio @@ -5086,8 +5082,8 @@ function createUnspentPattern(client, acc) { /** * @typedef {Object} MetricsTree_Market_MovingAverage_Sma_350d_X2 - * @property {MetricPattern1} cents * @property {MetricPattern1} usd + * @property {MetricPattern1} cents * @property {MetricPattern1} sats */ @@ -5487,15 +5483,15 @@ function createUnspentPattern(client, acc) { /** * @typedef {Object} MetricsTree_Prices_Ohlc - * @property {MetricPattern2} cents * @property {MetricPattern2} usd + * @property {MetricPattern2} cents * @property {MetricPattern2} sats */ /** * @typedef {Object} MetricsTree_Prices_Spot - * @property {MetricPattern1} cents * @property {MetricPattern1} usd + * @property {MetricPattern1} cents * @property {MetricPattern1} sats */ @@ -5561,7 +5557,7 @@ function createUnspentPattern(client, acc) { * @typedef {Object} MetricsTree_Cohorts_Utxo_All_Supply * @property {BtcCentsSatsUsdPattern} total * @property {BtcCentsSatsUsdPattern} half - * @property {ChangeRatePattern} delta + * @property {AbsoluteRatePattern} delta * @property {BtcCentsRelSatsUsdPattern2} inProfit * @property {BtcCentsRelSatsUsdPattern2} inLoss */ @@ -5598,8 +5594,8 @@ function createUnspentPattern(client, acc) { /** * @typedef {Object} MetricsTree_Cohorts_Utxo_All_Unrealized_NetPnl - * @property {MetricPattern1} cents * @property {MetricPattern1} usd + * @property {MetricPattern1} cents * @property {BpsPercentRatioPattern} relToOwnGross */ @@ -7161,15 +7157,15 @@ class BrkClient extends BrkClientBase { p2a: createBaseCumulativeSumPattern(this, 'p2a_new_address_count'), }, delta: { - all: createChangeRatePattern(this, 'address_count'), - p2pk65: createChangeRatePattern(this, 'p2pk65_address_count'), - p2pk33: createChangeRatePattern(this, 'p2pk33_address_count'), - p2pkh: createChangeRatePattern(this, 'p2pkh_address_count'), - p2sh: createChangeRatePattern(this, 'p2sh_address_count'), - p2wpkh: createChangeRatePattern(this, 'p2wpkh_address_count'), - p2wsh: createChangeRatePattern(this, 'p2wsh_address_count'), - p2tr: createChangeRatePattern(this, 'p2tr_address_count'), - p2a: createChangeRatePattern(this, 'p2a_address_count'), + all: createAbsoluteRatePattern(this, 'address_count'), + p2pk65: createAbsoluteRatePattern(this, 'p2pk65_address_count'), + p2pk33: createAbsoluteRatePattern(this, 'p2pk33_address_count'), + p2pkh: createAbsoluteRatePattern(this, 'p2pkh_address_count'), + p2sh: createAbsoluteRatePattern(this, 'p2sh_address_count'), + p2wpkh: createAbsoluteRatePattern(this, 'p2wpkh_address_count'), + p2wsh: createAbsoluteRatePattern(this, 'p2wsh_address_count'), + p2tr: createAbsoluteRatePattern(this, 'p2tr_address_count'), + p2a: createAbsoluteRatePattern(this, 'p2a_address_count'), }, }, scripts: { @@ -7299,8 +7295,8 @@ class BrkClient extends BrkClientBase { }, adjusted: { inflationRate: createBpsPercentRatioPattern(this, 'cointime_adj_inflation_rate'), - txVelocityBtc: createMetricPattern1(this, 'cointime_adj_tx_velocity_btc'), - txVelocityUsd: createMetricPattern1(this, 'cointime_adj_tx_velocity_usd'), + txVelocityNative: createMetricPattern1(this, 'cointime_adj_tx_velocity'), + txVelocityFiat: createMetricPattern1(this, 'cointime_adj_tx_velocity_fiat'), }, reserveRisk: { value: createMetricPattern1(this, 'reserve_risk'), @@ -7569,31 +7565,31 @@ class BrkClient extends BrkClientBase { _111d: createBpsCentsRatioSatsUsdPattern(this, 'price_sma_111d'), _144d: createBpsCentsRatioSatsUsdPattern(this, 'price_sma_144d'), _200d: { - cents: createMetricPattern1(this, 'price_sma_200d_cents'), usd: createMetricPattern1(this, 'price_sma_200d'), + cents: createMetricPattern1(this, 'price_sma_200d_cents'), sats: createMetricPattern1(this, 'price_sma_200d_sats'), bps: createMetricPattern1(this, 'price_sma_200d_ratio_bps'), ratio: createMetricPattern1(this, 'price_sma_200d_ratio'), x24: { - cents: createMetricPattern1(this, 'price_sma_200d_x2_4_cents'), usd: createMetricPattern1(this, 'price_sma_200d_x2_4_usd'), + cents: createMetricPattern1(this, 'price_sma_200d_x2_4_cents'), sats: createMetricPattern1(this, 'price_sma_200d_x2_4_sats'), }, x08: { - cents: createMetricPattern1(this, 'price_sma_200d_x0_8_cents'), usd: createMetricPattern1(this, 'price_sma_200d_x0_8_usd'), + cents: createMetricPattern1(this, 'price_sma_200d_x0_8_cents'), sats: createMetricPattern1(this, 'price_sma_200d_x0_8_sats'), }, }, _350d: { - cents: createMetricPattern1(this, 'price_sma_350d_cents'), usd: createMetricPattern1(this, 'price_sma_350d'), + cents: createMetricPattern1(this, 'price_sma_350d_cents'), sats: createMetricPattern1(this, 'price_sma_350d_sats'), bps: createMetricPattern1(this, 'price_sma_350d_ratio_bps'), ratio: createMetricPattern1(this, 'price_sma_350d_ratio'), x2: { - cents: createMetricPattern1(this, 'price_sma_350d_x2_cents'), usd: createMetricPattern1(this, 'price_sma_350d_x2_usd'), + cents: createMetricPattern1(this, 'price_sma_350d_x2_cents'), sats: createMetricPattern1(this, 'price_sma_350d_x2_sats'), }, }, @@ -7935,13 +7931,13 @@ class BrkClient extends BrkClientBase { close: createCentsSatsUsdPattern3(this, 'price_close'), }, ohlc: { - cents: createMetricPattern2(this, 'price_ohlc_cents'), usd: createMetricPattern2(this, 'price_ohlc'), + cents: createMetricPattern2(this, 'price_ohlc_cents'), sats: createMetricPattern2(this, 'price_ohlc_sats'), }, spot: { - cents: createMetricPattern1(this, 'price_cents'), usd: createMetricPattern1(this, 'price'), + cents: createMetricPattern1(this, 'price_cents'), sats: createMetricPattern1(this, 'price_sats'), }, }, @@ -7967,7 +7963,7 @@ class BrkClient extends BrkClientBase { supply: { total: createBtcCentsSatsUsdPattern(this, 'supply'), half: createBtcCentsSatsUsdPattern(this, 'supply_half'), - delta: createChangeRatePattern(this, 'supply_delta'), + delta: createAbsoluteRatePattern(this, 'supply_delta'), inProfit: createBtcCentsRelSatsUsdPattern2(this, 'supply_in_profit'), inLoss: createBtcCentsRelSatsUsdPattern2(this, 'supply_in_loss'), }, @@ -7993,8 +7989,8 @@ class BrkClient extends BrkClientBase { relToOwnGross: createBpsPercentRatioPattern3(this, 'unrealized_loss_rel_to_own_gross_pnl'), }, netPnl: { - cents: createMetricPattern1(this, 'net_unrealized_pnl_cents'), usd: createMetricPattern1(this, 'net_unrealized_pnl'), + cents: createMetricPattern1(this, 'net_unrealized_pnl_cents'), relToOwnGross: createBpsPercentRatioPattern(this, 'net_unrealized_pnl_rel_to_own_gross_pnl'), }, grossPnl: createCentsUsdPattern2(this, 'unrealized_gross_pnl'), diff --git a/packages/brk_client/brk_client/__init__.py b/packages/brk_client/brk_client/__init__.py index 007bdbff5..0213b27f2 100644 --- a/packages/brk_client/brk_client/__init__.py +++ b/packages/brk_client/brk_client/__init__.py @@ -67,8 +67,10 @@ CentsSigned = int # Used for precise accumulation of investor cap values: Σ(price² × sats). # investor_price = investor_cap_raw / realized_cap_raw CentsSquaredSats = int +# US Dollar amount as floating point +Dollars = float # Closing price value for a time period -Close = Cents +Close = Dollars # Cohort identifier for cost basis distribution. Cohort = str # Bucket type for cost basis aggregation. @@ -88,8 +90,6 @@ Limit = int RangeIndex = Union[int, Date, Timestamp] Day1 = int Day3 = int -# US Dollar amount as floating point -Dollars = float EmptyAddressIndex = TypeIndex EmptyOutputIndex = TypeIndex Epoch = int @@ -100,12 +100,12 @@ Halving = int # Hex-encoded string Hex = str # Highest price value for a time period -High = Cents +High = Dollars Hour1 = int Hour12 = int Hour4 = int # Lowest price value for a time period -Low = Cents +Low = Dollars # Virtual size in vbytes (weight / 4, rounded up) VSize = int # Metric name @@ -124,7 +124,7 @@ Month1 = int Month3 = int Month6 = int # Opening price value for a time period -Open = Cents +Open = Dollars OpReturnIndex = TypeIndex OutPoint = int # Type (P2PKH, P2WPKH, P2SH, P2TR, etc.) @@ -166,8 +166,7 @@ SatsFract = float # Signed satoshis (i64) - for values that can be negative. # Used for changes, deltas, profit/loss calculations, etc. SatsSigned = int -# Fixed-size boolean value optimized for on-disk storage (stored as u8) -StoredBool = int +StoredBool = bool # Stored 32-bit floating point value StoredF32 = float # Fixed-size 64-bit floating point value optimized for on-disk storage @@ -2454,7 +2453,7 @@ class BaseChangeCumulativeDeltaRelSumPattern: self.base: CentsUsdPattern = CentsUsdPattern(client, _m(acc, 'realized_pnl')) self.change_1m: RelPattern = RelPattern(client, _m(acc, 'pnl_change_1m_rel_to')) self.cumulative: CentsUsdPattern = CentsUsdPattern(client, _m(acc, 'realized_pnl_cumulative')) - self.delta: ChangeRatePattern2 = ChangeRatePattern2(client, _m(acc, 'realized_pnl_delta')) + self.delta: AbsoluteRatePattern2 = AbsoluteRatePattern2(client, _m(acc, 'realized_pnl_delta')) self.rel_to_rcap: BpsPercentRatioPattern = BpsPercentRatioPattern(client, _m(acc, 'realized_pnl_rel_to_rcap')) self.sum: _1m1w1y24hPattern3 = _1m1w1y24hPattern3(client, _m(acc, 'realized_pnl_sum')) @@ -2511,7 +2510,7 @@ class DeltaHalfInRelTotalPattern: def __init__(self, client: BrkClientBase, acc: str): """Create pattern node with accumulated metric name.""" - self.delta: ChangeRatePattern = ChangeRatePattern(client, _m(acc, 'delta')) + self.delta: AbsoluteRatePattern = AbsoluteRatePattern(client, _m(acc, 'delta')) self.half: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, _m(acc, 'half')) self.in_loss: BtcCentsRelSatsUsdPattern = BtcCentsRelSatsUsdPattern(client, _m(acc, 'in_loss')) self.in_profit: BtcCentsRelSatsUsdPattern = BtcCentsRelSatsUsdPattern(client, _m(acc, 'in_profit')) @@ -2523,7 +2522,7 @@ class DeltaHalfInRelTotalPattern2: def __init__(self, client: BrkClientBase, acc: str): """Create pattern node with accumulated metric name.""" - self.delta: ChangeRatePattern = ChangeRatePattern(client, _m(acc, 'delta')) + self.delta: AbsoluteRatePattern = AbsoluteRatePattern(client, _m(acc, 'delta')) self.half: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, _m(acc, 'half')) self.in_loss: BtcCentsRelSatsUsdPattern3 = BtcCentsRelSatsUsdPattern3(client, _m(acc, 'in_loss')) self.in_profit: BtcCentsRelSatsUsdPattern3 = BtcCentsRelSatsUsdPattern3(client, _m(acc, 'in_profit')) @@ -2613,7 +2612,7 @@ class DeltaHalfInTotalPattern2: def __init__(self, client: BrkClientBase, acc: str): """Create pattern node with accumulated metric name.""" - self.delta: ChangeRatePattern = ChangeRatePattern(client, _m(acc, 'delta')) + self.delta: AbsoluteRatePattern = AbsoluteRatePattern(client, _m(acc, 'delta')) self.half: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, _m(acc, 'half')) self.in_loss: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, _m(acc, 'in_loss')) self.in_profit: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, _m(acc, 'in_profit')) @@ -2750,7 +2749,7 @@ class BaseCumulativeDeltaSumPattern: """Create pattern node with accumulated metric name.""" self.base: CentsUsdPattern = CentsUsdPattern(client, acc) self.cumulative: CentsUsdPattern = CentsUsdPattern(client, _m(acc, 'cumulative')) - self.delta: ChangeRatePattern2 = ChangeRatePattern2(client, _m(acc, 'delta')) + self.delta: AbsoluteRatePattern2 = AbsoluteRatePattern2(client, _m(acc, 'delta')) self.sum: _1m1w1y24hPattern3 = _1m1w1y24hPattern3(client, _m(acc, 'sum')) class BaseCumulativeNegativeSumPattern: @@ -2789,7 +2788,7 @@ class CentsDeltaRelUsdPattern: def __init__(self, client: BrkClientBase, acc: str): """Create pattern node with accumulated metric name.""" self.cents: MetricPattern1[Cents] = MetricPattern1(client, _m(acc, 'cents')) - self.delta: ChangeRatePattern2 = ChangeRatePattern2(client, _m(acc, 'delta')) + self.delta: AbsoluteRatePattern2 = AbsoluteRatePattern2(client, _m(acc, 'delta')) self.rel_to_own_mcap: BpsPercentRatioPattern4 = BpsPercentRatioPattern4(client, _m(acc, 'rel_to_own_mcap')) self.usd: MetricPattern1[Dollars] = MetricPattern1(client, acc) @@ -2958,7 +2957,7 @@ class CentsDeltaUsdPattern: def __init__(self, client: BrkClientBase, acc: str): """Create pattern node with accumulated metric name.""" self.cents: MetricPattern1[Cents] = MetricPattern1(client, _m(acc, 'cents')) - self.delta: ChangeRatePattern2 = ChangeRatePattern2(client, _m(acc, 'delta')) + self.delta: AbsoluteRatePattern2 = AbsoluteRatePattern2(client, _m(acc, 'delta')) self.usd: MetricPattern1[Dollars] = MetricPattern1(client, acc) class CentsSatsUsdPattern: @@ -2975,7 +2974,7 @@ class DeltaHalfTotalPattern: def __init__(self, client: BrkClientBase, acc: str): """Create pattern node with accumulated metric name.""" - self.delta: ChangeRatePattern = ChangeRatePattern(client, _m(acc, 'delta')) + self.delta: AbsoluteRatePattern = AbsoluteRatePattern(client, _m(acc, 'delta')) self.half: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, _m(acc, 'half')) self.total: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, acc) @@ -3042,6 +3041,22 @@ class BaseCumulativeSumPattern(Generic[T]): self.cumulative: MetricPattern1[T] = MetricPattern1(client, _m(acc, 'cumulative')) self.sum: _1m1w1y24hPattern[T] = _1m1w1y24hPattern(client, _m(acc, 'sum')) +class AbsoluteRatePattern: + """Pattern struct for repeated tree structure.""" + + def __init__(self, client: BrkClientBase, acc: str): + """Create pattern node with accumulated metric name.""" + self.absolute: _1m1w1y24hPattern[StoredI64] = _1m1w1y24hPattern(client, acc) + self.rate: _1m1w1y24hPattern2 = _1m1w1y24hPattern2(client, acc) + +class AbsoluteRatePattern2: + """Pattern struct for repeated tree structure.""" + + def __init__(self, client: BrkClientBase, acc: str): + """Create pattern node with accumulated metric name.""" + self.absolute: _1m1w1y24hPattern3 = _1m1w1y24hPattern3(client, acc) + self.rate: _1m1w1y24hPattern2 = _1m1w1y24hPattern2(client, acc) + class BlocksDominancePattern: """Pattern struct for repeated tree structure.""" @@ -3082,22 +3097,6 @@ class CentsUsdPattern: self.cents: MetricPattern1[CentsSigned] = MetricPattern1(client, acc) self.usd: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'usd')) -class ChangeRatePattern: - """Pattern struct for repeated tree structure.""" - - def __init__(self, client: BrkClientBase, acc: str): - """Create pattern node with accumulated metric name.""" - self.change: _1m1w1y24hPattern[StoredI64] = _1m1w1y24hPattern(client, acc) - self.rate: _1m1w1y24hPattern2 = _1m1w1y24hPattern2(client, acc) - -class ChangeRatePattern2: - """Pattern struct for repeated tree structure.""" - - def __init__(self, client: BrkClientBase, acc: str): - """Create pattern node with accumulated metric name.""" - self.change: _1m1w1y24hPattern3 = _1m1w1y24hPattern3(client, acc) - self.rate: _1m1w1y24hPattern2 = _1m1w1y24hPattern2(client, acc) - class CoindaysSentPattern: """Pattern struct for repeated tree structure.""" @@ -3111,7 +3110,7 @@ class DeltaInnerPattern: def __init__(self, client: BrkClientBase, acc: str): """Create pattern node with accumulated metric name.""" - self.delta: ChangeRatePattern = ChangeRatePattern(client, _m(acc, 'delta')) + self.delta: AbsoluteRatePattern = AbsoluteRatePattern(client, _m(acc, 'delta')) self.inner: MetricPattern1[StoredU64] = MetricPattern1(client, acc) class InPattern: @@ -3569,15 +3568,15 @@ class MetricsTree_Addresses_Delta: """Metrics tree node.""" def __init__(self, client: BrkClientBase, base_path: str = ''): - self.all: ChangeRatePattern = ChangeRatePattern(client, 'address_count') - self.p2pk65: ChangeRatePattern = ChangeRatePattern(client, 'p2pk65_address_count') - self.p2pk33: ChangeRatePattern = ChangeRatePattern(client, 'p2pk33_address_count') - self.p2pkh: ChangeRatePattern = ChangeRatePattern(client, 'p2pkh_address_count') - self.p2sh: ChangeRatePattern = ChangeRatePattern(client, 'p2sh_address_count') - self.p2wpkh: ChangeRatePattern = ChangeRatePattern(client, 'p2wpkh_address_count') - self.p2wsh: ChangeRatePattern = ChangeRatePattern(client, 'p2wsh_address_count') - self.p2tr: ChangeRatePattern = ChangeRatePattern(client, 'p2tr_address_count') - self.p2a: ChangeRatePattern = ChangeRatePattern(client, 'p2a_address_count') + self.all: AbsoluteRatePattern = AbsoluteRatePattern(client, 'address_count') + self.p2pk65: AbsoluteRatePattern = AbsoluteRatePattern(client, 'p2pk65_address_count') + self.p2pk33: AbsoluteRatePattern = AbsoluteRatePattern(client, 'p2pk33_address_count') + self.p2pkh: AbsoluteRatePattern = AbsoluteRatePattern(client, 'p2pkh_address_count') + self.p2sh: AbsoluteRatePattern = AbsoluteRatePattern(client, 'p2sh_address_count') + self.p2wpkh: AbsoluteRatePattern = AbsoluteRatePattern(client, 'p2wpkh_address_count') + self.p2wsh: AbsoluteRatePattern = AbsoluteRatePattern(client, 'p2wsh_address_count') + self.p2tr: AbsoluteRatePattern = AbsoluteRatePattern(client, 'p2tr_address_count') + self.p2a: AbsoluteRatePattern = AbsoluteRatePattern(client, 'p2a_address_count') class MetricsTree_Addresses: """Metrics tree node.""" @@ -3807,8 +3806,8 @@ class MetricsTree_Cointime_Adjusted: def __init__(self, client: BrkClientBase, base_path: str = ''): self.inflation_rate: BpsPercentRatioPattern = BpsPercentRatioPattern(client, 'cointime_adj_inflation_rate') - self.tx_velocity_btc: MetricPattern1[StoredF64] = MetricPattern1(client, 'cointime_adj_tx_velocity_btc') - self.tx_velocity_usd: MetricPattern1[StoredF64] = MetricPattern1(client, 'cointime_adj_tx_velocity_usd') + self.tx_velocity_native: MetricPattern1[StoredF64] = MetricPattern1(client, 'cointime_adj_tx_velocity') + self.tx_velocity_fiat: MetricPattern1[StoredF64] = MetricPattern1(client, 'cointime_adj_tx_velocity_fiat') class MetricsTree_Cointime_ReserveRisk: """Metrics tree node.""" @@ -4253,24 +4252,24 @@ class MetricsTree_Market_MovingAverage_Sma_200d_X24: """Metrics tree node.""" def __init__(self, client: BrkClientBase, base_path: str = ''): - self.cents: MetricPattern1[Cents] = MetricPattern1(client, 'price_sma_200d_x2_4_cents') self.usd: MetricPattern1[Dollars] = MetricPattern1(client, 'price_sma_200d_x2_4_usd') + self.cents: MetricPattern1[Cents] = MetricPattern1(client, 'price_sma_200d_x2_4_cents') self.sats: MetricPattern1[SatsFract] = MetricPattern1(client, 'price_sma_200d_x2_4_sats') class MetricsTree_Market_MovingAverage_Sma_200d_X08: """Metrics tree node.""" def __init__(self, client: BrkClientBase, base_path: str = ''): - self.cents: MetricPattern1[Cents] = MetricPattern1(client, 'price_sma_200d_x0_8_cents') self.usd: MetricPattern1[Dollars] = MetricPattern1(client, 'price_sma_200d_x0_8_usd') + self.cents: MetricPattern1[Cents] = MetricPattern1(client, 'price_sma_200d_x0_8_cents') self.sats: MetricPattern1[SatsFract] = MetricPattern1(client, 'price_sma_200d_x0_8_sats') class MetricsTree_Market_MovingAverage_Sma_200d: """Metrics tree node.""" def __init__(self, client: BrkClientBase, base_path: str = ''): - self.cents: MetricPattern1[Cents] = MetricPattern1(client, 'price_sma_200d_cents') self.usd: MetricPattern1[Dollars] = MetricPattern1(client, 'price_sma_200d') + self.cents: MetricPattern1[Cents] = MetricPattern1(client, 'price_sma_200d_cents') self.sats: MetricPattern1[SatsFract] = MetricPattern1(client, 'price_sma_200d_sats') self.bps: MetricPattern1[BasisPoints32] = MetricPattern1(client, 'price_sma_200d_ratio_bps') self.ratio: MetricPattern1[StoredF32] = MetricPattern1(client, 'price_sma_200d_ratio') @@ -4281,16 +4280,16 @@ class MetricsTree_Market_MovingAverage_Sma_350d_X2: """Metrics tree node.""" def __init__(self, client: BrkClientBase, base_path: str = ''): - self.cents: MetricPattern1[Cents] = MetricPattern1(client, 'price_sma_350d_x2_cents') self.usd: MetricPattern1[Dollars] = MetricPattern1(client, 'price_sma_350d_x2_usd') + self.cents: MetricPattern1[Cents] = MetricPattern1(client, 'price_sma_350d_x2_cents') self.sats: MetricPattern1[SatsFract] = MetricPattern1(client, 'price_sma_350d_x2_sats') class MetricsTree_Market_MovingAverage_Sma_350d: """Metrics tree node.""" def __init__(self, client: BrkClientBase, base_path: str = ''): - self.cents: MetricPattern1[Cents] = MetricPattern1(client, 'price_sma_350d_cents') self.usd: MetricPattern1[Dollars] = MetricPattern1(client, 'price_sma_350d') + self.cents: MetricPattern1[Cents] = MetricPattern1(client, 'price_sma_350d_cents') self.sats: MetricPattern1[SatsFract] = MetricPattern1(client, 'price_sma_350d_sats') self.bps: MetricPattern1[BasisPoints32] = MetricPattern1(client, 'price_sma_350d_ratio_bps') self.ratio: MetricPattern1[StoredF32] = MetricPattern1(client, 'price_sma_350d_ratio') @@ -4749,16 +4748,16 @@ class MetricsTree_Prices_Ohlc: """Metrics tree node.""" def __init__(self, client: BrkClientBase, base_path: str = ''): - self.cents: MetricPattern2[OHLCCents] = MetricPattern2(client, 'price_ohlc_cents') self.usd: MetricPattern2[OHLCDollars] = MetricPattern2(client, 'price_ohlc') + self.cents: MetricPattern2[OHLCCents] = MetricPattern2(client, 'price_ohlc_cents') self.sats: MetricPattern2[OHLCSats] = MetricPattern2(client, 'price_ohlc_sats') class MetricsTree_Prices_Spot: """Metrics tree node.""" def __init__(self, client: BrkClientBase, base_path: str = ''): - self.cents: MetricPattern1[Cents] = MetricPattern1(client, 'price_cents') self.usd: MetricPattern1[Dollars] = MetricPattern1(client, 'price') + self.cents: MetricPattern1[Cents] = MetricPattern1(client, 'price_cents') self.sats: MetricPattern1[Sats] = MetricPattern1(client, 'price_sats') class MetricsTree_Prices: @@ -4802,7 +4801,7 @@ class MetricsTree_Cohorts_Utxo_All_Supply: def __init__(self, client: BrkClientBase, base_path: str = ''): self.total: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'supply') self.half: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'supply_half') - self.delta: ChangeRatePattern = ChangeRatePattern(client, 'supply_delta') + self.delta: AbsoluteRatePattern = AbsoluteRatePattern(client, 'supply_delta') self.in_profit: BtcCentsRelSatsUsdPattern2 = BtcCentsRelSatsUsdPattern2(client, 'supply_in_profit') self.in_loss: BtcCentsRelSatsUsdPattern2 = BtcCentsRelSatsUsdPattern2(client, 'supply_in_loss') @@ -4831,8 +4830,8 @@ class MetricsTree_Cohorts_Utxo_All_Unrealized_NetPnl: """Metrics tree node.""" def __init__(self, client: BrkClientBase, base_path: str = ''): - self.cents: MetricPattern1[CentsSigned] = MetricPattern1(client, 'net_unrealized_pnl_cents') self.usd: MetricPattern1[Dollars] = MetricPattern1(client, 'net_unrealized_pnl') + self.cents: MetricPattern1[CentsSigned] = MetricPattern1(client, 'net_unrealized_pnl_cents') self.rel_to_own_gross: BpsPercentRatioPattern = BpsPercentRatioPattern(client, 'net_unrealized_pnl_rel_to_own_gross_pnl') class MetricsTree_Cohorts_Utxo_All_Unrealized: diff --git a/website/scripts/options/cointime.js b/website/scripts/options/cointime.js index 88a801a6a..815018d61 100644 --- a/website/scripts/options/cointime.js +++ b/website/scripts/options/cointime.js @@ -550,7 +550,7 @@ export function createCointimeSection() { unit: Unit.ratio, }), line({ - metric: adjusted.txVelocityBtc, + metric: adjusted.txVelocityNative, name: "Cointime-Adjusted", color: colors.adjusted, unit: Unit.ratio, @@ -568,7 +568,7 @@ export function createCointimeSection() { unit: Unit.ratio, }), line({ - metric: adjusted.txVelocityUsd, + metric: adjusted.txVelocityFiat, name: "Cointime-Adjusted", color: colors.vaulted, unit: Unit.ratio, diff --git a/website/scripts/options/distribution/activity.js b/website/scripts/options/distribution/activity.js index 45314e3b0..cda84667e 100644 --- a/website/scripts/options/distribution/activity.js +++ b/website/scripts/options/distribution/activity.js @@ -9,7 +9,7 @@ */ import { Unit } from "../../utils/units.js"; -import { line, baseline, dotsBaseline, dots } from "../series.js"; +import { line, baseline, dotsBaseline, percentRatio, percentRatioDots } from "../series.js"; import { mapCohortsWithAll, flatMapCohortsWithAll, @@ -35,31 +35,18 @@ function volumeAndCoinsTree(activity, color, title) { name: "Sum", title: title("Sent Volume"), bottom: [ - line({ - metric: activity.sent.sum._24h, - name: "24h", - color: colors.indicator.main, - unit: Unit.sats, - defaultActive: false, - }), - line({ - metric: activity.sent.base, - name: "Sum", - color, - unit: Unit.sats, - }), + line({ metric: activity.sent.base, name: "Sum", color, unit: Unit.sats }), + line({ metric: activity.sent.sum._24h, name: "24h", color: colors.time._24h, unit: Unit.sats, defaultActive: false }), + line({ metric: activity.sent.sum._1w, name: "1w", color: colors.time._1w, unit: Unit.sats, defaultActive: false }), + line({ metric: activity.sent.sum._1m, name: "1m", color: colors.time._1m, unit: Unit.sats, defaultActive: false }), + line({ metric: activity.sent.sum._1y, name: "1y", color: colors.time._1y, unit: Unit.sats, defaultActive: false }), ], }, { name: "Cumulative", title: title("Sent Volume (Total)"), bottom: [ - line({ - metric: activity.sent.cumulative, - name: "All-time", - color, - unit: Unit.sats, - }), + line({ metric: activity.sent.cumulative, name: "All-time", color, unit: Unit.sats }), ], }, ], @@ -68,27 +55,21 @@ function volumeAndCoinsTree(activity, color, title) { name: "Coins Destroyed", tree: [ { - name: "Sum", + name: "Base", title: title("Coindays Destroyed"), bottom: [ - line({ - metric: activity.coindaysDestroyed.sum._24h, - name: "24h", - color, - unit: Unit.coindays, - }), + line({ metric: activity.coindaysDestroyed.base, name: "Base", color, unit: Unit.coindays }), + line({ metric: activity.coindaysDestroyed.sum._24h, name: "24h", color: colors.time._24h, unit: Unit.coindays, defaultActive: false }), + line({ metric: activity.coindaysDestroyed.sum._1w, name: "1w", color: colors.time._1w, unit: Unit.coindays, defaultActive: false }), + line({ metric: activity.coindaysDestroyed.sum._1m, name: "1m", color: colors.time._1m, unit: Unit.coindays, defaultActive: false }), + line({ metric: activity.coindaysDestroyed.sum._1y, name: "1y", color: colors.time._1y, unit: Unit.coindays, defaultActive: false }), ], }, { name: "Cumulative", title: title("Cumulative Coindays Destroyed"), bottom: [ - line({ - metric: activity.coindaysDestroyed.cumulative, - name: "All-time", - color, - unit: Unit.coindays, - }), + line({ metric: activity.coindaysDestroyed.cumulative, name: "All-time", color, unit: Unit.coindays }), ], }, ], @@ -156,31 +137,31 @@ function singleSellSideRiskTree(sellSideRisk, title) { name: "Compare", title: title("Rolling Sell Side Risk"), bottom: [ - line({ metric: sellSideRisk._24h.ratio, name: "24h", color: colors.time._24h, unit: Unit.ratio }), - line({ metric: sellSideRisk._1w.ratio, name: "7d", color: colors.time._1w, unit: Unit.ratio }), - line({ metric: sellSideRisk._1m.ratio, name: "30d", color: colors.time._1m, unit: Unit.ratio }), - line({ metric: sellSideRisk._1y.ratio, name: "1y", color: colors.time._1y, unit: Unit.ratio }), + ...percentRatioDots({ pattern: sellSideRisk._24h, name: "24h", color: colors.time._24h }), + ...percentRatio({ pattern: sellSideRisk._1w, name: "7d", color: colors.time._1w }), + ...percentRatio({ pattern: sellSideRisk._1m, name: "30d", color: colors.time._1m }), + ...percentRatio({ pattern: sellSideRisk._1y, name: "1y", color: colors.time._1y }), ], }, { name: "24h", title: title("Sell Side Risk (24h)"), - bottom: [dots({ metric: sellSideRisk._24h.ratio, name: "Raw", color: colors.bitcoin, unit: Unit.ratio })], + bottom: percentRatioDots({ pattern: sellSideRisk._24h, name: "Raw", color: colors.bitcoin }), }, { name: "7d", title: title("Sell Side Risk (7d)"), - bottom: [line({ metric: sellSideRisk._1w.ratio, name: "Risk", unit: Unit.ratio })], + bottom: percentRatio({ pattern: sellSideRisk._1w, name: "Risk" }), }, { name: "30d", title: title("Sell Side Risk (30d)"), - bottom: [line({ metric: sellSideRisk._1m.ratio, name: "Risk", unit: Unit.ratio })], + bottom: percentRatio({ pattern: sellSideRisk._1m, name: "Risk" }), }, { name: "1y", title: title("Sell Side Risk (1y)"), - bottom: [line({ metric: sellSideRisk._1y.ratio, name: "Risk", unit: Unit.ratio })], + bottom: percentRatio({ pattern: sellSideRisk._1y, name: "Risk" }), }, ]; } @@ -255,6 +236,14 @@ function singleRollingValueTree(valueCreated, valueDestroyed, title, prefix = "" line({ metric: valueDestroyed.sum._1y, name: "Destroyed", color: colors.loss, unit: Unit.usd }), ], }, + { + name: "Cumulative", + title: title(`${prefix}Value Created & Destroyed (Total)`), + bottom: [ + line({ metric: valueCreated.cumulative, name: "Created", color: colors.usd, unit: Unit.usd }), + line({ metric: valueDestroyed.cumulative, name: "Destroyed", color: colors.loss, unit: Unit.usd }), + ], + }, ]; } diff --git a/website/scripts/options/distribution/cost-basis.js b/website/scripts/options/distribution/cost-basis.js index a1ea12fbc..30b73d75f 100644 --- a/website/scripts/options/distribution/cost-basis.js +++ b/website/scripts/options/distribution/cost-basis.js @@ -14,8 +14,8 @@ import { colors } from "../../utils/colors.js"; import { entries } from "../../utils/array.js"; import { Unit } from "../../utils/units.js"; import { priceLines } from "../constants.js"; -import { line, price } from "../series.js"; -import { mapCohortsWithAll } from "../shared.js"; +import { price, percentRatio } from "../series.js"; +import { mapCohortsWithAll, flatMapCohortsWithAll } from "../shared.js"; const ACTIVE_PCTS = new Set(["pct75", "pct50", "pct25"]); @@ -129,11 +129,10 @@ function createSingleByCapitalSeries(cohort) { function createSingleSupplyDensitySeries(cohort) { const { tree } = cohort; return [ - line({ - metric: tree.costBasis.supplyDensity.percent, + ...percentRatio({ + pattern: tree.costBasis.supplyDensity, name: "Supply Density", color: colors.bitcoin, - unit: Unit.percentage, }), ...priceLines({ numbers: [100, 50, 0], unit: Unit.percentage }), ]; @@ -269,12 +268,11 @@ export function createGroupedCostBasisSectionWithPercentiles({ { name: "Supply Density", title: title("Cost Basis Supply Density"), - bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => - line({ - metric: tree.costBasis.supplyDensity.percent, + bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => + percentRatio({ + pattern: tree.costBasis.supplyDensity, name, color, - unit: Unit.percentage, }), ), }, diff --git a/website/scripts/options/distribution/holdings.js b/website/scripts/options/distribution/holdings.js index d89266f99..d4dd2c45f 100644 --- a/website/scripts/options/distribution/holdings.js +++ b/website/scripts/options/distribution/holdings.js @@ -10,7 +10,7 @@ */ import { Unit } from "../../utils/units.js"; -import { line, baseline } from "../series.js"; +import { ROLLING_WINDOWS, line, baseline, rollingWindowsTree, rollingPercentRatioTree, percentRatio } from "../series.js"; import { satsBtcUsd, mapCohorts, @@ -74,23 +74,15 @@ function fullSupplySeries(supply) { /** * % of Own Supply series (profit/loss relative to own supply) - * @param {{ inProfit: { relToOwn: { percent: AnyMetricPattern } }, inLoss: { relToOwn: { percent: AnyMetricPattern } } }} supply + * @param {{ inProfit: { relToOwn: { percent: AnyMetricPattern, ratio: AnyMetricPattern } }, inLoss: { relToOwn: { percent: AnyMetricPattern, ratio: AnyMetricPattern } } }} supply * @returns {AnyFetchedSeriesBlueprint[]} */ function ownSupplyPctSeries(supply) { return [ - line({ - metric: supply.inProfit.relToOwn.percent, - name: "In Profit", - color: colors.profit, - unit: Unit.pctOwn, - }), - line({ - metric: supply.inLoss.relToOwn.percent, - name: "In Loss", - color: colors.loss, - unit: Unit.pctOwn, - }), + line({ metric: supply.inProfit.relToOwn.percent, name: "In Profit", color: colors.profit, unit: Unit.pctOwn }), + line({ metric: supply.inLoss.relToOwn.percent, name: "In Loss", color: colors.loss, unit: Unit.pctOwn }), + line({ metric: supply.inProfit.relToOwn.ratio, name: "In Profit", color: colors.profit, unit: Unit.ratio }), + line({ metric: supply.inLoss.relToOwn.ratio, name: "In Loss", color: colors.loss, unit: Unit.ratio }), ...priceLines({ numbers: [100, 50, 0], unit: Unit.pctOwn }), ]; } @@ -144,42 +136,58 @@ function groupedUtxoCountChart(list, all, title) { } /** - * @param {readonly (UtxoCohortObject | CohortWithoutRelative)[]} list - * @param {CohortAll} all + * @param {{ absolute: { _24h: AnyMetricPattern, _1w: AnyMetricPattern, _1m: AnyMetricPattern, _1y: AnyMetricPattern }, rate: { _24h: { percent: AnyMetricPattern, ratio: AnyMetricPattern }, _1w: { percent: AnyMetricPattern, ratio: AnyMetricPattern }, _1m: { percent: AnyMetricPattern, ratio: AnyMetricPattern }, _1y: { percent: AnyMetricPattern, ratio: AnyMetricPattern } } }} delta + * @param {Unit} unit * @param {(metric: string) => string} title + * @param {string} name + * @returns {PartialOptionsGroup} */ -function grouped30dSupplyChangeChart(list, all, title) { +function singleDeltaTree(delta, unit, title, name) { return { - name: "Supply", - title: title("Supply 30d Change"), - bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => - baseline({ - metric: tree.supply.delta.change._1m, - name, - color, - unit: Unit.sats, - }), - ), + name, + tree: [ + { ...rollingWindowsTree({ windows: delta.absolute, title: title(`${name} Change`), unit, series: baseline }), name: "Absolute" }, + { ...rollingPercentRatioTree({ windows: delta.rate, title: title(`${name} Rate`) }), name: "Rate" }, + ], }; } /** - * @param {readonly (UtxoCohortObject | CohortWithoutRelative)[]} list - * @param {CohortAll} all + * @template {{ name: string, color: Color }} T + * @template {{ name: string, color: Color }} A + * @param {readonly T[]} list + * @param {A} all + * @param {(c: T | A) => ChangeRatePattern | ChangeRatePattern2} getDelta + * @param {Unit} unit * @param {(metric: string) => string} title + * @param {string} name + * @returns {PartialOptionsGroup} */ -function grouped30dUtxoCountChangeChart(list, all, title) { +function groupedDeltaTree(list, all, getDelta, unit, title, name) { return { - name: "UTXO Count", - title: title("UTXO Count 30d Change"), - bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => - baseline({ - metric: tree.outputs.unspentCount.delta.change._1m, - name, - unit: Unit.count, - color, - }), - ), + name, + tree: [ + { + name: "Absolute", + tree: ROLLING_WINDOWS.map((w) => ({ + name: w.name, + title: title(`${name} Change (${w.name})`), + bottom: mapCohortsWithAll(list, all, (c) => + baseline({ metric: getDelta(c).absolute[w.key], name: c.name, color: c.color, unit }), + ), + })), + }, + { + name: "Rate", + tree: ROLLING_WINDOWS.map((w) => ({ + name: w.name, + title: title(`${name} Rate (${w.name})`), + bottom: flatMapCohortsWithAll(list, all, (c) => + percentRatio({ pattern: getDelta(c).rate[w.key], name: c.name, color: c.color }), + ), + })), + }, + ], }; } @@ -203,43 +211,6 @@ function singleUtxoCountChart(cohort, title) { }; } -/** - * @param {UtxoCohortObject | CohortWithoutRelative} cohort - * @param {(metric: string) => string} title - * @returns {PartialChartOption} - */ -function single30dSupplyChangeChart(cohort, title) { - return { - name: "Supply", - title: title("Supply 30d Change"), - bottom: [ - baseline({ - metric: cohort.tree.supply.delta.change._1m, - name: "30d Change", - unit: Unit.sats, - }), - ], - }; -} - -/** - * @param {UtxoCohortObject | CohortWithoutRelative} cohort - * @param {(metric: string) => string} title - * @returns {PartialChartOption} - */ -function single30dUtxoCountChangeChart(cohort, title) { - return { - name: "UTXO Count", - title: title("UTXO Count 30d Change"), - bottom: [ - baseline({ - metric: cohort.tree.outputs.unspentCount.delta.change._1m, - name: "30d Change", - unit: Unit.count, - }), - ], - }; -} /** * @param {CohortAll | CohortAddress | AddressCohortObject} cohort @@ -261,24 +232,6 @@ function singleAddressCountChart(cohort, title) { }; } -/** - * @param {CohortAll | CohortAddress | AddressCohortObject} cohort - * @param {(metric: string) => string} title - * @returns {PartialChartOption} - */ -function single30dAddressCountChangeChart(cohort, title) { - return { - name: "Address Count", - title: title("Address Count 30d Change"), - bottom: [ - baseline({ - metric: cohort.addressCount.delta.change._1m, - name: "30d Change", - unit: Unit.count, - }), - ], - }; -} // ============================================================================ // Single Cohort Holdings Sections @@ -301,10 +254,10 @@ export function createHoldingsSection({ cohort, title }) { }, singleUtxoCountChart(cohort, title), { - name: "30d Changes", + name: "Change", tree: [ - single30dSupplyChangeChart(cohort, title), - single30dUtxoCountChangeChart(cohort, title), + singleDeltaTree(cohort.tree.supply.delta, Unit.sats, title, "Supply"), + singleDeltaTree(cohort.tree.outputs.unspentCount.delta, Unit.count, title, "UTXO Count"), ], }, ], @@ -332,11 +285,11 @@ export function createHoldingsSectionAll({ cohort, title }) { singleUtxoCountChart(cohort, title), singleAddressCountChart(cohort, title), { - name: "30d Changes", + name: "Change", tree: [ - single30dSupplyChangeChart(cohort, title), - single30dUtxoCountChangeChart(cohort, title), - single30dAddressCountChangeChart(cohort, title), + singleDeltaTree(cohort.tree.supply.delta, Unit.sats, title, "Supply"), + singleDeltaTree(cohort.tree.outputs.unspentCount.delta, Unit.count, title, "UTXO Count"), + singleDeltaTree(cohort.addressCount.delta, Unit.count, title, "Address Count"), ], }, ], @@ -365,10 +318,10 @@ export function createHoldingsSectionWithRelative({ cohort, title }) { }, singleUtxoCountChart(cohort, title), { - name: "30d Changes", + name: "Change", tree: [ - single30dSupplyChangeChart(cohort, title), - single30dUtxoCountChangeChart(cohort, title), + singleDeltaTree(cohort.tree.supply.delta, Unit.sats, title, "Supply"), + singleDeltaTree(cohort.tree.outputs.unspentCount.delta, Unit.count, title, "UTXO Count"), ], }, ], @@ -396,10 +349,10 @@ export function createHoldingsSectionWithOwnSupply({ cohort, title }) { }, singleUtxoCountChart(cohort, title), { - name: "30d Changes", + name: "Change", tree: [ - single30dSupplyChangeChart(cohort, title), - single30dUtxoCountChangeChart(cohort, title), + singleDeltaTree(cohort.tree.supply.delta, Unit.sats, title, "Supply"), + singleDeltaTree(cohort.tree.outputs.unspentCount.delta, Unit.count, title, "UTXO Count"), ], }, ], @@ -423,11 +376,11 @@ export function createHoldingsSectionAddress({ cohort, title }) { singleUtxoCountChart(cohort, title), singleAddressCountChart(cohort, title), { - name: "30d Changes", + name: "Change", tree: [ - single30dSupplyChangeChart(cohort, title), - single30dUtxoCountChangeChart(cohort, title), - single30dAddressCountChangeChart(cohort, title), + singleDeltaTree(cohort.tree.supply.delta, Unit.sats, title, "Supply"), + singleDeltaTree(cohort.tree.outputs.unspentCount.delta, Unit.count, title, "UTXO Count"), + singleDeltaTree(cohort.addressCount.delta, Unit.count, title, "Address Count"), ], }, ], @@ -451,11 +404,11 @@ export function createHoldingsSectionAddressAmount({ cohort, title }) { singleUtxoCountChart(cohort, title), singleAddressCountChart(cohort, title), { - name: "30d Changes", + name: "Change", tree: [ - single30dSupplyChangeChart(cohort, title), - single30dUtxoCountChangeChart(cohort, title), - single30dAddressCountChangeChart(cohort, title), + singleDeltaTree(cohort.tree.supply.delta, Unit.sats, title, "Supply"), + singleDeltaTree(cohort.tree.outputs.unspentCount.delta, Unit.count, title, "UTXO Count"), + singleDeltaTree(cohort.addressCount.delta, Unit.count, title, "Address Count"), ], }, ], @@ -517,22 +470,11 @@ export function createGroupedHoldingsSectionAddress({ list, all, title }) { ), }, { - name: "30d Changes", + name: "Change", tree: [ - grouped30dSupplyChangeChart(list, all, title), - grouped30dUtxoCountChangeChart(list, all, title), - { - name: "Address Count", - title: title("Address Count 30d Change"), - bottom: mapCohortsWithAll(list, all, ({ name, color, addressCount }) => - baseline({ - metric: addressCount.delta.change._1m, - name, - unit: Unit.count, - color, - }), - ), - }, + groupedDeltaTree(list, all, (c) => c.tree.supply.delta, Unit.sats, title, "Supply"), + groupedDeltaTree(list, all, (c) => c.tree.outputs.unspentCount.delta, Unit.count, title, "UTXO Count"), + groupedDeltaTree(list, all, (c) => c.addressCount.delta, Unit.count, title, "Address Count"), ], }, ], @@ -573,22 +515,11 @@ export function createGroupedHoldingsSectionAddressAmount({ ), }, { - name: "30d Changes", + name: "Change", tree: [ - grouped30dSupplyChangeChart(list, all, title), - grouped30dUtxoCountChangeChart(list, all, title), - { - name: "Address Count", - title: title("Address Count 30d Change"), - bottom: mapCohortsWithAll(list, all, ({ name, color, addressCount }) => - baseline({ - metric: addressCount.delta.change._1m, - name, - unit: Unit.count, - color, - }), - ), - }, + groupedDeltaTree(list, all, (c) => c.tree.supply.delta, Unit.sats, title, "Supply"), + groupedDeltaTree(list, all, (c) => c.tree.outputs.unspentCount.delta, Unit.count, title, "UTXO Count"), + groupedDeltaTree(list, all, (c) => c.addressCount.delta, Unit.count, title, "Address Count"), ], }, ], @@ -618,10 +549,10 @@ export function createGroupedHoldingsSection({ list, all, title }) { }, groupedUtxoCountChart(list, all, title), { - name: "30d Changes", + name: "Change", tree: [ - grouped30dSupplyChangeChart(list, all, title), - grouped30dUtxoCountChangeChart(list, all, title), + groupedDeltaTree(list, all, (c) => c.tree.supply.delta, Unit.sats, title, "Supply"), + groupedDeltaTree(list, all, (c) => c.tree.outputs.unspentCount.delta, Unit.count, title, "UTXO Count"), ], }, ], @@ -708,10 +639,10 @@ export function createGroupedHoldingsSectionWithOwnSupply({ }, groupedUtxoCountChart(list, all, title), { - name: "30d Changes", + name: "Change", tree: [ - grouped30dSupplyChangeChart(list, all, title), - grouped30dUtxoCountChangeChart(list, all, title), + groupedDeltaTree(list, all, (c) => c.tree.supply.delta, Unit.sats, title, "Supply"), + groupedDeltaTree(list, all, (c) => c.tree.outputs.unspentCount.delta, Unit.count, title, "UTXO Count"), ], }, ], @@ -812,10 +743,10 @@ export function createGroupedHoldingsSectionWithRelative({ list, all, title }) { }, groupedUtxoCountChart(list, all, title), { - name: "30d Changes", + name: "Change", tree: [ - grouped30dSupplyChangeChart(list, all, title), - grouped30dUtxoCountChangeChart(list, all, title), + groupedDeltaTree(list, all, (c) => c.tree.supply.delta, Unit.sats, title, "Supply"), + groupedDeltaTree(list, all, (c) => c.tree.outputs.unspentCount.delta, Unit.count, title, "UTXO Count"), ], }, ], diff --git a/website/scripts/options/distribution/profitability.js b/website/scripts/options/distribution/profitability.js index a93332f86..536d7b9e8 100644 --- a/website/scripts/options/distribution/profitability.js +++ b/website/scripts/options/distribution/profitability.js @@ -11,12 +11,13 @@ */ import { Unit } from "../../utils/units.js"; -import { line, baseline, dots, dotsBaseline } from "../series.js"; +import { ROLLING_WINDOWS, line, baseline, dots, dotsBaseline, percentRatio, percentRatioBaseline } from "../series.js"; import { colors } from "../../utils/colors.js"; -import { priceLine, priceLines } from "../constants.js"; +import { priceLine } from "../constants.js"; import { - mapCohorts, mapCohortsWithAll, + flatMapCohorts, + flatMapCohortsWithAll, } from "../shared.js"; // ============================================================================ @@ -62,69 +63,82 @@ function netBaseline(metric, unit) { // ============================================================================ /** - * Unrealized P&L (USD + relToMcap) for All cohort + * @param {{ profit: { base: { usd: AnyMetricPattern } }, loss: { base: { usd: AnyMetricPattern }, negative: AnyMetricPattern }, grossPnl: { usd: AnyMetricPattern } }} u + * @returns {AnyFetchedSeriesBlueprint[]} + */ +function unrealizedUsdSeries(u) { + return [ + ...pnlLines( + { profit: u.profit.base.usd, loss: u.loss.base.usd, negLoss: u.loss.negative, gross: u.grossPnl.usd }, + Unit.usd, + ), + priceLine({ unit: Unit.usd, defaultActive: false }), + ]; +} + +/** + * @param {{ percent: AnyMetricPattern, ratio: AnyMetricPattern }} profit + * @param {{ percent: AnyMetricPattern, ratio: AnyMetricPattern }} loss + * @param {string} name + * @param {(metric: string) => string} title + * @returns {AnyPartialOption} + */ +function relPnlChart(profit, loss, name, title) { + return { + name, + title: title(`Unrealized P&L (${name})`), + bottom: [ + ...percentRatio({ pattern: profit, name: "Profit", color: colors.profit }), + ...percentRatio({ pattern: loss, name: "Loss", color: colors.loss }), + ], + }; +} + +/** + * Unrealized P&L tree for All cohort * @param {Brk.MetricsTree_Cohorts_Utxo_All_Unrealized} u - * @returns {AnyFetchedSeriesBlueprint[]} + * @param {(metric: string) => string} title + * @returns {PartialOptionsTree} */ -function unrealizedAll(u) { +function unrealizedPnlTreeAll(u, title) { return [ - ...pnlLines( - { profit: u.profit.base.usd, loss: u.loss.base.usd, negLoss: u.loss.negative, gross: u.grossPnl.usd }, - Unit.usd, - ), - priceLine({ unit: Unit.usd, defaultActive: false }), - line({ metric: u.profit.relToMcap.ratio, name: "Profit", color: colors.profit, unit: Unit.pctMcap }), - line({ metric: u.loss.relToMcap.ratio, name: "Loss", color: colors.loss, unit: Unit.pctMcap }), - priceLine({ unit: Unit.pctMcap, defaultActive: false }), - line({ metric: u.profit.relToOwnGross.ratio, name: "Profit", color: colors.profit, unit: Unit.pctOwnPnl }), - line({ metric: u.loss.relToOwnGross.ratio, name: "Loss", color: colors.loss, unit: Unit.pctOwnPnl }), - ...priceLines({ numbers: [100, 50, 0], unit: Unit.pctOwnPnl }), + { name: "USD", title: title("Unrealized P&L"), bottom: unrealizedUsdSeries(u) }, + relPnlChart(u.profit.relToMcap, u.loss.relToMcap, "% of Mcap", title), + relPnlChart(u.profit.relToOwnGross, u.loss.relToOwnGross, "% of Own P&L", title), ]; } /** - * Unrealized P&L (USD + relToMcap + relToOwnMcap + relToOwnGross) for Full cohorts (STH/LTH) + * Unrealized P&L tree for Full cohorts (STH) * @param {Brk.GrossInvestedLossNetNuplProfitSentimentPattern2} u - * @returns {AnyFetchedSeriesBlueprint[]} + * @param {(metric: string) => string} title + * @returns {PartialOptionsTree} */ -function unrealizedFull(u) { +function unrealizedPnlTreeFull(u, title) { return [ - ...pnlLines( - { profit: u.profit.base.usd, loss: u.loss.base.usd, negLoss: u.loss.negative, gross: u.grossPnl.usd }, - Unit.usd, - ), - priceLine({ unit: Unit.usd, defaultActive: false }), - line({ metric: u.profit.relToMcap.ratio, name: "Profit", color: colors.profit, unit: Unit.pctMcap }), - line({ metric: u.loss.relToMcap.ratio, name: "Loss", color: colors.loss, unit: Unit.pctMcap }), - priceLine({ unit: Unit.pctMcap, defaultActive: false }), - line({ metric: u.profit.relToOwnMcap.ratio, name: "Profit", color: colors.profit, unit: Unit.pctOwnMcap }), - line({ metric: u.loss.relToOwnMcap.ratio, name: "Loss", color: colors.loss, unit: Unit.pctOwnMcap }), - priceLine({ unit: Unit.pctOwnMcap, defaultActive: false }), - line({ metric: u.profit.relToOwnGross.ratio, name: "Profit", color: colors.profit, unit: Unit.pctOwnPnl }), - line({ metric: u.loss.relToOwnGross.ratio, name: "Loss", color: colors.loss, unit: Unit.pctOwnPnl }), - ...priceLines({ numbers: [100, 50, 0], unit: Unit.pctOwnPnl }), + { name: "USD", title: title("Unrealized P&L"), bottom: unrealizedUsdSeries(u) }, + relPnlChart(u.profit.relToMcap, u.loss.relToMcap, "% of Mcap", title), + relPnlChart(u.profit.relToOwnMcap, u.loss.relToOwnMcap, "% of Own Mcap", title), + relPnlChart(u.profit.relToOwnGross, u.loss.relToOwnGross, "% of Own P&L", title), ]; } /** - * Unrealized P&L for LTH (loss relToMcap only + relToOwnMcap + relToOwnGross) + * Unrealized P&L tree for LTH (loss relToMcap only) * @param {Brk.GrossInvestedLossNetNuplProfitSentimentPattern2} u - * @returns {AnyFetchedSeriesBlueprint[]} + * @param {(metric: string) => string} title + * @returns {PartialOptionsTree} */ -function unrealizedLongTerm(u) { +function unrealizedPnlTreeLongTerm(u, title) { return [ - ...pnlLines( - { profit: u.profit.base.usd, loss: u.loss.base.usd, negLoss: u.loss.negative, gross: u.grossPnl.usd }, - Unit.usd, - ), - priceLine({ unit: Unit.usd, defaultActive: false }), - line({ metric: u.loss.relToMcap.ratio, name: "Loss", color: colors.loss, unit: Unit.pctMcap }), - line({ metric: u.profit.relToOwnMcap.ratio, name: "Profit", color: colors.profit, unit: Unit.pctOwnMcap }), - line({ metric: u.loss.relToOwnMcap.ratio, name: "Loss", color: colors.loss, unit: Unit.pctOwnMcap }), - priceLine({ unit: Unit.pctOwnMcap, defaultActive: false }), - line({ metric: u.profit.relToOwnGross.ratio, name: "Profit", color: colors.profit, unit: Unit.pctOwnPnl }), - line({ metric: u.loss.relToOwnGross.ratio, name: "Loss", color: colors.loss, unit: Unit.pctOwnPnl }), - ...priceLines({ numbers: [100, 50, 0], unit: Unit.pctOwnPnl }), + { name: "USD", title: title("Unrealized P&L"), bottom: unrealizedUsdSeries(u) }, + { + name: "% of Mcap", + title: title("Unrealized Loss (% of Mcap)"), + bottom: percentRatio({ pattern: u.loss.relToMcap, name: "Loss", color: colors.loss }), + }, + relPnlChart(u.profit.relToOwnMcap, u.loss.relToOwnMcap, "% of Own Mcap", title), + relPnlChart(u.profit.relToOwnGross, u.loss.relToOwnGross, "% of Own P&L", title), ]; } @@ -148,27 +162,39 @@ function unrealizedMid(u) { // ============================================================================ /** - * Net P&L for All cohort * @param {Brk.MetricsTree_Cohorts_Utxo_All_Unrealized} u - * @returns {AnyFetchedSeriesBlueprint[]} + * @param {(metric: string) => string} title + * @returns {PartialOptionsTree} */ -function netUnrealizedAll(u) { +function netUnrealizedTreeAll(u, title) { return [ - netBaseline(u.netPnl.usd, Unit.usd), - netBaseline(u.netPnl.relToOwnGross.ratio, Unit.pctOwnPnl), + { name: "USD", title: title("Net Unrealized P&L"), bottom: [netBaseline(u.netPnl.usd, Unit.usd)] }, + { + name: "% of Own P&L", + title: title("Net Unrealized P&L (% of Own P&L)"), + bottom: percentRatioBaseline({ pattern: u.netPnl.relToOwnGross, name: "Net P&L" }), + }, ]; } /** - * Net P&L for Full cohorts (STH/LTH) * @param {Brk.GrossInvestedLossNetNuplProfitSentimentPattern2} u - * @returns {AnyFetchedSeriesBlueprint[]} + * @param {(metric: string) => string} title + * @returns {PartialOptionsTree} */ -function netUnrealizedFull(u) { +function netUnrealizedTreeFull(u, title) { return [ - netBaseline(u.netPnl.usd, Unit.usd), - netBaseline(u.netPnl.relToOwnMcap.ratio, Unit.pctOwnMcap), - netBaseline(u.netPnl.relToOwnGross.ratio, Unit.pctOwnPnl), + { name: "USD", title: title("Net Unrealized P&L"), bottom: [netBaseline(u.netPnl.usd, Unit.usd)] }, + { + name: "% of Own Mcap", + title: title("Net Unrealized P&L (% of Own Mcap)"), + bottom: percentRatioBaseline({ pattern: u.netPnl.relToOwnMcap, name: "Net P&L" }), + }, + { + name: "% of Own P&L", + title: title("Net Unrealized P&L (% of Own P&L)"), + bottom: percentRatioBaseline({ pattern: u.netPnl.relToOwnGross, name: "Net P&L" }), + }, ]; } @@ -225,52 +251,92 @@ function nuplSeries(nupl) { /** * @param {Brk.CapGrossInvestorLossMvrvNetPeakPriceProfitSellSoprPattern | Brk.MetricsTree_Cohorts_Utxo_Lth_Realized} r - * @returns {AnyFetchedSeriesBlueprint[]} + * @param {(metric: string) => string} title + * @returns {PartialOptionsTree} */ -function realizedPnlSumFull(r) { +function realizedPnlSumTreeFull(r, title) { return [ - dots({ metric: r.profit.base.usd, name: "Profit", color: colors.profit, unit: Unit.usd }), - dots({ metric: r.loss.negative, name: "Negative Loss", color: colors.loss, unit: Unit.usd, defaultActive: false }), - dots({ metric: r.loss.base.usd, name: "Loss", color: colors.loss, unit: Unit.usd, defaultActive: false }), - baseline({ metric: r.profit.relToRcap.ratio, name: "Profit", color: colors.profit, unit: Unit.pctRcap }), - baseline({ metric: r.loss.relToRcap.ratio, name: "Loss", color: colors.loss, unit: Unit.pctRcap }), + { + name: "USD", + title: title("Realized P&L"), + bottom: [ + dots({ metric: r.profit.base.usd, name: "Profit", color: colors.profit, unit: Unit.usd }), + dots({ metric: r.loss.negative, name: "Negative Loss", color: colors.loss, unit: Unit.usd, defaultActive: false }), + dots({ metric: r.loss.base.usd, name: "Loss", color: colors.loss, unit: Unit.usd, defaultActive: false }), + ], + }, + { + name: "% of Rcap", + title: title("Realized P&L (% of Realized Cap)"), + bottom: [ + ...percentRatioBaseline({ pattern: r.profit.relToRcap, name: "Profit", color: colors.profit }), + ...percentRatioBaseline({ pattern: r.loss.relToRcap, name: "Loss", color: colors.loss }), + ], + }, ]; } /** * @param {Brk.CapGrossInvestorLossMvrvNetPeakPriceProfitSellSoprPattern | Brk.MetricsTree_Cohorts_Utxo_Lth_Realized} r - * @returns {AnyFetchedSeriesBlueprint[]} + * @param {(metric: string) => string} title + * @returns {PartialOptionsTree} */ -function realizedNetPnlSumFull(r) { +function realizedNetPnlSumTreeFull(r, title) { return [ - dotsBaseline({ metric: r.netPnl.base.usd, name: "Net", unit: Unit.usd, defaultActive: false }), - baseline({ metric: r.netPnl.relToRcap.ratio, name: "Net", unit: Unit.pctRcap }), + { name: "USD", title: title("Net Realized P&L"), bottom: [dotsBaseline({ metric: r.netPnl.base.usd, name: "Net", unit: Unit.usd })] }, + { + name: "% of Rcap", + title: title("Net Realized P&L (% of Realized Cap)"), + bottom: percentRatioBaseline({ pattern: r.netPnl.relToRcap, name: "Net" }), + }, ]; } /** * @param {Brk.CapGrossInvestorLossMvrvNetPeakPriceProfitSellSoprPattern | Brk.MetricsTree_Cohorts_Utxo_Lth_Realized} r - * @returns {AnyFetchedSeriesBlueprint[]} + * @param {(metric: string) => string} title + * @returns {PartialOptionsTree} */ -function realizedPnlCumulativeFull(r) { +function realizedPnlCumulativeTreeFull(r, title) { return [ - line({ metric: r.profit.cumulative.usd, name: "Profit", color: colors.profit, unit: Unit.usd }), - line({ metric: r.loss.cumulative.usd, name: "Loss", color: colors.loss, unit: Unit.usd }), - line({ metric: r.loss.negative, name: "Negative Loss", color: colors.loss, unit: Unit.usd, defaultActive: false }), - baseline({ metric: r.profit.relToRcap.ratio, name: "Profit", color: colors.profit, unit: Unit.pctRcap }), - baseline({ metric: r.loss.relToRcap.ratio, name: "Loss", color: colors.loss, unit: Unit.pctRcap }), + { + name: "USD", + title: title("Cumulative Realized P&L"), + bottom: [ + line({ metric: r.profit.cumulative.usd, name: "Profit", color: colors.profit, unit: Unit.usd }), + line({ metric: r.loss.cumulative.usd, name: "Loss", color: colors.loss, unit: Unit.usd }), + line({ metric: r.loss.negative, name: "Negative Loss", color: colors.loss, unit: Unit.usd, defaultActive: false }), + ], + }, + { + name: "% of Rcap", + title: title("Cumulative Realized P&L (% of Realized Cap)"), + bottom: [ + ...percentRatioBaseline({ pattern: r.profit.relToRcap, name: "Profit", color: colors.profit }), + ...percentRatioBaseline({ pattern: r.loss.relToRcap, name: "Loss", color: colors.loss }), + ], + }, ]; } /** * @param {Brk.CapGrossInvestorLossMvrvNetPeakPriceProfitSellSoprPattern | Brk.MetricsTree_Cohorts_Utxo_Lth_Realized} r - * @returns {AnyFetchedSeriesBlueprint[]} + * @param {(metric: string) => string} title + * @returns {PartialOptionsTree} */ -function realized30dChangeFull(r) { +function realized30dChangeTreeFull(r, title) { return [ - baseline({ metric: r.netPnl.delta.change._1m.usd, name: "30d Change", unit: Unit.usd }), - baseline({ metric: r.netPnl.change1m.relToMcap.ratio, name: "30d Change", unit: Unit.pctMcap }), - baseline({ metric: r.netPnl.change1m.relToRcap.ratio, name: "30d Change", unit: Unit.pctRcap }), + { name: "USD", title: title("Realized P&L 30d Change"), bottom: [baseline({ metric: r.netPnl.delta.absolute._1m.usd, name: "30d Change", unit: Unit.usd })] }, + { + name: "% of Mcap", + title: title("Realized 30d Change (% of Mcap)"), + bottom: percentRatioBaseline({ pattern: r.netPnl.change1m.relToMcap, name: "30d Change" }), + }, + { + name: "% of Rcap", + title: title("Realized 30d Change (% of Realized Cap)"), + bottom: percentRatioBaseline({ pattern: r.netPnl.change1m.relToRcap, name: "30d Change" }), + }, ]; } @@ -288,17 +354,15 @@ function singleRollingRealizedTreeFull(r, title) { { name: "Compare", title: title("Rolling Realized Profit"), - bottom: [ - line({ metric: r.profit.sum._24h.usd, name: "24h", color: colors.time._24h, unit: Unit.usd }), - line({ metric: r.profit.sum._1w.usd, name: "7d", color: colors.time._1w, unit: Unit.usd }), - line({ metric: r.profit.sum._1m.usd, name: "30d", color: colors.time._1m, unit: Unit.usd }), - line({ metric: r.profit.sum._1y.usd, name: "1y", color: colors.time._1y, unit: Unit.usd }), - ], + bottom: ROLLING_WINDOWS.map((w) => + line({ metric: r.profit.sum[w.key].usd, name: w.name, color: w.color, unit: Unit.usd }), + ), }, - { name: "24h", title: title("Realized Profit (24h)"), bottom: [line({ metric: r.profit.sum._24h.usd, name: "Profit", color: colors.profit, unit: Unit.usd })] }, - { name: "7d", title: title("Realized Profit (7d)"), bottom: [line({ metric: r.profit.sum._1w.usd, name: "Profit", color: colors.profit, unit: Unit.usd })] }, - { name: "30d", title: title("Realized Profit (30d)"), bottom: [line({ metric: r.profit.sum._1m.usd, name: "Profit", color: colors.profit, unit: Unit.usd })] }, - { name: "1y", title: title("Realized Profit (1y)"), bottom: [line({ metric: r.profit.sum._1y.usd, name: "Profit", color: colors.profit, unit: Unit.usd })] }, + ...ROLLING_WINDOWS.map((w) => ({ + name: w.name, + title: title(`Realized Profit (${w.name})`), + bottom: [line({ metric: r.profit.sum[w.key].usd, name: "Profit", color: colors.profit, unit: Unit.usd })], + })), ], }, { @@ -307,17 +371,32 @@ function singleRollingRealizedTreeFull(r, title) { { name: "Compare", title: title("Rolling Realized Loss"), - bottom: [ - line({ metric: r.loss.sum._24h.usd, name: "24h", color: colors.time._24h, unit: Unit.usd }), - line({ metric: r.loss.sum._1w.usd, name: "7d", color: colors.time._1w, unit: Unit.usd }), - line({ metric: r.loss.sum._1m.usd, name: "30d", color: colors.time._1m, unit: Unit.usd }), - line({ metric: r.loss.sum._1y.usd, name: "1y", color: colors.time._1y, unit: Unit.usd }), - ], + bottom: ROLLING_WINDOWS.map((w) => + line({ metric: r.loss.sum[w.key].usd, name: w.name, color: w.color, unit: Unit.usd }), + ), }, - { name: "24h", title: title("Realized Loss (24h)"), bottom: [line({ metric: r.loss.sum._24h.usd, name: "Loss", color: colors.loss, unit: Unit.usd })] }, - { name: "7d", title: title("Realized Loss (7d)"), bottom: [line({ metric: r.loss.sum._1w.usd, name: "Loss", color: colors.loss, unit: Unit.usd })] }, - { name: "30d", title: title("Realized Loss (30d)"), bottom: [line({ metric: r.loss.sum._1m.usd, name: "Loss", color: colors.loss, unit: Unit.usd })] }, - { name: "1y", title: title("Realized Loss (1y)"), bottom: [line({ metric: r.loss.sum._1y.usd, name: "Loss", color: colors.loss, unit: Unit.usd })] }, + ...ROLLING_WINDOWS.map((w) => ({ + name: w.name, + title: title(`Realized Loss (${w.name})`), + bottom: [line({ metric: r.loss.sum[w.key].usd, name: "Loss", color: colors.loss, unit: Unit.usd })], + })), + ], + }, + { + name: "Net", + tree: [ + { + name: "Compare", + title: title("Rolling Net Realized P&L"), + bottom: ROLLING_WINDOWS.map((w) => + baseline({ metric: r.netPnl.sum[w.key].usd, name: w.name, color: w.color, unit: Unit.usd }), + ), + }, + ...ROLLING_WINDOWS.map((w) => ({ + name: w.name, + title: title(`Net Realized P&L (${w.name})`), + bottom: [baseline({ metric: r.netPnl.sum[w.key].usd, name: "Net", unit: Unit.usd })], + })), ], }, { @@ -326,17 +405,15 @@ function singleRollingRealizedTreeFull(r, title) { { name: "Compare", title: title("Rolling Realized P/L Ratio"), - bottom: [ - baseline({ metric: r.profitToLossRatio._24h, name: "24h", color: colors.time._24h, unit: Unit.ratio, base: 1 }), - baseline({ metric: r.profitToLossRatio._1w, name: "7d", color: colors.time._1w, unit: Unit.ratio, base: 1 }), - baseline({ metric: r.profitToLossRatio._1m, name: "30d", color: colors.time._1m, unit: Unit.ratio, base: 1 }), - baseline({ metric: r.profitToLossRatio._1y, name: "1y", color: colors.time._1y, unit: Unit.ratio, base: 1 }), - ], + bottom: ROLLING_WINDOWS.map((w) => + baseline({ metric: r.profitToLossRatio[w.key], name: w.name, color: w.color, unit: Unit.ratio, base: 1 }), + ), }, - { name: "24h", title: title("Realized P/L Ratio (24h)"), bottom: [baseline({ metric: r.profitToLossRatio._24h, name: "P/L Ratio", unit: Unit.ratio, base: 1 })] }, - { name: "7d", title: title("Realized P/L Ratio (7d)"), bottom: [baseline({ metric: r.profitToLossRatio._1w, name: "P/L Ratio", unit: Unit.ratio, base: 1 })] }, - { name: "30d", title: title("Realized P/L Ratio (30d)"), bottom: [baseline({ metric: r.profitToLossRatio._1m, name: "P/L Ratio", unit: Unit.ratio, base: 1 })] }, - { name: "1y", title: title("Realized P/L Ratio (1y)"), bottom: [baseline({ metric: r.profitToLossRatio._1y, name: "P/L Ratio", unit: Unit.ratio, base: 1 })] }, + ...ROLLING_WINDOWS.map((w) => ({ + name: w.name, + title: title(`Realized P/L Ratio (${w.name})`), + bottom: [baseline({ metric: r.profitToLossRatio[w.key], name: "P/L Ratio", unit: Unit.ratio, base: 1 })], + })), ], }, ]; @@ -353,21 +430,19 @@ function singleRollingRealizedTreeBasic(profit, loss, title) { return [ { name: "Profit", - tree: [ - { name: "24h", title: title("Realized Profit (24h)"), bottom: [line({ metric: profit.sum._24h.usd, name: "Profit", color: colors.profit, unit: Unit.usd })] }, - { name: "7d", title: title("Realized Profit (7d)"), bottom: [line({ metric: profit.sum._1w.usd, name: "Profit", color: colors.profit, unit: Unit.usd })] }, - { name: "30d", title: title("Realized Profit (30d)"), bottom: [line({ metric: profit.sum._1m.usd, name: "Profit", color: colors.profit, unit: Unit.usd })] }, - { name: "1y", title: title("Realized Profit (1y)"), bottom: [line({ metric: profit.sum._1y.usd, name: "Profit", color: colors.profit, unit: Unit.usd })] }, - ], + tree: ROLLING_WINDOWS.map((w) => ({ + name: w.name, + title: title(`Realized Profit (${w.name})`), + bottom: [line({ metric: profit.sum[w.key].usd, name: "Profit", color: colors.profit, unit: Unit.usd })], + })), }, { name: "Loss", - tree: [ - { name: "24h", title: title("Realized Loss (24h)"), bottom: [line({ metric: loss.sum._24h.usd, name: "Loss", color: colors.loss, unit: Unit.usd })] }, - { name: "7d", title: title("Realized Loss (7d)"), bottom: [line({ metric: loss.sum._1w.usd, name: "Loss", color: colors.loss, unit: Unit.usd })] }, - { name: "30d", title: title("Realized Loss (30d)"), bottom: [line({ metric: loss.sum._1m.usd, name: "Loss", color: colors.loss, unit: Unit.usd })] }, - { name: "1y", title: title("Realized Loss (1y)"), bottom: [line({ metric: loss.sum._1y.usd, name: "Loss", color: colors.loss, unit: Unit.usd })] }, - ], + tree: ROLLING_WINDOWS.map((w) => ({ + name: w.name, + title: title(`Realized Loss (${w.name})`), + bottom: [line({ metric: loss.sum[w.key].usd, name: "Loss", color: colors.loss, unit: Unit.usd })], + })), }, ]; } @@ -386,13 +461,32 @@ function realizedSubfolderFull(r, title) { return { name: "Realized", tree: [ - { name: "P&L", title: title("Realized P&L"), bottom: realizedPnlSumFull(r) }, - { name: "Net", title: title("Net Realized P&L"), bottom: realizedNetPnlSumFull(r) }, - { name: "30d Change", title: title("Realized P&L 30d Change"), bottom: realized30dChangeFull(r) }, + { name: "P&L", tree: realizedPnlSumTreeFull(r, title) }, + { name: "Net", tree: realizedNetPnlSumTreeFull(r, title) }, + { name: "30d Change", tree: realized30dChangeTreeFull(r, title) }, { - name: "Total", - title: title("Total Realized P&L"), - bottom: [line({ metric: r.grossPnl.cumulative.usd, name: "Total", unit: Unit.usd, color: colors.bitcoin })], + name: "Gross P&L", + tree: [ + { name: "Base", title: title("Gross Realized P&L"), bottom: [dots({ metric: r.grossPnl.base.usd, name: "Gross P&L", color: colors.bitcoin, unit: Unit.usd })] }, + { + name: "Rolling", + tree: [ + { + name: "Compare", + title: title("Rolling Gross Realized P&L"), + bottom: ROLLING_WINDOWS.map((w) => + line({ metric: r.grossPnl.sum[w.key].usd, name: w.name, color: w.color, unit: Unit.usd }), + ), + }, + ...ROLLING_WINDOWS.map((w) => ({ + name: w.name, + title: title(`Gross Realized P&L (${w.name})`), + bottom: [line({ metric: r.grossPnl.sum[w.key].usd, name: "Gross P&L", color: colors.bitcoin, unit: Unit.usd })], + })), + ], + }, + { name: "Cumulative", title: title("Total Realized P&L"), bottom: [line({ metric: r.grossPnl.cumulative.usd, name: "Total", unit: Unit.usd, color: colors.bitcoin })] }, + ], }, { name: "P/L Ratio", @@ -402,29 +496,33 @@ function realizedSubfolderFull(r, title) { { name: "Peak Regret", title: title("Realized Peak Regret"), - bottom: [ - line({ metric: r.peakRegret.base, name: "Peak Regret", unit: Unit.usd }), - ], + bottom: [line({ metric: r.peakRegret.base, name: "Peak Regret", unit: Unit.usd })], }, { name: "Rolling", tree: singleRollingRealizedTreeFull(r, title) }, { name: "Cumulative", tree: [ - { name: "P&L", title: title("Cumulative Realized P&L"), bottom: realizedPnlCumulativeFull(r) }, + { name: "P&L", tree: realizedPnlCumulativeTreeFull(r, title) }, { name: "Net", - title: title("Cumulative Net Realized P&L"), - bottom: [ - baseline({ metric: r.netPnl.cumulative.usd, name: "Net", unit: Unit.usd }), - baseline({ metric: r.netPnl.relToRcap.ratio, name: "Net", unit: Unit.pctRcap }), + tree: [ + { name: "USD", title: title("Cumulative Net Realized P&L"), bottom: [baseline({ metric: r.netPnl.cumulative.usd, name: "Net", unit: Unit.usd })] }, + { + name: "% of Rcap", + title: title("Cumulative Net P&L (% of Realized Cap)"), + bottom: percentRatioBaseline({ pattern: r.netPnl.relToRcap, name: "Net" }), + }, ], }, { name: "Peak Regret", - title: title("Cumulative Realized Peak Regret"), - bottom: [ - line({ metric: r.peakRegret.cumulative, name: "Peak Regret", unit: Unit.usd }), - line({ metric: r.peakRegret.relToRcap.ratio, name: "Peak Regret", unit: Unit.pctRcap }), + tree: [ + { name: "USD", title: title("Cumulative Peak Regret"), bottom: [line({ metric: r.peakRegret.cumulative, name: "Peak Regret", unit: Unit.usd })] }, + { + name: "% of Rcap", + title: title("Cumulative Peak Regret (% of Realized Cap)"), + bottom: percentRatioBaseline({ pattern: r.peakRegret.relToRcap, name: "Peak Regret" }), + }, ], }, ], @@ -460,7 +558,7 @@ function realizedSubfolderMid(r, title) { { name: "30d Change", title: title("Realized P&L 30d Change"), - bottom: [baseline({ metric: r.netPnl.delta.change._1m.usd, name: "30d Change", unit: Unit.usd })], + bottom: [baseline({ metric: r.netPnl.delta.absolute._1m.usd, name: "30d Change", unit: Unit.usd })], }, { name: "Rolling", tree: singleRollingRealizedTreeBasic(r.profit, r.loss, title) }, { @@ -555,8 +653,8 @@ export function createProfitabilitySectionAll({ cohort, title }) { { name: "Unrealized", tree: [ - { name: "P&L", title: title("Unrealized P&L"), bottom: unrealizedAll(u) }, - { name: "Net P&L", title: title("Net Unrealized P&L"), bottom: netUnrealizedAll(u) }, + { name: "P&L", tree: unrealizedPnlTreeAll(u, title) }, + { name: "Net P&L", tree: netUnrealizedTreeAll(u, title) }, { name: "NUPL", title: title("NUPL"), bottom: nuplSeries(u.nupl) }, ], }, @@ -585,8 +683,8 @@ export function createProfitabilitySectionFull({ cohort, title }) { { name: "Unrealized", tree: [ - { name: "P&L", title: title("Unrealized P&L"), bottom: unrealizedFull(u) }, - { name: "Net P&L", title: title("Net Unrealized P&L"), bottom: netUnrealizedFull(u) }, + { name: "P&L", tree: unrealizedPnlTreeFull(u, title) }, + { name: "Net P&L", tree: netUnrealizedTreeFull(u, title) }, { name: "NUPL", title: title("NUPL"), bottom: nuplSeries(u.nupl) }, ], }, @@ -636,8 +734,8 @@ export function createProfitabilitySectionLongTerm({ cohort, title }) { { name: "Unrealized", tree: [ - { name: "P&L", title: title("Unrealized P&L"), bottom: unrealizedLongTerm(u) }, - { name: "Net P&L", title: title("Net Unrealized P&L"), bottom: netUnrealizedFull(u) }, + { name: "P&L", tree: unrealizedPnlTreeLongTerm(u, title) }, + { name: "Net P&L", tree: netUnrealizedTreeFull(u, title) }, { name: "NUPL", title: title("NUPL"), bottom: nuplSeries(u.nupl) }, ], }, @@ -769,21 +867,19 @@ function groupedRollingRealizedCharts(list, all, title) { return [ { name: "Profit", - tree: [ - { name: "24h", title: title("Realized Profit (24h)"), bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.realized.profit.sum._24h.usd, name, color, unit: Unit.usd })) }, - { name: "7d", title: title("Realized Profit (7d)"), bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.realized.profit.sum._1w.usd, name, color, unit: Unit.usd })) }, - { name: "30d", title: title("Realized Profit (30d)"), bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.realized.profit.sum._1m.usd, name, color, unit: Unit.usd })) }, - { name: "1y", title: title("Realized Profit (1y)"), bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.realized.profit.sum._1y.usd, name, color, unit: Unit.usd })) }, - ], + tree: ROLLING_WINDOWS.map((w) => ({ + name: w.name, + title: title(`Realized Profit (${w.name})`), + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.realized.profit.sum[w.key].usd, name, color, unit: Unit.usd })), + })), }, { name: "Loss", - tree: [ - { name: "24h", title: title("Realized Loss (24h)"), bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.realized.loss.sum._24h.usd, name, color, unit: Unit.usd })) }, - { name: "7d", title: title("Realized Loss (7d)"), bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.realized.loss.sum._1w.usd, name, color, unit: Unit.usd })) }, - { name: "30d", title: title("Realized Loss (30d)"), bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.realized.loss.sum._1m.usd, name, color, unit: Unit.usd })) }, - { name: "1y", title: title("Realized Loss (1y)"), bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.realized.loss.sum._1y.usd, name, color, unit: Unit.usd })) }, - ], + tree: ROLLING_WINDOWS.map((w) => ({ + name: w.name, + title: title(`Realized Loss (${w.name})`), + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.realized.loss.sum[w.key].usd, name, color, unit: Unit.usd })), + })), }, ]; } @@ -800,12 +896,13 @@ function groupedRollingRealizedChartsFull(list, all, title) { ...groupedRollingRealizedCharts(list, all, title), { name: "P/L Ratio", - tree: [ - { name: "24h", title: title("Realized P/L Ratio (24h)"), bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => baseline({ metric: tree.realized.profitToLossRatio._24h, name, color, unit: Unit.ratio, base: 1 })) }, - { name: "7d", title: title("Realized P/L Ratio (7d)"), bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => baseline({ metric: tree.realized.profitToLossRatio._1w, name, color, unit: Unit.ratio, base: 1 })) }, - { name: "30d", title: title("Realized P/L Ratio (30d)"), bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => baseline({ metric: tree.realized.profitToLossRatio._1m, name, color, unit: Unit.ratio, base: 1 })) }, - { name: "1y", title: title("Realized P/L Ratio (1y)"), bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => baseline({ metric: tree.realized.profitToLossRatio._1y, name, color, unit: Unit.ratio, base: 1 })) }, - ], + tree: ROLLING_WINDOWS.map((w) => ({ + name: w.name, + title: title(`Realized P/L Ratio (${w.name})`), + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => + baseline({ metric: tree.realized.profitToLossRatio[w.key], name, color, unit: Unit.ratio, base: 1 }), + ), + })), }, ]; } @@ -871,7 +968,7 @@ function groupedRealizedSubfolderFull(list, all, title) { name: "30d Change", title: title("Realized P&L 30d Change"), bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => - baseline({ metric: tree.realized.netPnl.delta.change._1m.usd, name, color, unit: Unit.usd }), + baseline({ metric: tree.realized.netPnl.delta.absolute._1m.usd, name, color, unit: Unit.usd }), ), }, { name: "Rolling", tree: groupedRollingRealizedChartsFull(list, all, title) }, @@ -937,26 +1034,40 @@ function groupedPnlChartsWithMarketCap(list, all, title) { return [ { name: "Profit", - title: title("Unrealized Profit"), - bottom: [ - ...mapCohortsWithAll(list, all, ({ name, color, tree }) => - line({ metric: tree.unrealized.profit.base.usd, name, color, unit: Unit.usd }), - ), - ...mapCohortsWithAll(list, all, ({ name, color, tree }) => - baseline({ metric: tree.unrealized.profit.relToMcap.ratio, name, color, unit: Unit.pctMcap }), - ), + tree: [ + { + name: "USD", + title: title("Unrealized Profit"), + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => + line({ metric: tree.unrealized.profit.base.usd, name, color, unit: Unit.usd }), + ), + }, + { + name: "% of Mcap", + title: title("Unrealized Profit (% of Mcap)"), + bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => + percentRatio({ pattern: tree.unrealized.profit.relToMcap, name, color }), + ), + }, ], }, { name: "Loss", - title: title("Unrealized Loss"), - bottom: [ - ...mapCohortsWithAll(list, all, ({ name, color, tree }) => - line({ metric: tree.unrealized.loss.base.usd, name, color, unit: Unit.usd }), - ), - ...mapCohortsWithAll(list, all, ({ name, color, tree }) => - baseline({ metric: tree.unrealized.loss.relToMcap.ratio, name, color, unit: Unit.pctMcap }), - ), + tree: [ + { + name: "USD", + title: title("Unrealized Loss"), + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => + line({ metric: tree.unrealized.loss.base.usd, name, color, unit: Unit.usd }), + ), + }, + { + name: "% of Mcap", + title: title("Unrealized Loss (% of Mcap)"), + bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => + percentRatio({ pattern: tree.unrealized.loss.relToMcap, name, color }), + ), + }, ], }, { @@ -1013,50 +1124,87 @@ function groupedPnlChartsLongTerm(list, all, title) { return [ { name: "Profit", - title: title("Unrealized Profit"), - bottom: [ - ...mapCohortsWithAll(list, all, ({ name, color, tree }) => - line({ metric: tree.unrealized.profit.base.usd, name, color, unit: Unit.usd }), - ), - ...mapCohorts(list, ({ name, color, tree }) => - line({ metric: tree.unrealized.profit.relToOwnMcap.ratio, name, color, unit: Unit.pctOwnMcap }), - ), - ...mapCohortsWithAll(list, all, ({ name, color, tree }) => - line({ metric: tree.unrealized.profit.relToOwnGross.ratio, name, color, unit: Unit.pctOwnPnl }), - ), + tree: [ + { + name: "USD", + title: title("Unrealized Profit"), + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => + line({ metric: tree.unrealized.profit.base.usd, name, color, unit: Unit.usd }), + ), + }, + { + name: "% of Own Mcap", + title: title("Unrealized Profit (% of Own Mcap)"), + bottom: flatMapCohorts(list, ({ name, color, tree }) => + percentRatio({ pattern: tree.unrealized.profit.relToOwnMcap, name, color }), + ), + }, + { + name: "% of Own P&L", + title: title("Unrealized Profit (% of Own P&L)"), + bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => + percentRatio({ pattern: tree.unrealized.profit.relToOwnGross, name, color }), + ), + }, ], }, { name: "Loss", - title: title("Unrealized Loss"), - bottom: [ - ...mapCohortsWithAll(list, all, ({ name, color, tree }) => - line({ metric: tree.unrealized.loss.base.usd, name, color, unit: Unit.usd }), - ), - ...mapCohortsWithAll(list, all, ({ name, color, tree }) => - line({ metric: tree.unrealized.loss.relToMcap.ratio, name, color, unit: Unit.pctMcap }), - ), - ...mapCohorts(list, ({ name, color, tree }) => - line({ metric: tree.unrealized.loss.relToOwnMcap.ratio, name, color, unit: Unit.pctOwnMcap }), - ), - ...mapCohortsWithAll(list, all, ({ name, color, tree }) => - line({ metric: tree.unrealized.loss.relToOwnGross.ratio, name, color, unit: Unit.pctOwnPnl }), - ), + tree: [ + { + name: "USD", + title: title("Unrealized Loss"), + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => + line({ metric: tree.unrealized.loss.base.usd, name, color, unit: Unit.usd }), + ), + }, + { + name: "% of Mcap", + title: title("Unrealized Loss (% of Mcap)"), + bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => + percentRatio({ pattern: tree.unrealized.loss.relToMcap, name, color }), + ), + }, + { + name: "% of Own Mcap", + title: title("Unrealized Loss (% of Own Mcap)"), + bottom: flatMapCohorts(list, ({ name, color, tree }) => + percentRatio({ pattern: tree.unrealized.loss.relToOwnMcap, name, color }), + ), + }, + { + name: "% of Own P&L", + title: title("Unrealized Loss (% of Own P&L)"), + bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => + percentRatio({ pattern: tree.unrealized.loss.relToOwnGross, name, color }), + ), + }, ], }, { name: "Net P&L", - title: title("Net Unrealized P&L"), - bottom: [ - ...mapCohortsWithAll(list, all, ({ name, color, tree }) => - baseline({ metric: tree.unrealized.netPnl.usd, name, color, unit: Unit.usd }), - ), - ...mapCohorts(list, ({ name, color, tree }) => - baseline({ metric: tree.unrealized.netPnl.relToOwnMcap.ratio, name, color, unit: Unit.pctOwnMcap }), - ), - ...mapCohortsWithAll(list, all, ({ name, color, tree }) => - baseline({ metric: tree.unrealized.netPnl.relToOwnGross.ratio, name, color, unit: Unit.pctOwnPnl }), - ), + tree: [ + { + name: "USD", + title: title("Net Unrealized P&L"), + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => + baseline({ metric: tree.unrealized.netPnl.usd, name, color, unit: Unit.usd }), + ), + }, + { + name: "% of Own Mcap", + title: title("Net Unrealized P&L (% of Own Mcap)"), + bottom: flatMapCohorts(list, ({ name, color, tree }) => + percentRatioBaseline({ pattern: tree.unrealized.netPnl.relToOwnMcap, name, color }), + ), + }, + { + name: "% of Own P&L", + title: title("Net Unrealized P&L (% of Own P&L)"), + bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => + percentRatioBaseline({ pattern: tree.unrealized.netPnl.relToOwnGross, name, color }), + ), + }, ], }, ]; diff --git a/website/scripts/options/distribution/valuation.js b/website/scripts/options/distribution/valuation.js index ba0223bd3..8eef6e24f 100644 --- a/website/scripts/options/distribution/valuation.js +++ b/website/scripts/options/distribution/valuation.js @@ -11,8 +11,8 @@ */ import { Unit } from "../../utils/units.js"; -import { line, baseline } from "../series.js"; -import { createRatioChart, mapCohortsWithAll } from "../shared.js"; +import { ROLLING_WINDOWS, line, baseline, mapWindows, rollingWindowsTree, rollingPercentRatioTree, percentRatio, percentRatioBaseline } from "../series.js"; +import { createRatioChart, mapCohortsWithAll, flatMapCohortsWithAll } from "../shared.js"; /** * @param {UtxoCohortObject | CohortWithoutRelative} cohort @@ -30,20 +30,6 @@ function createSingleRealizedCapSeries(cohort) { ]; } -/** - * @param {UtxoCohortObject | CohortWithoutRelative} cohort - * @returns {AnyFetchedSeriesBlueprint[]} - */ -function createSingle30dChangeSeries(cohort) { - return [ - baseline({ - metric: cohort.tree.realized.cap.delta.change._1m.usd, - name: "30d Change", - unit: Unit.usd, - }), - ]; -} - /** * Create valuation section for cohorts with full ratio patterns * (CohortAll, CohortFull, CohortWithPercentiles) @@ -57,21 +43,25 @@ export function createValuationSectionFull({ cohort, title }) { tree: [ { name: "Realized Cap", - title: title("Realized Cap"), - bottom: [ - ...createSingleRealizedCapSeries(cohort), - baseline({ - metric: tree.realized.cap.relToOwnMcap.percent, - name: "Rel. to Own M.Cap", - color, - unit: Unit.pctOwnMcap, - }), + tree: [ + { + name: "USD", + title: title("Realized Cap"), + bottom: createSingleRealizedCapSeries(cohort), + }, + { + name: "% of Own Mcap", + title: title("Realized Cap (% of Own Mcap)"), + bottom: percentRatioBaseline({ pattern: tree.realized.cap.relToOwnMcap, name: "Rel. to Own M.Cap", color }), + }, ], }, { - name: "30d Change", - title: title("Realized Cap 30d Change"), - bottom: createSingle30dChangeSeries(cohort), + name: "Change", + tree: [ + { ...rollingWindowsTree({ windows: mapWindows(tree.realized.cap.delta.absolute, (c) => c.usd), title: title("Realized Cap Change"), unit: Unit.usd, series: baseline }), name: "Absolute" }, + { ...rollingPercentRatioTree({ windows: tree.realized.cap.delta.rate, title: title("Realized Cap Rate") }), name: "Rate" }, + ], }, createRatioChart({ title, @@ -101,9 +91,11 @@ export function createValuationSection({ cohort, title }) { bottom: createSingleRealizedCapSeries(cohort), }, { - name: "30d Change", - title: title("Realized Cap 30d Change"), - bottom: createSingle30dChangeSeries(cohort), + name: "Change", + tree: [ + { ...rollingWindowsTree({ windows: mapWindows(tree.realized.cap.delta.absolute, (c) => c.usd), title: title("Realized Cap Change"), unit: Unit.usd, series: baseline }), name: "Absolute" }, + { ...rollingPercentRatioTree({ windows: tree.realized.cap.delta.rate, title: title("Realized Cap Rate") }), name: "Rate" }, + ], }, { name: "MVRV", @@ -142,16 +134,29 @@ export function createGroupedValuationSection({ list, all, title }) { ), }, { - name: "30d Change", - title: title("Realized Cap 30d Change"), - bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => - baseline({ - metric: tree.realized.cap.delta.change._1m.usd, - name, - color, - unit: Unit.usd, - }), - ), + name: "Change", + tree: [ + { + name: "Absolute", + tree: ROLLING_WINDOWS.map((w) => ({ + name: w.name, + title: title(`Realized Cap Change (${w.name})`), + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => + baseline({ metric: tree.realized.cap.delta.absolute[w.key].usd, name, color, unit: Unit.usd }), + ), + })), + }, + { + name: "Rate", + tree: ROLLING_WINDOWS.map((w) => ({ + name: w.name, + title: title(`Realized Cap Rate (${w.name})`), + bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => + percentRatio({ pattern: tree.realized.cap.delta.rate[w.key], name, color }), + ), + })), + }, + ], }, { name: "MVRV", @@ -184,37 +189,47 @@ export function createGroupedValuationSectionWithOwnMarketCap({ tree: [ { name: "Realized Cap", - title: title("Realized Cap"), - bottom: [ - ...mapCohortsWithAll(list, all, ({ name, color, tree }) => - line({ - metric: tree.realized.cap.usd, - name, - color, - unit: Unit.usd, - }), - ), - ...mapCohortsWithAll(list, all, ({ name, color, tree }) => - baseline({ - metric: tree.realized.cap.relToOwnMcap.percent, - name, - color, - unit: Unit.pctOwnMcap, - }), - ), + tree: [ + { + name: "USD", + title: title("Realized Cap"), + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => + line({ metric: tree.realized.cap.usd, name, color, unit: Unit.usd }), + ), + }, + { + name: "% of Own Mcap", + title: title("Realized Cap (% of Own Mcap)"), + bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => + percentRatio({ pattern: tree.realized.cap.relToOwnMcap, name, color }), + ), + }, ], }, { - name: "30d Change", - title: title("Realized Cap 30d Change"), - bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => - baseline({ - metric: tree.realized.cap.delta.change._1m.usd, - name, - color, - unit: Unit.usd, - }), - ), + name: "Change", + tree: [ + { + name: "Absolute", + tree: ROLLING_WINDOWS.map((w) => ({ + name: w.name, + title: title(`Realized Cap Change (${w.name})`), + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => + baseline({ metric: tree.realized.cap.delta.absolute[w.key].usd, name, color, unit: Unit.usd }), + ), + })), + }, + { + name: "Rate", + tree: ROLLING_WINDOWS.map((w) => ({ + name: w.name, + title: title(`Realized Cap Rate (${w.name})`), + bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => + percentRatio({ pattern: tree.realized.cap.delta.rate[w.key], name, color }), + ), + })), + }, + ], }, { name: "MVRV", diff --git a/website/scripts/options/network.js b/website/scripts/options/network.js index afee59603..ff4a0c36f 100644 --- a/website/scripts/options/network.js +++ b/website/scripts/options/network.js @@ -175,7 +175,7 @@ export function createNetworkSection() { name: "Trends", tree: [ rollingWindowsTree({ - windows: addresses.delta[key].change, + windows: addresses.delta[key].absolute, title: `${titlePrefix}Address Count Change`, unit: Unit.count, series: baseline, @@ -487,7 +487,7 @@ export function createNetworkSection() { }, rollingWindowsTree({ windows: mapWindows( - supply.marketCap.delta.change, + supply.marketCap.delta.absolute, (c) => c.usd, ), title: "Market Cap Change", @@ -1074,7 +1074,7 @@ export function createNetworkSection() { title: "UTXO Count 30d Change", bottom: [ baseline({ - metric: cohorts.utxo.all.outputs.unspentCount.delta.change._1m, + metric: cohorts.utxo.all.outputs.unspentCount.delta.absolute._1m, name: "30d Change", unit: Unit.count, }),