diff --git a/crates/brk_client/src/lib.rs b/crates/brk_client/src/lib.rs index d982ec70a..83d0b308b 100644 --- a/crates/brk_client/src/lib.rs +++ b/crates/brk_client/src/lib.rs @@ -5182,9 +5182,9 @@ pub struct MetricsTree_Market_Ath { pub high: CentsSatsUsdPattern, pub drawdown: BpsPercentRatioPattern5, pub days_since: MetricPattern1, - pub years_since: MetricPattern2, + pub years_since: MetricPattern1, pub max_days_between: MetricPattern1, - pub max_years_between: MetricPattern2, + pub max_years_between: MetricPattern1, } impl MetricsTree_Market_Ath { @@ -5193,9 +5193,9 @@ impl MetricsTree_Market_Ath { high: CentsSatsUsdPattern::new(client.clone(), "price_ath".to_string()), drawdown: BpsPercentRatioPattern5::new(client.clone(), "price_drawdown".to_string()), days_since: MetricPattern1::new(client.clone(), "days_since_price_ath".to_string()), - years_since: MetricPattern2::new(client.clone(), "years_since_price_ath".to_string()), + years_since: MetricPattern1::new(client.clone(), "years_since_price_ath".to_string()), max_days_between: MetricPattern1::new(client.clone(), "max_days_between_price_ath".to_string()), - max_years_between: MetricPattern2::new(client.clone(), "max_years_between_price_ath".to_string()), + max_years_between: MetricPattern1::new(client.clone(), "max_years_between_price_ath".to_string()), } } } diff --git a/crates/brk_computer/src/distribution/metrics/realized/full.rs b/crates/brk_computer/src/distribution/metrics/realized/full.rs index 8889b449d..51a11a505 100644 --- a/crates/brk_computer/src/distribution/metrics/realized/full.rs +++ b/crates/brk_computer/src/distribution/metrics/realized/full.rs @@ -313,22 +313,22 @@ impl RealizedFull { .value_created .base .height - .truncate_push(height, accum.profit_value_created)?; + .truncate_push(height, accum.profit_value_created())?; self.profit .value_destroyed .base .height - .truncate_push(height, accum.profit_value_destroyed)?; + .truncate_push(height, accum.profit_value_destroyed())?; self.loss .value_created .base .height - .truncate_push(height, accum.loss_value_created)?; + .truncate_push(height, accum.loss_value_created())?; self.loss .value_destroyed .base .height - .truncate_push(height, accum.loss_value_destroyed)?; + .truncate_push(height, accum.loss_value_destroyed())?; self.cap_raw .truncate_push(height, accum.cap_raw)?; self.investor @@ -353,7 +353,7 @@ impl RealizedFull { .value .base .height - .truncate_push(height, accum.peak_regret)?; + .truncate_push(height, accum.peak_regret())?; Ok(()) } @@ -600,23 +600,43 @@ impl RealizedFull { #[derive(Default)] pub struct RealizedFullAccum { - pub(crate) profit_value_created: Cents, - pub(crate) profit_value_destroyed: Cents, - pub(crate) loss_value_created: Cents, - pub(crate) loss_value_destroyed: Cents, + profit_value_created: CentsSats, + profit_value_destroyed: CentsSats, + loss_value_created: CentsSats, + loss_value_destroyed: CentsSats, pub(crate) cap_raw: CentsSats, pub(crate) investor_cap_raw: CentsSquaredSats, - pub(crate) peak_regret: Cents, + peak_regret: CentsSats, } impl RealizedFullAccum { pub(crate) fn add(&mut self, state: &RealizedState) { - self.profit_value_created += state.profit_value_created(); - self.profit_value_destroyed += state.profit_value_destroyed(); - self.loss_value_created += state.loss_value_created(); - self.loss_value_destroyed += state.loss_value_destroyed(); + self.profit_value_created += CentsSats::new(state.profit_value_created_raw()); + self.profit_value_destroyed += CentsSats::new(state.profit_value_destroyed_raw()); + self.loss_value_created += CentsSats::new(state.loss_value_created_raw()); + self.loss_value_destroyed += CentsSats::new(state.loss_value_destroyed_raw()); self.cap_raw += state.cap_raw(); self.investor_cap_raw += state.investor_cap_raw(); - self.peak_regret += state.peak_regret(); + self.peak_regret += CentsSats::new(state.peak_regret_raw()); + } + + pub(crate) fn profit_value_created(&self) -> Cents { + self.profit_value_created.to_cents() + } + + pub(crate) fn profit_value_destroyed(&self) -> Cents { + self.profit_value_destroyed.to_cents() + } + + pub(crate) fn loss_value_created(&self) -> Cents { + self.loss_value_created.to_cents() + } + + pub(crate) fn loss_value_destroyed(&self) -> Cents { + self.loss_value_destroyed.to_cents() + } + + pub(crate) fn peak_regret(&self) -> Cents { + self.peak_regret.to_cents() } } diff --git a/crates/brk_computer/src/market/ath/import.rs b/crates/brk_computer/src/market/ath/import.rs index be7d1c710..8503ace9c 100644 --- a/crates/brk_computer/src/market/ath/import.rs +++ b/crates/brk_computer/src/market/ath/import.rs @@ -1,11 +1,11 @@ use brk_error::Result; use brk_types::Version; -use vecdb::Database; +use vecdb::{Database, ReadableCloneableVec}; use super::Vecs; use crate::{ indexes, - internal::{PerBlock, DaysToYears, DerivedResolutions, PercentPerBlock, Price}, + internal::{DaysToYears, LazyPerBlock, PerBlock, PercentPerBlock, Price}, }; const VERSION: Version = Version::ONE; @@ -23,18 +23,20 @@ impl Vecs { let max_days_between = PerBlock::forced_import(db, "max_days_between_price_ath", v, indexes)?; - let max_years_between = DerivedResolutions::from_computed::( + let max_years_between = LazyPerBlock::from_computed::( "max_years_between_price_ath", v, + max_days_between.height.read_only_boxed_clone(), &max_days_between, ); let days_since = PerBlock::forced_import(db, "days_since_price_ath", v, indexes)?; - let years_since = DerivedResolutions::from_computed::( + let years_since = LazyPerBlock::from_computed::( "years_since_price_ath", v, + days_since.height.read_only_boxed_clone(), &days_since, ); diff --git a/crates/brk_computer/src/market/ath/vecs.rs b/crates/brk_computer/src/market/ath/vecs.rs index dac621d51..6a2c21b4f 100644 --- a/crates/brk_computer/src/market/ath/vecs.rs +++ b/crates/brk_computer/src/market/ath/vecs.rs @@ -2,14 +2,14 @@ use brk_traversable::Traversable; use brk_types::{BasisPointsSigned16, Cents, StoredF32}; use vecdb::{Rw, StorageMode}; -use crate::internal::{PerBlock, DerivedResolutions, PercentPerBlock, Price}; +use crate::internal::{LazyPerBlock, PerBlock, PercentPerBlock, Price}; #[derive(Traversable)] pub struct Vecs { pub high: Price>, pub drawdown: PercentPerBlock, pub days_since: PerBlock, - pub years_since: DerivedResolutions, + pub years_since: LazyPerBlock, pub max_days_between: PerBlock, - pub max_years_between: DerivedResolutions, + pub max_years_between: LazyPerBlock, } diff --git a/crates/brk_types/src/basis_points_16.rs b/crates/brk_types/src/basis_points_16.rs index f25349970..158db615a 100644 --- a/crates/brk_types/src/basis_points_16.rs +++ b/crates/brk_types/src/basis_points_16.rs @@ -79,12 +79,12 @@ impl From for u16 { impl From for BasisPoints16 { #[inline] fn from(value: f32) -> Self { - let value = value.max(0.0); + let scaled = (value * 10000.0).round(); debug_assert!( - value <= u16::MAX as f32 / 10000.0, + scaled >= 0.0 && scaled <= u16::MAX as f32, "f32 out of BasisPoints16 range: {value}" ); - Self((value * 10000.0).round() as u16) + Self(scaled as u16) } } @@ -93,12 +93,12 @@ impl From for BasisPoints16 { impl From for BasisPoints16 { #[inline] fn from(value: f64) -> Self { - let value = value.max(0.0); + let scaled = (value * 10000.0).round(); debug_assert!( - value <= u16::MAX as f64 / 10000.0, + scaled >= 0.0 && scaled <= u16::MAX as f64, "f64 out of BasisPoints16 range: {value}" ); - Self((value * 10000.0).round() as u16) + Self(scaled as u16) } } diff --git a/crates/brk_types/src/basis_points_signed_32.rs b/crates/brk_types/src/basis_points_signed_32.rs index 56ae1fe78..7a6919d60 100644 --- a/crates/brk_types/src/basis_points_signed_32.rs +++ b/crates/brk_types/src/basis_points_signed_32.rs @@ -95,7 +95,10 @@ impl From for BasisPointsSigned32 { impl From for BasisPointsSigned32 { #[inline] fn from(value: f32) -> Self { - Self((value * 10000.0).round() as i32) + let scaled = (value * 10000.0) + .round() + .clamp(i32::MIN as f32, i32::MAX as f32); + Self(scaled as i32) } } diff --git a/crates/brk_types/src/sats.rs b/crates/brk_types/src/sats.rs index 86f0bab97..73cf8537d 100644 --- a/crates/brk_types/src/sats.rs +++ b/crates/brk_types/src/sats.rs @@ -257,13 +257,6 @@ impl From for Sats { } } -impl From for f32 { - #[inline] - fn from(value: Sats) -> Self { - value.0 as f32 - } -} - impl From for Sats { #[inline] fn from(value: f64) -> Self { diff --git a/crates/brk_types/src/stored_f32.rs b/crates/brk_types/src/stored_f32.rs index d62275cad..22238043e 100644 --- a/crates/brk_types/src/stored_f32.rs +++ b/crates/brk_types/src/stored_f32.rs @@ -11,7 +11,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use vecdb::{CheckedSub, Formattable, Pco, PrintableIndex}; -use crate::{Close, Sats, StoredU32}; +use crate::{Close, StoredU32}; use super::{Dollars, StoredF64}; @@ -143,13 +143,6 @@ impl From for StoredF32 { } } -impl From for StoredF32 { - #[inline] - fn from(value: Sats) -> Self { - Self(f32::from(value)) - } -} - impl From> for StoredF32 { #[inline] fn from(value: Close) -> Self { diff --git a/modules/brk-client/index.js b/modules/brk-client/index.js index bf32fda7b..9d3f2f166 100644 --- a/modules/brk-client/index.js +++ b/modules/brk-client/index.js @@ -4944,9 +4944,9 @@ function createUnspentPattern(client, acc) { * @property {CentsSatsUsdPattern} high * @property {BpsPercentRatioPattern5} drawdown * @property {MetricPattern1} daysSince - * @property {MetricPattern2} yearsSince + * @property {MetricPattern1} yearsSince * @property {MetricPattern1} maxDaysBetween - * @property {MetricPattern2} maxYearsBetween + * @property {MetricPattern1} maxYearsBetween */ /** @@ -7498,9 +7498,9 @@ class BrkClient extends BrkClientBase { high: createCentsSatsUsdPattern(this, 'price_ath'), drawdown: createBpsPercentRatioPattern5(this, 'price_drawdown'), daysSince: createMetricPattern1(this, 'days_since_price_ath'), - yearsSince: createMetricPattern2(this, 'years_since_price_ath'), + yearsSince: createMetricPattern1(this, 'years_since_price_ath'), maxDaysBetween: createMetricPattern1(this, 'max_days_between_price_ath'), - maxYearsBetween: createMetricPattern2(this, 'max_years_between_price_ath'), + maxYearsBetween: createMetricPattern1(this, 'max_years_between_price_ath'), }, lookback: { _24h: createCentsSatsUsdPattern(this, 'price_lookback_24h'), diff --git a/packages/brk_client/brk_client/__init__.py b/packages/brk_client/brk_client/__init__.py index ff3898bbf..2e24be4c7 100644 --- a/packages/brk_client/brk_client/__init__.py +++ b/packages/brk_client/brk_client/__init__.py @@ -4162,9 +4162,9 @@ class MetricsTree_Market_Ath: self.high: CentsSatsUsdPattern = CentsSatsUsdPattern(client, 'price_ath') self.drawdown: BpsPercentRatioPattern5 = BpsPercentRatioPattern5(client, 'price_drawdown') self.days_since: MetricPattern1[StoredF32] = MetricPattern1(client, 'days_since_price_ath') - self.years_since: MetricPattern2[StoredF32] = MetricPattern2(client, 'years_since_price_ath') + self.years_since: MetricPattern1[StoredF32] = MetricPattern1(client, 'years_since_price_ath') self.max_days_between: MetricPattern1[StoredF32] = MetricPattern1(client, 'max_days_between_price_ath') - self.max_years_between: MetricPattern2[StoredF32] = MetricPattern2(client, 'max_years_between_price_ath') + self.max_years_between: MetricPattern1[StoredF32] = MetricPattern1(client, 'max_years_between_price_ath') class MetricsTree_Market_Lookback: """Metrics tree node.""" diff --git a/website/scripts/options/mining.js b/website/scripts/options/mining.js index 2b5350c2a..a3c8b3c5d 100644 --- a/website/scripts/options/mining.js +++ b/website/scripts/options/mining.js @@ -10,6 +10,9 @@ import { dotted, distributionBtcSatsUsd, rollingWindowsTree, + ROLLING_WINDOWS, + percentRatio, + percentRatioDots, } from "./series.js"; import { satsBtcUsd, @@ -83,40 +86,11 @@ export function createMiningSection() { name: "Dominance", title: `Dominance: ${name}`, bottom: [ - dots({ - metric: pool.dominance._24h.percent, - name: "24h", - color: colors.time._24h, - unit: Unit.percentage, - defaultActive: false, - }), - line({ - metric: pool.dominance._1w.percent, - name: "1w", - color: colors.time._1w, - unit: Unit.percentage, - defaultActive: false, - }), - line({ - metric: pool.dominance._1m.percent, - name: "1m", - color: colors.time._1m, - unit: Unit.percentage, - }), - line({ - metric: pool.dominance._1y.percent, - name: "1y", - color: colors.time._1y, - unit: Unit.percentage, - defaultActive: false, - }), - line({ - metric: pool.dominance.percent, - name: "All Time", - color: colors.time.all, - unit: Unit.percentage, - defaultActive: false, - }), + ...percentRatioDots({ pattern: pool.dominance._24h, name: "24h", color: colors.time._24h, defaultActive: false }), + ...percentRatio({ pattern: pool.dominance._1w, name: "1w", color: colors.time._1w, defaultActive: false }), + ...percentRatio({ pattern: pool.dominance._1m, name: "1m", color: colors.time._1m }), + ...percentRatio({ pattern: pool.dominance._1y, name: "1y", color: colors.time._1y, defaultActive: false }), + ...percentRatio({ pattern: pool.dominance, name: "All Time", color: colors.time.all, defaultActive: false }), ], }, { @@ -179,14 +153,7 @@ export function createMiningSection() { { name: "Dominance", title: `Dominance: ${name}`, - bottom: [ - line({ - metric: pool.dominance.percent, - name: "All Time", - color: colors.time.all, - unit: Unit.percentage, - }), - ], + bottom: percentRatio({ pattern: pool.dominance, name: "All Time", color: colors.time.all }), }, { name: "Blocks Mined", @@ -299,14 +266,11 @@ export function createMiningSection() { { name: "Drawdown", title: "Network Hashrate Drawdown", - bottom: [ - line({ - metric: mining.hashrate.rate.drawdown.percent, - name: "Drawdown", - unit: Unit.percentage, - color: colors.loss, - }), - ], + bottom: percentRatio({ + pattern: mining.hashrate.rate.drawdown, + name: "Drawdown", + color: colors.loss, + }), }, ], }, @@ -599,8 +563,11 @@ export function createMiningSection() { }, { name: "Distribution", - title: "Transaction Fee Revenue per Block Distribution", - bottom: distributionBtcSatsUsd(mining.rewards.fees._24h), + tree: ROLLING_WINDOWS.map((w) => ({ + name: w.name, + title: `Fee Revenue per Block Distribution (${w.name})`, + bottom: distributionBtcSatsUsd(mining.rewards.fees[w.key]), + })), }, { name: "Cumulative", @@ -623,72 +590,20 @@ export function createMiningSection() { name: "Subsidy", title: "Subsidy Dominance", bottom: [ - line({ - metric: mining.rewards.subsidy.dominance.percent, - name: "All-time", - color: colors.time.all, - unit: Unit.percentage, - }), - line({ - metric: mining.rewards.subsidy.dominance._24h.percent, - name: "24h", - color: colors.time._24h, - unit: Unit.percentage, - }), - line({ - metric: mining.rewards.subsidy.dominance._1w.percent, - name: "7d", - color: colors.time._1w, - unit: Unit.percentage, - }), - line({ - metric: mining.rewards.subsidy.dominance._1m.percent, - name: "30d", - color: colors.time._1m, - unit: Unit.percentage, - }), - line({ - metric: mining.rewards.subsidy.dominance._1y.percent, - name: "1y", - color: colors.time._1y, - unit: Unit.percentage, - }), + ...percentRatio({ pattern: mining.rewards.subsidy.dominance, name: "All-time", color: colors.time.all }), + ...ROLLING_WINDOWS.flatMap((w) => + percentRatio({ pattern: mining.rewards.subsidy.dominance[w.key], name: w.name, color: w.color }), + ), ], }, { name: "Fees", title: "Fee Dominance", bottom: [ - line({ - metric: mining.rewards.fees.dominance.percent, - name: "All-time", - color: colors.time.all, - unit: Unit.percentage, - }), - line({ - metric: mining.rewards.fees.dominance._24h.percent, - name: "24h", - color: colors.time._24h, - unit: Unit.percentage, - }), - line({ - metric: mining.rewards.fees.dominance._1w.percent, - name: "7d", - color: colors.time._1w, - unit: Unit.percentage, - }), - line({ - metric: mining.rewards.fees.dominance._1m.percent, - name: "30d", - color: colors.time._1m, - unit: Unit.percentage, - }), - line({ - metric: mining.rewards.fees.dominance._1y.percent, - name: "1y", - color: colors.time._1y, - unit: Unit.percentage, - }), + ...percentRatio({ pattern: mining.rewards.fees.dominance, name: "All-time", color: colors.time.all }), + ...ROLLING_WINDOWS.flatMap((w) => + percentRatio({ pattern: mining.rewards.fees.dominance[w.key], name: w.name, color: w.color }), + ), ], }, ], @@ -697,92 +612,35 @@ export function createMiningSection() { name: "All-time", title: "Revenue Dominance (All-time)", bottom: [ - line({ - metric: mining.rewards.subsidy.dominance.percent, - name: "Subsidy", - color: colors.mining.subsidy, - unit: Unit.percentage, - }), - line({ - metric: mining.rewards.fees.dominance.percent, - name: "Fees", - color: colors.mining.fee, - unit: Unit.percentage, - }), + ...percentRatio({ pattern: mining.rewards.subsidy.dominance, name: "Subsidy", color: colors.mining.subsidy }), + ...percentRatio({ pattern: mining.rewards.fees.dominance, name: "Fees", color: colors.mining.fee }), ], }, - { - name: "24h", - title: "Revenue Dominance (24h)", + ...ROLLING_WINDOWS.map((w) => ({ + name: w.name, + title: `Revenue Dominance (${w.name})`, bottom: [ - line({ - metric: mining.rewards.subsidy.dominance._24h.percent, - name: "Subsidy", - color: colors.mining.subsidy, - unit: Unit.percentage, - }), - line({ - metric: mining.rewards.fees.dominance._24h.percent, - name: "Fees", - color: colors.mining.fee, - unit: Unit.percentage, - }), + ...percentRatio({ pattern: mining.rewards.subsidy.dominance[w.key], name: "Subsidy", color: colors.mining.subsidy }), + ...percentRatio({ pattern: mining.rewards.fees.dominance[w.key], name: "Fees", color: colors.mining.fee }), ], - }, + })), + ], + }, + { + name: "Fee Multiple", + tree: [ { - name: "7d", - title: "Revenue Dominance (7d)", - bottom: [ - line({ - metric: mining.rewards.subsidy.dominance._1w.percent, - name: "Subsidy", - color: colors.mining.subsidy, - unit: Unit.percentage, - }), - line({ - metric: mining.rewards.fees.dominance._1w.percent, - name: "Fees", - color: colors.mining.fee, - unit: Unit.percentage, - }), - ], - }, - { - name: "30d", - title: "Revenue Dominance (30d)", - bottom: [ - line({ - metric: mining.rewards.subsidy.dominance._1m.percent, - name: "Subsidy", - color: colors.mining.subsidy, - unit: Unit.percentage, - }), - line({ - metric: mining.rewards.fees.dominance._1m.percent, - name: "Fees", - color: colors.mining.fee, - unit: Unit.percentage, - }), - ], - }, - { - name: "1y", - title: "Revenue Dominance (1y)", - bottom: [ - line({ - metric: mining.rewards.subsidy.dominance._1y.percent, - name: "Subsidy", - color: colors.mining.subsidy, - unit: Unit.percentage, - }), - line({ - metric: mining.rewards.fees.dominance._1y.percent, - name: "Fees", - color: colors.mining.fee, - unit: Unit.percentage, - }), - ], + name: "Compare", + title: "Fee-to-Subsidy Ratio", + bottom: ROLLING_WINDOWS.map((w) => + line({ metric: mining.rewards.fees.ratioMultiple[w.key].ratio, name: w.name, color: w.color, unit: Unit.ratio }), + ), }, + ...ROLLING_WINDOWS.map((w) => ({ + name: w.name, + title: `Fee-to-Subsidy Ratio (${w.name})`, + bottom: [line({ metric: mining.rewards.fees.ratioMultiple[w.key].ratio, name: w.name, color: w.color, unit: Unit.ratio })], + })), ], }, { @@ -797,6 +655,23 @@ export function createMiningSection() { name: "sum", }), }, + { + name: "Rolling", + tree: [ + { + name: "Compare", + title: "Unclaimed Rewards Rolling", + bottom: ROLLING_WINDOWS.flatMap((w) => + satsBtcUsd({ pattern: mining.rewards.unclaimed.sum[w.key], name: w.name, color: w.color }), + ), + }, + ...ROLLING_WINDOWS.map((w) => ({ + name: w.name, + title: `Unclaimed Rewards ${w.name}`, + bottom: satsBtcUsd({ pattern: mining.rewards.unclaimed.sum[w.key], name: w.name, color: w.color }), + })), + ], + }, { name: "Cumulative", title: "Unclaimed Rewards (Total)", @@ -879,18 +754,8 @@ export function createMiningSection() { name: "Recovery", title: "Recovery", bottom: [ - line({ - metric: mining.hashrate.price.rebound.percent, - name: "Hash Price", - color: colors.usd, - unit: Unit.percentage, - }), - line({ - metric: mining.hashrate.value.rebound.percent, - name: "Hash Value", - color: colors.bitcoin, - unit: Unit.percentage, - }), + ...percentRatio({ pattern: mining.hashrate.price.rebound, name: "Hash Price", color: colors.usd }), + ...percentRatio({ pattern: mining.hashrate.value.rebound, name: "Hash Value", color: colors.bitcoin }), ], }, ], @@ -941,12 +806,11 @@ export function createMiningSection() { { name: "Dominance", title: "Dominance: Major Pools (1m)", - bottom: featuredPools.map((p, i) => - line({ - metric: p.pool.dominance._1m.percent, + bottom: featuredPools.flatMap((p, i) => + percentRatio({ + pattern: p.pool.dominance._1m, name: p.name, color: colors.at(i, featuredPools.length), - unit: Unit.percentage, }), ), }, @@ -983,12 +847,11 @@ export function createMiningSection() { { name: "Dominance", title: "Dominance: AntPool & Friends (1m)", - bottom: antpoolFriends.map((p, i) => - line({ - metric: p.pool.dominance._1m.percent, + bottom: antpoolFriends.flatMap((p, i) => + percentRatio({ + pattern: p.pool.dominance._1m, name: p.name, color: colors.at(i, antpoolFriends.length), - unit: Unit.percentage, }), ), },