diff --git a/crates/brk_computer/src/storage/vecs/grouped/builder.rs b/crates/brk_computer/src/storage/vecs/grouped/builder.rs index 84835ad53..cf8a2119b 100644 --- a/crates/brk_computer/src/storage/vecs/grouped/builder.rs +++ b/crates/brk_computer/src/storage/vecs/grouped/builder.rs @@ -209,8 +209,6 @@ where ) -> Result<()> where I2: StoredIndex + StoredType, - T: Ord + From, - f64: From, { let index = self.starting_index(max_from); @@ -295,20 +293,14 @@ where if needs_average_sum_or_total { let len = values.len(); + let sum = values.into_iter().fold(T::from(0), |a, b| a + b); if let Some(average) = self.average.as_mut() { - let len = len as f64; - let total = values - .iter() - .map(|v| f64::from(v.clone())) - .fold(0.0, |a, b| a + b); - let avg = T::from(total / len); + let avg = sum.clone() / len; average.forced_push_at(i, avg, exit)?; } if needs_sum_or_total { - let sum = values.into_iter().fold(T::from(0), |a, b| a + b); - if let Some(sum_vec) = self.sum.as_mut() { sum_vec.forced_push_at(i, sum.clone(), exit)?; } @@ -345,8 +337,6 @@ where ) -> Result<()> where I2: StoredIndex + StoredType, - T: Ord + From, - f64: From, { if self._90p.is_some() || self._75p.is_some() @@ -415,14 +405,12 @@ where .as_ref() .unwrap() .collect_inclusive_range(first_index, last_index)?; - let len = values.len() as f64; - let total = values - .into_iter() - .map(|v| f64::from(v)) - .fold(0.0, |a, b| a + b); + + let len = values.len(); + let total = values.into_iter().fold(T::from(0), |a, b| a + b); // TODO: Multiply by count then divide by total // Right now it's not 100% accurate as there could be more or less elements in the lower timeframe (28 days vs 31 days in a month for example) - let avg = T::from(total / len); + let avg = total / len; average.forced_push_at(i, avg, exit)?; } @@ -432,6 +420,7 @@ where .as_ref() .unwrap() .collect_inclusive_range(first_index, last_index)?; + let sum = values.into_iter().fold(T::from(0), |a, b| a + b); if let Some(sum_vec) = self.sum.as_mut() { diff --git a/crates/brk_computer/src/storage/vecs/grouped/from_dateindex.rs b/crates/brk_computer/src/storage/vecs/grouped/from_dateindex.rs index bf446fe01..0639cc0f4 100644 --- a/crates/brk_computer/src/storage/vecs/grouped/from_dateindex.rs +++ b/crates/brk_computer/src/storage/vecs/grouped/from_dateindex.rs @@ -27,8 +27,7 @@ const VERSION: Version = Version::ZERO; impl ComputedVecsFromDateindex where - T: ComputedType + Ord + From, - f64: From, + T: ComputedType, { pub fn forced_import( path: &Path, diff --git a/crates/brk_computer/src/storage/vecs/grouped/stored_type.rs b/crates/brk_computer/src/storage/vecs/grouped/stored_type.rs index 95d41ef0b..c98b6498f 100644 --- a/crates/brk_computer/src/storage/vecs/grouped/stored_type.rs +++ b/crates/brk_computer/src/storage/vecs/grouped/stored_type.rs @@ -4,10 +4,10 @@ use brk_vec::StoredType; pub trait ComputedType where - Self: StoredType + From + Div + Add, + Self: StoredType + From + Div + Add + Ord, { } impl ComputedType for T where - T: StoredType + From + Div + Add + T: StoredType + From + Div + Add + Ord { } diff --git a/crates/brk_computer/src/storage/vecs/marketprice.rs b/crates/brk_computer/src/storage/vecs/marketprice.rs index fea826bc6..f1548df09 100644 --- a/crates/brk_computer/src/storage/vecs/marketprice.rs +++ b/crates/brk_computer/src/storage/vecs/marketprice.rs @@ -65,6 +65,9 @@ pub struct Vecs { pub decadeindex_to_ohlc_in_sats: ComputedVec, } +const VERSION: Version = Version::ZERO; +const VERSION_IN_SATS: Version = Version::ONE; + impl Vecs { pub fn forced_import(path: &Path, compressed: Compressed) -> color_eyre::Result { fs::create_dir_all(path)?; @@ -87,7 +90,7 @@ impl Vecs { )?, dateindex_to_ohlc_in_sats: ComputedVec::forced_import( &path.join("dateindex_to_ohlc_in_sats"), - Version::ZERO, + VERSION + VERSION_IN_SATS + Version::ZERO, compressed, )?, dateindex_to_close_in_cents: ComputedVec::forced_import( @@ -122,7 +125,7 @@ impl Vecs { )?, height_to_ohlc_in_sats: ComputedVec::forced_import( &path.join("height_to_ohlc_in_sats"), - Version::ZERO, + VERSION + VERSION_IN_SATS + Version::ZERO, compressed, )?, height_to_close_in_cents: ComputedVec::forced_import( @@ -176,28 +179,28 @@ impl Vecs { timeindexes_to_open_in_sats: ComputedVecsFromDateindex::forced_import( path, "open_in_sats", - Version::ZERO, + VERSION + VERSION_IN_SATS + Version::ZERO, compressed, StorableVecGeneatorOptions::default().add_first(), )?, timeindexes_to_high_in_sats: ComputedVecsFromDateindex::forced_import( path, "high_in_sats", - Version::ZERO, + VERSION + VERSION_IN_SATS + Version::ZERO, compressed, StorableVecGeneatorOptions::default().add_max(), )?, timeindexes_to_low_in_sats: ComputedVecsFromDateindex::forced_import( path, "low_in_sats", - Version::ZERO, + VERSION + VERSION_IN_SATS + Version::ZERO, compressed, StorableVecGeneatorOptions::default().add_min(), )?, timeindexes_to_close_in_sats: ComputedVecsFromDateindex::forced_import( path, "close_in_sats", - Version::ZERO, + VERSION + VERSION_IN_SATS + Version::ZERO, compressed, StorableVecGeneatorOptions::default().add_last(), )?, @@ -232,28 +235,28 @@ impl Vecs { chainindexes_to_open_in_sats: ComputedVecsFromHeightStrict::forced_import( path, "open_in_sats", - Version::ZERO, + VERSION + VERSION_IN_SATS + Version::ZERO, compressed, StorableVecGeneatorOptions::default().add_first(), )?, chainindexes_to_high_in_sats: ComputedVecsFromHeightStrict::forced_import( path, "high_in_sats", - Version::ZERO, + VERSION + VERSION_IN_SATS + Version::ZERO, compressed, StorableVecGeneatorOptions::default().add_max(), )?, chainindexes_to_low_in_sats: ComputedVecsFromHeightStrict::forced_import( path, "low_in_sats", - Version::ZERO, + VERSION + VERSION_IN_SATS + Version::ZERO, compressed, StorableVecGeneatorOptions::default().add_min(), )?, chainindexes_to_close_in_sats: ComputedVecsFromHeightStrict::forced_import( path, "close_in_sats", - Version::ZERO, + VERSION + VERSION_IN_SATS + Version::ZERO, compressed, StorableVecGeneatorOptions::default().add_last(), )?, @@ -264,7 +267,7 @@ impl Vecs { )?, weekindex_to_ohlc_in_sats: ComputedVec::forced_import( &path.join("weekindex_to_ohlc_in_sats"), - Version::ZERO, + VERSION + VERSION_IN_SATS + Version::ZERO, compressed, )?, difficultyepoch_to_ohlc: ComputedVec::forced_import( @@ -274,7 +277,7 @@ impl Vecs { )?, difficultyepoch_to_ohlc_in_sats: ComputedVec::forced_import( &path.join("difficultyepoch_to_ohlc_in_sats"), - Version::ZERO, + VERSION + VERSION_IN_SATS + Version::ZERO, compressed, )?, monthindex_to_ohlc: ComputedVec::forced_import( @@ -284,7 +287,7 @@ impl Vecs { )?, monthindex_to_ohlc_in_sats: ComputedVec::forced_import( &path.join("monthindex_to_ohlc_in_sats"), - Version::ZERO, + VERSION + VERSION_IN_SATS + Version::ZERO, compressed, )?, quarterindex_to_ohlc: ComputedVec::forced_import( @@ -294,7 +297,7 @@ impl Vecs { )?, quarterindex_to_ohlc_in_sats: ComputedVec::forced_import( &path.join("quarterindex_to_ohlc_in_sats"), - Version::ZERO, + VERSION + VERSION_IN_SATS + Version::ZERO, compressed, )?, yearindex_to_ohlc: ComputedVec::forced_import( @@ -304,7 +307,7 @@ impl Vecs { )?, yearindex_to_ohlc_in_sats: ComputedVec::forced_import( &path.join("yearindex_to_ohlc_in_sats"), - Version::ZERO, + VERSION + VERSION_IN_SATS + Version::ZERO, compressed, )?, // halvingepoch_to_ohlc: StorableVec::forced_import(&path.join("halvingepoch_to_ohlc"), Version::ZERO, compressed)?, @@ -315,7 +318,7 @@ impl Vecs { )?, decadeindex_to_ohlc_in_sats: ComputedVec::forced_import( &path.join("decadeindex_to_ohlc_in_sats"), - Version::ZERO, + VERSION + VERSION_IN_SATS + Version::ZERO, compressed, )?, }) diff --git a/crates/brk_core/src/structs/bitcoin.rs b/crates/brk_core/src/structs/bitcoin.rs index 90ef59e44..8020a47dc 100644 --- a/crates/brk_core/src/structs/bitcoin.rs +++ b/crates/brk_core/src/structs/bitcoin.rs @@ -23,27 +23,27 @@ pub struct Bitcoin(f64); impl Add for Bitcoin { type Output = Self; fn add(self, rhs: Self) -> Self::Output { - Self(self.0 + rhs.0) + Self::from(Sats::from(self) + Sats::from(rhs)) } } impl Mul for Bitcoin { type Output = Self; fn mul(self, rhs: Self) -> Self::Output { - Self(self.0 * rhs.0) + Self::from(Sats::from(self) * Sats::from(rhs)) } } impl Div for Bitcoin { type Output = Self; fn div(self, rhs: usize) -> Self::Output { - Self(self.0 / rhs as f64) + Self::from(Sats::from(self) / rhs) } } impl From for Bitcoin { fn from(value: Sats) -> Self { - Self(u64::from(value) as f64 / (u64::from(Sats::ONE_BTC) as f64)) + Self(f64::from(value) / (f64::from(Sats::ONE_BTC))) } } diff --git a/crates/brk_core/src/structs/cents.rs b/crates/brk_core/src/structs/cents.rs index 1ff3382e4..7aa06256d 100644 --- a/crates/brk_core/src/structs/cents.rs +++ b/crates/brk_core/src/structs/cents.rs @@ -1,3 +1,5 @@ +use std::ops::{Add, Div}; + use serde::Serialize; use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; @@ -43,3 +45,17 @@ impl From for u64 { value.0 } } + +impl Add for Cents { + type Output = Self; + fn add(self, rhs: Self) -> Self::Output { + Self(self.0 + rhs.0) + } +} + +impl Div for Cents { + type Output = Self; + fn div(self, rhs: usize) -> Self::Output { + Self(self.0 / rhs as u64) + } +} diff --git a/crates/brk_core/src/structs/dollars.rs b/crates/brk_core/src/structs/dollars.rs index 53e8a7db2..d4568e70d 100644 --- a/crates/brk_core/src/structs/dollars.rs +++ b/crates/brk_core/src/structs/dollars.rs @@ -49,14 +49,14 @@ impl From for Dollars { impl Add for Dollars { type Output = Self; fn add(self, rhs: Self) -> Self::Output { - Self(self.0 + rhs.0) + Self::from(Cents::from(self) + Cents::from(rhs)) } } impl Div for Dollars { type Output = Self; fn div(self, rhs: usize) -> Self::Output { - Self(self.0 / rhs as f64) + Self::from(Cents::from(self) / rhs) } } diff --git a/crates/brk_core/src/structs/sats.rs b/crates/brk_core/src/structs/sats.rs index 7a3daaf6c..dd36ed37f 100644 --- a/crates/brk_core/src/structs/sats.rs +++ b/crates/brk_core/src/structs/sats.rs @@ -9,7 +9,7 @@ use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; use crate::CheckedSub; -use super::{Bitcoin, Dollars, Height}; +use super::{Bitcoin, Cents, Dollars, Height}; #[derive( Debug, @@ -30,6 +30,7 @@ pub struct Sats(u64); impl Sats { pub const ZERO: Self = Self(0); + pub const MAX: Self = Self(u64::MAX); pub const ONE_BTC: Self = Self(100_000_000); pub fn is_zero(&self) -> bool { @@ -39,8 +40,8 @@ impl Sats { impl Add for Sats { type Output = Self; - fn add(self, rhs: Sats) -> Self::Output { - Sats::from(self.0 + rhs.0) + fn add(self, rhs: Self) -> Self::Output { + Self::from(self.0 + rhs.0) } } @@ -93,7 +94,12 @@ impl Sum for Sats { impl Div for Sats { type Output = Self; fn div(self, rhs: Dollars) -> Self::Output { - Self((self.0 as f64 / f64::from(rhs)) as u64) + let raw_cents = u64::from(Cents::from(rhs)); + if raw_cents != 0 { + Self(self.0 * 100 / raw_cents) + } else { + Self::MAX + } } } @@ -118,7 +124,7 @@ impl From for Sats { impl From for Sats { fn from(value: f64) -> Self { - Self(value as u64) + Self(value.round() as u64) } } @@ -141,7 +147,7 @@ impl From for Amount { impl From for Sats { fn from(value: Bitcoin) -> Self { - Self((f64::from(value) * (u64::from(Sats::ONE_BTC) as f64)).round() as u64) + Self((f64::from(value) * (Sats::ONE_BTC.0 as f64)).round() as u64) } } diff --git a/crates/brk_core/src/structs/weight.rs b/crates/brk_core/src/structs/weight.rs index 991915a95..af8e1d312 100644 --- a/crates/brk_core/src/structs/weight.rs +++ b/crates/brk_core/src/structs/weight.rs @@ -64,3 +64,10 @@ impl Div for Weight { Self::from(self.0 as usize / rhs) } } + +impl Div for Weight { + type Output = Self; + fn div(self, rhs: Self) -> Self::Output { + Self(self.0 / rhs.0) + } +} diff --git a/websites/kibo.money/packages/lightweight-charts/wrapper.js b/websites/kibo.money/packages/lightweight-charts/wrapper.js index 86b367bda..66299950b 100644 --- a/websites/kibo.money/packages/lightweight-charts/wrapper.js +++ b/websites/kibo.money/packages/lightweight-charts/wrapper.js @@ -366,7 +366,7 @@ export default import("./v5.0.5-treeshaked/script.js").then((lc) => { this.addPriceScaleSelectorIfNeeded({ paneIndex, seriesType: "Candlestick", - id, + id: `${id}-${paneIndex}`, unit, }); @@ -449,7 +449,7 @@ export default import("./v5.0.5-treeshaked/script.js").then((lc) => { this.addPriceScaleSelectorIfNeeded({ paneIndex, seriesType: "Line", - id, + id: `${id}-${paneIndex}`, unit, }); @@ -532,7 +532,7 @@ export default import("./v5.0.5-treeshaked/script.js").then((lc) => { this.addPriceScaleSelectorIfNeeded({ paneIndex, seriesType: "Baseline", - id, + id: `${id}-${paneIndex}`, unit, }); diff --git a/websites/kibo.money/scripts/chart.js b/websites/kibo.money/scripts/chart.js index dad0a0043..86ba1e468 100644 --- a/websites/kibo.money/scripts/chart.js +++ b/websites/kibo.money/scripts/chart.js @@ -57,12 +57,13 @@ export function init({ /** @satisfies {Unit} */ ("Sats"), ]), signals, + sorted: true, }); signals.createEffect(topUnit, (topUnit) => { const { field: seriesTypeField, selected: topSeriesType } = utils.dom.createHorizontalChoiceField({ - defaultValue: "Candles", + defaultValue: "Line", keyPrefix: "charts", key: "seriestype-0", choices: /** @type {const} */ (["Candles", "Line"]), @@ -80,6 +81,7 @@ export function init({ key: "unit-1", choices: bottomUnits, signals, + sorted: true, }); signals.createEffect(bottomUnit, (bottomUnit) => { diff --git a/websites/kibo.money/scripts/main.js b/websites/kibo.money/scripts/main.js index f74c315c3..dd250964f 100644 --- a/websites/kibo.money/scripts/main.js +++ b/websites/kibo.money/scripts/main.js @@ -330,17 +330,23 @@ function createUtils() { * @param {T} args.choices * @param {string} [args.keyPrefix] * @param {string} args.key + * @param {boolean} [args.sorted] * @param {{createEffect: CreateEffect, createSignal: Signals["createSignal"]}} args.signals */ createHorizontalChoiceField({ title, id, - choices, + choices: unsortedChoices, defaultValue, keyPrefix, key, signals, + sorted, }) { + const choices = sorted + ? /** @type {T} */ (/** @type {any} */ (unsortedChoices.toSorted())) + : unsortedChoices; + /** @type {Signal} */ const selected = signals.createSignal(defaultValue, { save: { @@ -349,6 +355,9 @@ function createUtils() { key, }, }); + if (!choices.includes(selected())) { + selected.set(() => defaultValue); + } const field = window.document.createElement("div"); field.classList.add("field"); diff --git a/websites/kibo.money/scripts/options.js b/websites/kibo.money/scripts/options.js index 142c55151..a31d89d3a 100644 --- a/websites/kibo.money/scripts/options.js +++ b/websites/kibo.money/scripts/options.js @@ -30,8 +30,8 @@ * "Transactions" | * "USD" | * "Version" | - * "Virtual Bytes" | - * "Weight Units" + * "vB" | + * "WU" * } Unit * * @typedef {Object} BaseSeriesBlueprint @@ -224,6 +224,18 @@ function createPartialOptions(colors) { */ function createMinMaxPercentilesSeries({ concat }) { return /** @satisfies {AnyFetchedSeriesBlueprint[]} */ ([ + { + key: `${concat}-max`, + title: "Max", + color: colors.pink, + defaultActive: false, + }, + { + key: `${concat}-min`, + title: "Min", + color: colors.green, + defaultActive: false, + }, { key: `${concat}-median`, title: "Median", @@ -254,21 +266,20 @@ function createPartialOptions(colors) { color: colors.lime, defaultActive: false, }, - { - key: `${concat}-max`, - title: "Max", - color: colors.pink, - defaultActive: false, - }, - { - key: `${concat}-min`, - title: "Min", - color: colors.green, - defaultActive: false, - }, ]); } + /** + * @param {VecIdAverageBase & VecIdSumBase & TotalVecIdBase & VecIdMinBase & VecIdMaxBase & VecId90pBase & VecId75pBase & VecIdMedianBase & VecId25pBase & VecId10pBase} key + */ + function createAverageSumTotalMinMaxPercentilesSeries(key) { + return [ + createAverageSeries({ concat: key }), + ...createSumTotalSeries({ concat: key }), + ...createMinMaxPercentilesSeries({ concat: key }), + ]; + } + /** * @param {Object} args * @param {ChartableVecId & VecIdAverageBase & VecIdSumBase & TotalVecIdBase & VecIdMinBase & VecIdMaxBase & VecId90pBase & VecId75pBase & VecIdMedianBase & VecId25pBase & VecId10pBase} args.key @@ -280,9 +291,7 @@ function createPartialOptions(colors) { key, name, }), - createAverageSeries({ concat: key }), - ...createSumTotalSeries({ concat: key }), - ...createMinMaxPercentilesSeries({ concat: key }), + ...createAverageSumTotalMinMaxPercentilesSeries(key), ]; } @@ -363,17 +372,10 @@ function createPartialOptions(colors) { { name: "Count", title: "Transaction Count", - bottom: [ - createBaseSeries({ - key: "tx-count", - name: "Count", - }), - createAverageSeries({ concat: "tx-count" }), - ...createSumTotalSeries({ concat: "tx-count" }), - ...createMinMaxPercentilesSeries({ - concat: "tx-count", - }), - ], + bottom: createBaseAverageSumTotalMinMaxPercentilesSeries({ + key: "tx-count", + name: "Count", + }), }, { name: "Subsidy", @@ -415,9 +417,9 @@ function createPartialOptions(colors) { name: "Fee", title: "Transaction Fee", bottom: [ - createAverageSeries({ concat: "fee" }), - ...createSumTotalSeries({ concat: "fee" }), - ...createMinMaxPercentilesSeries({ concat: "fee" }), + ...createAverageSumTotalMinMaxPercentilesSeries("fee"), + ...createAverageSumTotalMinMaxPercentilesSeries("fee-in-btc"), + ...createAverageSumTotalMinMaxPercentilesSeries("fee-in-usd"), ], }, { @@ -701,9 +703,9 @@ export function initOptions({ } else if (key.includes("-size")) { unit = "Megabytes"; } else if (key.includes("weight")) { - unit = "Weight Units"; + unit = "WU"; } else if (key.includes("vbytes") || key.includes("vsize")) { - unit = "Virtual Bytes"; + unit = "vB"; } else if (key.match(/v[1-3]/g)) { unit = "Version"; } else {