From a2bd7ca29973bd0dcc054c2ea3d1122b056bcc3d Mon Sep 17 00:00:00 2001 From: nym21 Date: Sat, 28 Feb 2026 00:22:55 +0100 Subject: [PATCH] global: snapshot --- crates/brk_client/src/lib.rs | 12 ++-- crates/brk_computer/src/blocks/compute.rs | 46 +----------- crates/brk_computer/src/blocks/import.rs | 2 +- crates/brk_computer/src/blocks/time/import.rs | 39 +++++++++-- crates/brk_computer/src/blocks/time/mod.rs | 2 +- crates/brk_computer/src/blocks/time/vecs.rs | 70 +++++++++++++++++-- .../src/cointime/reserve_risk/compute.rs | 4 +- .../src/cointime/value/compute.rs | 8 +-- .../src/distribution/compute/block_loop.rs | 2 +- .../src/distribution/compute/context.rs | 2 +- .../src/distribution/metrics/realized/base.rs | 4 +- .../distribution/metrics/unrealized/base.rs | 4 +- crates/brk_computer/src/distribution/vecs.rs | 2 +- .../multi/from_height/ratio/extended.rs | 2 +- .../internal/multi/from_height/value_full.rs | 2 +- .../internal/multi/from_height/value_last.rs | 2 +- .../value_lazy_computed_cumulative.rs | 2 +- .../multi/from_height/value_sum_cumulative.rs | 2 +- .../src/internal/single/height/value.rs | 2 +- crates/brk_computer/src/market/ath/compute.rs | 4 +- crates/brk_computer/src/market/dca/compute.rs | 6 +- .../src/market/indicators/compute.rs | 2 +- .../src/market/indicators/macd.rs | 2 +- .../src/market/lookback/compute.rs | 2 +- .../src/market/moving_average/compute.rs | 2 +- .../brk_computer/src/market/range/compute.rs | 2 +- .../src/market/returns/compute.rs | 2 +- crates/brk_computer/src/prices/by_unit.rs | 15 ++-- crates/brk_computer/src/prices/compute.rs | 46 +++++++----- crates/brk_computer/src/prices/mod.rs | 29 ++++---- crates/brk_computer/src/prices/ohlcs.rs | 24 ++++--- crates/brk_types/src/index.rs | 2 +- modules/brk-client/index.js | 12 ++-- packages/brk_client/brk_client/__init__.py | 6 +- website/scripts/chart/index.js | 19 +++++ website/scripts/options/market.js | 2 +- website/scripts/options/types.js | 9 ++- website/scripts/panes/chart.js | 50 +++++++------ 38 files changed, 279 insertions(+), 166 deletions(-) diff --git a/crates/brk_client/src/lib.rs b/crates/brk_client/src/lib.rs index 0e3b41c00..8f594dc96 100644 --- a/crates/brk_client/src/lib.rs +++ b/crates/brk_client/src/lib.rs @@ -6015,17 +6015,17 @@ impl MetricsTree_Prices_Ohlc { /// Metrics tree node. pub struct MetricsTree_Prices_Price { - pub cents: MetricPattern20, - pub usd: MetricPattern20, - pub sats: MetricPattern20, + pub cents: MetricPattern1, + pub usd: MetricPattern1, + pub sats: MetricPattern1, } impl MetricsTree_Prices_Price { pub fn new(client: Arc, base_path: String) -> Self { Self { - cents: MetricPattern20::new(client.clone(), "price_cents".to_string()), - usd: MetricPattern20::new(client.clone(), "price".to_string()), - sats: MetricPattern20::new(client.clone(), "price_sats".to_string()), + cents: MetricPattern1::new(client.clone(), "price_cents".to_string()), + usd: MetricPattern1::new(client.clone(), "price".to_string()), + sats: MetricPattern1::new(client.clone(), "price_sats".to_string()), } } } diff --git a/crates/brk_computer/src/blocks/compute.rs b/crates/brk_computer/src/blocks/compute.rs index 3c67fc5e2..413f21a9c 100644 --- a/crates/brk_computer/src/blocks/compute.rs +++ b/crates/brk_computer/src/blocks/compute.rs @@ -14,49 +14,9 @@ impl Vecs { starting_indexes: &ComputeIndexes, exit: &Exit, ) -> Result<()> { - { - let ts = &mut self.time.timestamp; - - macro_rules! period { - ($field:ident) => { - ts.$field.compute_transform( - starting_indexes.$field, - &indexes.$field.first_height, - |(idx, _, _)| (idx, idx.to_timestamp()), - exit, - )?; - }; - } - - period!(minute1); - period!(minute5); - period!(minute10); - period!(minute30); - period!(hour1); - period!(hour4); - period!(hour12); - period!(day1); - period!(day3); - period!(week1); - period!(month1); - period!(month3); - period!(month6); - period!(year1); - period!(year10); - - ts.halvingepoch.compute_indirect( - starting_indexes.halvingepoch, - &indexes.halvingepoch.first_height, - &indexer.vecs.blocks.timestamp, - exit, - )?; - ts.difficultyepoch.compute_indirect( - starting_indexes.difficultyepoch, - &indexes.difficultyepoch.first_height, - &indexer.vecs.blocks.timestamp, - exit, - )?; - } + self.time + .timestamp + .compute(indexer, indexes, starting_indexes, exit)?; self.count .compute(indexer, &self.time, starting_indexes, exit)?; self.interval diff --git a/crates/brk_computer/src/blocks/import.rs b/crates/brk_computer/src/blocks/import.rs index 25c29c3a0..7c9a08a9a 100644 --- a/crates/brk_computer/src/blocks/import.rs +++ b/crates/brk_computer/src/blocks/import.rs @@ -29,7 +29,7 @@ impl Vecs { let interval = IntervalVecs::forced_import(&db, version, indexes)?; let size = SizeVecs::forced_import(&db, version, indexes)?; let weight = WeightVecs::forced_import(&db, version, indexes)?; - let time = TimeVecs::forced_import(&db, version)?; + let time = TimeVecs::forced_import(&db, version, indexes)?; let difficulty = DifficultyVecs::forced_import(&db, version, indexer, indexes)?; let halving = HalvingVecs::forced_import(&db, version, indexes)?; diff --git a/crates/brk_computer/src/blocks/time/import.rs b/crates/brk_computer/src/blocks/time/import.rs index e009a9187..584e0b9ca 100644 --- a/crates/brk_computer/src/blocks/time/import.rs +++ b/crates/brk_computer/src/blocks/time/import.rs @@ -2,11 +2,15 @@ use brk_error::Result; use brk_types::{Date, Height, Version}; use vecdb::{Database, EagerVec, ImportableVec, LazyVecFrom1, ReadableCloneableVec}; -use super::Vecs; -use crate::internal::EagerIndexes; +use super::{TimestampIndexes, Vecs}; +use crate::indexes; impl Vecs { - pub(crate) fn forced_import(db: &Database, version: Version) -> Result { + pub(crate) fn forced_import( + db: &Database, + version: Version, + indexes: &indexes::Vecs, + ) -> Result { let timestamp_monotonic = EagerVec::forced_import(db, "timestamp_monotonic", version)?; @@ -18,7 +22,34 @@ impl Vecs { |_height: Height, timestamp| Date::from(timestamp), ), timestamp_monotonic, - timestamp: EagerIndexes::forced_import(db, "timestamp", version)?, + timestamp: TimestampIndexes::forced_import(db, version, indexes)?, }) } } + +impl TimestampIndexes { + fn forced_import( + db: &Database, + version: Version, + indexes: &indexes::Vecs, + ) -> Result { + macro_rules! period { + ($field:ident) => { + LazyVecFrom1::init( + "timestamp", + version, + indexes.$field.first_height.read_only_boxed_clone(), + |idx, _: Height| idx.to_timestamp(), + ) + }; + } + + macro_rules! epoch { + ($field:ident) => { + ImportableVec::forced_import(db, "timestamp", version)? + }; + } + + Ok(Self(crate::indexes_from!(period, epoch))) + } +} diff --git a/crates/brk_computer/src/blocks/time/mod.rs b/crates/brk_computer/src/blocks/time/mod.rs index 1136f9ebd..546680f72 100644 --- a/crates/brk_computer/src/blocks/time/mod.rs +++ b/crates/brk_computer/src/blocks/time/mod.rs @@ -2,4 +2,4 @@ mod compute; mod import; mod vecs; -pub use vecs::Vecs; +pub use vecs::{TimestampIndexes, Vecs}; diff --git a/crates/brk_computer/src/blocks/time/vecs.rs b/crates/brk_computer/src/blocks/time/vecs.rs index 1a7a179b8..ae5564d01 100644 --- a/crates/brk_computer/src/blocks/time/vecs.rs +++ b/crates/brk_computer/src/blocks/time/vecs.rs @@ -1,13 +1,75 @@ +use brk_error::Result; use brk_traversable::Traversable; -use brk_types::{Date, Height, Timestamp}; -use vecdb::{EagerVec, LazyVecFrom1, PcoVec, Rw, StorageMode}; +use brk_types::{ + Date, Day1, Day3, DifficultyEpoch, HalvingEpoch, Height, Hour1, Hour12, Hour4, Minute1, + Minute10, Minute30, Minute5, Month1, Month3, Month6, Timestamp, Week1, Year1, Year10, +}; +use derive_more::{Deref, DerefMut}; +use vecdb::{EagerVec, Exit, LazyVecFrom1, PcoVec, Rw, StorageMode}; -use crate::internal::EagerIndexes; +use crate::{ComputeIndexes, indexes, internal::Indexes}; /// Timestamp and date metrics for blocks #[derive(Traversable)] pub struct Vecs { pub date: LazyVecFrom1, pub timestamp_monotonic: M::Stored>>, - pub timestamp: EagerIndexes, + pub timestamp: TimestampIndexes, +} + +/// Per-period timestamp indexes. +/// +/// Time-based periods (minute1–year10) are lazy: `idx.to_timestamp()` is a pure +/// function of the index, so no storage or decompression is needed. +/// Epoch-based periods (halvingepoch, difficultyepoch) are eager: their timestamps +/// come from block data via `compute_indirect`. +#[derive(Deref, DerefMut, Traversable)] +#[traversable(transparent)] +pub struct TimestampIndexes( + #[allow(clippy::type_complexity)] + pub Indexes< + LazyVecFrom1, + LazyVecFrom1, + LazyVecFrom1, + LazyVecFrom1, + LazyVecFrom1, + LazyVecFrom1, + LazyVecFrom1, + LazyVecFrom1, + LazyVecFrom1, + LazyVecFrom1, + LazyVecFrom1, + LazyVecFrom1, + LazyVecFrom1, + LazyVecFrom1, + LazyVecFrom1, + M::Stored>>, + M::Stored>>, + >, +); + +impl TimestampIndexes { + /// Compute epoch timestamps via indirect lookup from block timestamps. + /// Time-based periods are lazy (idx.to_timestamp()) and need no compute. + pub(crate) fn compute( + &mut self, + indexer: &brk_indexer::Indexer, + indexes: &indexes::Vecs, + starting_indexes: &ComputeIndexes, + exit: &Exit, + ) -> Result<()> { + self.halvingepoch.compute_indirect( + starting_indexes.halvingepoch, + &indexes.halvingepoch.first_height, + &indexer.vecs.blocks.timestamp, + exit, + )?; + self.difficultyepoch.compute_indirect( + starting_indexes.difficultyepoch, + &indexes.difficultyepoch.first_height, + &indexer.vecs.blocks.timestamp, + exit, + )?; + Ok(()) + } } diff --git a/crates/brk_computer/src/cointime/reserve_risk/compute.rs b/crates/brk_computer/src/cointime/reserve_risk/compute.rs index e871cb216..6d8b404c9 100644 --- a/crates/brk_computer/src/cointime/reserve_risk/compute.rs +++ b/crates/brk_computer/src/cointime/reserve_risk/compute.rs @@ -23,7 +23,7 @@ impl Vecs { self.hodl_bank.compute_cumulative_transformed_binary( starting_indexes.height, - &prices.price.usd, + &prices.price.usd.height, &self.vocdd_365d_median, |price, median| StoredF64::from(f64::from(price) - f64::from(median)), exit, @@ -31,7 +31,7 @@ impl Vecs { self.reserve_risk.height.compute_divide( starting_indexes.height, - &prices.price.usd, + &prices.price.usd.height, &self.hodl_bank, exit, )?; diff --git a/crates/brk_computer/src/cointime/value/compute.rs b/crates/brk_computer/src/cointime/value/compute.rs index dd3326d60..fe78ce962 100644 --- a/crates/brk_computer/src/cointime/value/compute.rs +++ b/crates/brk_computer/src/cointime/value/compute.rs @@ -45,7 +45,7 @@ impl Vecs { .compute(starting_indexes.height, &window_starts, exit, |vec| { vec.compute_multiply( starting_indexes.height, - &prices.price.usd, + &prices.price.usd.height, &coinblocks_destroyed.height, exit, )?; @@ -56,7 +56,7 @@ impl Vecs { .compute(starting_indexes.height, &window_starts, exit, |vec| { vec.compute_multiply( starting_indexes.height, - &prices.price.usd, + &prices.price.usd.height, &activity.coinblocks_created.height, exit, )?; @@ -67,7 +67,7 @@ impl Vecs { .compute(starting_indexes.height, &window_starts, exit, |vec| { vec.compute_multiply( starting_indexes.height, - &prices.price.usd, + &prices.price.usd.height, &activity.coinblocks_stored.height, exit, )?; @@ -81,7 +81,7 @@ impl Vecs { .compute(starting_indexes.height, &window_starts, exit, |vec| { vec.compute_transform3( starting_indexes.height, - &prices.price.usd, + &prices.price.usd.height, &coindays_destroyed.height, circulating_supply, |(i, price, cdd, supply, _): (_, Dollars, StoredF64, Bitcoin, _)| { diff --git a/crates/brk_computer/src/distribution/compute/block_loop.rs b/crates/brk_computer/src/distribution/compute/block_loop.rs index d4a1e5873..73c697c73 100644 --- a/crates/brk_computer/src/distribution/compute/block_loop.rs +++ b/crates/brk_computer/src/distribution/compute/block_loop.rs @@ -78,7 +78,7 @@ pub(crate) fn process_blocks( let txindex_to_input_count = &indexes.txindex.input_count; // From price - use cents for computation: - let height_to_price = &prices.price.cents; + let height_to_price = &prices.price.cents.height; // Access pre-computed vectors from context for thread-safe access let height_to_price_vec = &ctx.height_to_price; diff --git a/crates/brk_computer/src/distribution/compute/context.rs b/crates/brk_computer/src/distribution/compute/context.rs index f3bcf81e0..1c3e5302c 100644 --- a/crates/brk_computer/src/distribution/compute/context.rs +++ b/crates/brk_computer/src/distribution/compute/context.rs @@ -125,7 +125,7 @@ impl ComputeContext { blocks.time.timestamp_monotonic.collect(); let height_to_price: Vec = - prices.price.cents.collect(); + prices.price.cents.height.collect(); // Build sparse table for O(1) range max queries on prices // Used for computing peak price during UTXO holding periods (peak regret) diff --git a/crates/brk_computer/src/distribution/metrics/realized/base.rs b/crates/brk_computer/src/distribution/metrics/realized/base.rs index ccc3937d7..7c0038d7a 100644 --- a/crates/brk_computer/src/distribution/metrics/realized/base.rs +++ b/crates/brk_computer/src/distribution/metrics/realized/base.rs @@ -855,14 +855,14 @@ impl RealizedBase { self.realized_price_extra.compute_ratio( starting_indexes, - &prices.price.usd, + &prices.price.usd.height, &self.realized_price.usd.height, exit, )?; self.investor_price_extra.compute_ratio( starting_indexes, - &prices.price.usd, + &prices.price.usd.height, &self.investor_price.usd.height, exit, )?; diff --git a/crates/brk_computer/src/distribution/metrics/unrealized/base.rs b/crates/brk_computer/src/distribution/metrics/unrealized/base.rs index ff719e474..9ff3a6bbe 100644 --- a/crates/brk_computer/src/distribution/metrics/unrealized/base.rs +++ b/crates/brk_computer/src/distribution/metrics/unrealized/base.rs @@ -390,7 +390,7 @@ impl UnrealizedBase { starting_indexes.height, &self.investor_cap_in_loss_raw, &self.invested_capital_in_loss_raw, - &prices.price.cents, + &prices.price.cents.height, |(h, investor_cap, invested_cap, spot, ..)| { if invested_cap.inner() == 0 { return (h, Dollars::ZERO); @@ -410,7 +410,7 @@ impl UnrealizedBase { starting_indexes.height, &self.investor_cap_in_profit_raw, &self.invested_capital_in_profit_raw, - &prices.price.cents, + &prices.price.cents.height, |(h, investor_cap, invested_cap, spot, ..)| { if invested_cap.inner() == 0 { return (h, Dollars::ZERO); diff --git a/crates/brk_computer/src/distribution/vecs.rs b/crates/brk_computer/src/distribution/vecs.rs index d7ea05b30..6eabb6c2d 100644 --- a/crates/brk_computer/src/distribution/vecs.rs +++ b/crates/brk_computer/src/distribution/vecs.rs @@ -249,7 +249,7 @@ impl Vecs { // Recover chain_state from stored values debug!("recovering chain_state from stored values"); let height_to_timestamp = &blocks.time.timestamp_monotonic; - let height_to_price = &prices.price.cents; + let height_to_price = &prices.price.cents.height; let end = usize::from(recovered_height); let timestamp_data: Vec<_> = height_to_timestamp.collect_range_at(0, end); diff --git a/crates/brk_computer/src/internal/multi/from_height/ratio/extended.rs b/crates/brk_computer/src/internal/multi/from_height/ratio/extended.rs index 24f2b525a..e0fcd8530 100644 --- a/crates/brk_computer/src/internal/multi/from_height/ratio/extended.rs +++ b/crates/brk_computer/src/internal/multi/from_height/ratio/extended.rs @@ -40,7 +40,7 @@ impl ComputedFromHeightRatioExtended { exit: &Exit, metric_price: &impl ReadableVec, ) -> Result<()> { - let close_price = &prices.price.usd; + let close_price = &prices.price.usd.height; self.base .compute_ratio(starting_indexes, close_price, metric_price, exit)?; self.extended diff --git a/crates/brk_computer/src/internal/multi/from_height/value_full.rs b/crates/brk_computer/src/internal/multi/from_height/value_full.rs index 8f486c1aa..9824472a9 100644 --- a/crates/brk_computer/src/internal/multi/from_height/value_full.rs +++ b/crates/brk_computer/src/internal/multi/from_height/value_full.rs @@ -56,7 +56,7 @@ impl ValueFromHeightFull { .compute_binary::( max_from, &self.base.sats.height, - &prices.price.usd, + &prices.price.usd.height, exit, )?; diff --git a/crates/brk_computer/src/internal/multi/from_height/value_last.rs b/crates/brk_computer/src/internal/multi/from_height/value_last.rs index 770ba2490..6fd6cddbc 100644 --- a/crates/brk_computer/src/internal/multi/from_height/value_last.rs +++ b/crates/brk_computer/src/internal/multi/from_height/value_last.rs @@ -41,7 +41,7 @@ impl ValueFromHeightLast { self.base.usd.compute_binary::( max_from, &self.base.sats.height, - &prices.price.usd, + &prices.price.usd.height, exit, )?; Ok(()) diff --git a/crates/brk_computer/src/internal/multi/from_height/value_lazy_computed_cumulative.rs b/crates/brk_computer/src/internal/multi/from_height/value_lazy_computed_cumulative.rs index a72cdcae8..b7e6161bf 100644 --- a/crates/brk_computer/src/internal/multi/from_height/value_lazy_computed_cumulative.rs +++ b/crates/brk_computer/src/internal/multi/from_height/value_lazy_computed_cumulative.rs @@ -48,7 +48,7 @@ impl LazyComputedValueFromHeightCumulative { .compute_binary::( max_from, &self.base.sats.height, - &prices.price.usd, + &prices.price.usd.height, exit, )?; diff --git a/crates/brk_computer/src/internal/multi/from_height/value_sum_cumulative.rs b/crates/brk_computer/src/internal/multi/from_height/value_sum_cumulative.rs index 55a2f78ba..d8c423ddf 100644 --- a/crates/brk_computer/src/internal/multi/from_height/value_sum_cumulative.rs +++ b/crates/brk_computer/src/internal/multi/from_height/value_sum_cumulative.rs @@ -55,7 +55,7 @@ impl ValueFromHeightSumCumulative { .compute_binary::( max_from, &self.base.sats.height, - &prices.price.usd, + &prices.price.usd.height, exit, )?; diff --git a/crates/brk_computer/src/internal/single/height/value.rs b/crates/brk_computer/src/internal/single/height/value.rs index 371660e22..34fdcd95d 100644 --- a/crates/brk_computer/src/internal/single/height/value.rs +++ b/crates/brk_computer/src/internal/single/height/value.rs @@ -51,7 +51,7 @@ impl ValueFromHeight { self.usd.compute_binary::( max_from, &self.sats, - &prices.price.usd, + &prices.price.usd.height, exit, )?; Ok(()) diff --git a/crates/brk_computer/src/market/ath/compute.rs b/crates/brk_computer/src/market/ath/compute.rs index 1e09c788b..8c3549d29 100644 --- a/crates/brk_computer/src/market/ath/compute.rs +++ b/crates/brk_computer/src/market/ath/compute.rs @@ -14,7 +14,7 @@ impl Vecs { ) -> Result<()> { self.price_ath.usd.height.compute_all_time_high( starting_indexes.height, - &prices.price.usd, + &prices.price.usd.height, exit, )?; @@ -22,7 +22,7 @@ impl Vecs { self.days_since_price_ath.height.compute_transform2( starting_indexes.height, &self.price_ath.usd.height, - &prices.price.usd, + &prices.price.usd.height, |(i, ath, price, slf)| { if prev.is_none() { let i = i.to_usize(); diff --git a/crates/brk_computer/src/market/dca/compute.rs b/crates/brk_computer/src/market/dca/compute.rs index 6b8b37bcb..8fae7bdcc 100644 --- a/crates/brk_computer/src/market/dca/compute.rs +++ b/crates/brk_computer/src/market/dca/compute.rs @@ -97,7 +97,7 @@ impl Vecs { { returns.compute_binary::( starting_indexes.height, - &prices.price.usd, + &prices.price.usd.height, &average_price.usd.height, exit, )?; @@ -165,7 +165,7 @@ impl Vecs { { returns.compute_binary::( starting_indexes.height, - &prices.price.usd, + &prices.price.usd.height, &lookback_price.usd.height, exit, )?; @@ -268,7 +268,7 @@ impl Vecs { returns.compute_binary::( starting_indexes.height, - &prices.price.usd, + &prices.price.usd.height, &average_price.usd.height, exit, )?; diff --git a/crates/brk_computer/src/market/indicators/compute.rs b/crates/brk_computer/src/market/indicators/compute.rs index 2c9c8aa50..1b00a9070 100644 --- a/crates/brk_computer/src/market/indicators/compute.rs +++ b/crates/brk_computer/src/market/indicators/compute.rs @@ -34,7 +34,7 @@ impl Vecs { // Stochastic Oscillator: K = (close - low_2w) / (high_2w - low_2w) * 100 { - let price = &prices.price.usd; + let price = &prices.price.usd.height; self.stoch_k.height.compute_transform3( starting_indexes.height, price, diff --git a/crates/brk_computer/src/market/indicators/macd.rs b/crates/brk_computer/src/market/indicators/macd.rs index e25a5abbf..6eaa7d708 100644 --- a/crates/brk_computer/src/market/indicators/macd.rs +++ b/crates/brk_computer/src/market/indicators/macd.rs @@ -15,7 +15,7 @@ pub(super) fn compute( starting_indexes: &ComputeIndexes, exit: &Exit, ) -> Result<()> { - let source_version = prices.price.usd.version(); + let source_version = prices.price.usd.height.version(); chain .line diff --git a/crates/brk_computer/src/market/lookback/compute.rs b/crates/brk_computer/src/market/lookback/compute.rs index 5415a326b..d8b1de659 100644 --- a/crates/brk_computer/src/market/lookback/compute.rs +++ b/crates/brk_computer/src/market/lookback/compute.rs @@ -13,7 +13,7 @@ impl Vecs { starting_indexes: &ComputeIndexes, exit: &Exit, ) -> Result<()> { - let close_data: Vec = prices.price.usd.collect(); + let close_data: Vec = prices.price.usd.height.collect(); for (price_ago, days) in self.price_ago.iter_mut_with_days() { let window_starts = blocks.count.start_vec(days as usize); diff --git a/crates/brk_computer/src/market/moving_average/compute.rs b/crates/brk_computer/src/market/moving_average/compute.rs index 22e225274..6abdbf6cd 100644 --- a/crates/brk_computer/src/market/moving_average/compute.rs +++ b/crates/brk_computer/src/market/moving_average/compute.rs @@ -14,7 +14,7 @@ impl Vecs { starting_indexes: &ComputeIndexes, exit: &Exit, ) -> Result<()> { - let close = &prices.price.usd; + let close = &prices.price.usd.height; for (sma, period) in [ (&mut self.price_1w_sma, 7), diff --git a/crates/brk_computer/src/market/range/compute.rs b/crates/brk_computer/src/market/range/compute.rs index 956325546..601ff9bd4 100644 --- a/crates/brk_computer/src/market/range/compute.rs +++ b/crates/brk_computer/src/market/range/compute.rs @@ -16,7 +16,7 @@ impl Vecs { starting_indexes: &ComputeIndexes, exit: &Exit, ) -> Result<()> { - let price = &prices.price.usd; + let price = &prices.price.usd.height; self.price_1w_min.usd.height.compute_rolling_min_from_starts( starting_indexes.height, diff --git a/crates/brk_computer/src/market/returns/compute.rs b/crates/brk_computer/src/market/returns/compute.rs index 70b5288e7..af35cb4a7 100644 --- a/crates/brk_computer/src/market/returns/compute.rs +++ b/crates/brk_computer/src/market/returns/compute.rs @@ -23,7 +23,7 @@ impl Vecs { { returns.compute_binary::( starting_indexes.height, - &prices.price.usd, + &prices.price.usd.height, &lookback_price.usd.height, exit, )?; diff --git a/crates/brk_computer/src/prices/by_unit.rs b/crates/brk_computer/src/prices/by_unit.rs index 8b1234682..a60044a4c 100644 --- a/crates/brk_computer/src/prices/by_unit.rs +++ b/crates/brk_computer/src/prices/by_unit.rs @@ -1,8 +1,11 @@ use brk_traversable::Traversable; -use brk_types::{Cents, Dollars, Height, OHLCCents, OHLCDollars, OHLCSats, Sats}; -use vecdb::{LazyVecFrom1, PcoVec, Rw, StorageMode}; +use brk_types::{Cents, Dollars, OHLCCents, OHLCDollars, OHLCSats, Sats}; +use vecdb::{Rw, StorageMode}; -use crate::internal::{ComputedHeightDerivedLast, EagerIndexes, LazyEagerIndexes}; +use crate::internal::{ + ComputedFromHeightLast, ComputedHeightDerivedLast, EagerIndexes, LazyEagerIndexes, + LazyFromHeightLast, +}; use super::ohlcs::{LazyOhlcVecs, OhlcVecs}; @@ -43,7 +46,7 @@ pub struct OhlcByUnit { #[derive(Traversable)] pub struct PriceByUnit { - pub cents: M::Stored>, - pub usd: LazyVecFrom1, - pub sats: LazyVecFrom1, + pub cents: ComputedFromHeightLast, + pub usd: LazyFromHeightLast, + pub sats: LazyFromHeightLast, } diff --git a/crates/brk_computer/src/prices/compute.rs b/crates/brk_computer/src/prices/compute.rs index 4e150118c..4f5d4d00e 100644 --- a/crates/brk_computer/src/prices/compute.rs +++ b/crates/brk_computer/src/prices/compute.rs @@ -22,15 +22,15 @@ impl Vecs { self.split .open .cents - .compute_first(starting_indexes, &self.price.cents, indexes, exit)?; + .compute_first(starting_indexes, &self.price.cents.height, indexes, exit)?; self.split .high .cents - .compute_max(starting_indexes, &self.price.cents, indexes, exit)?; + .compute_max(starting_indexes, &self.price.cents.height, indexes, exit)?; self.split .low .cents - .compute_min(starting_indexes, &self.price.cents, indexes, exit)?; + .compute_min(starting_indexes, &self.price.cents.height, indexes, exit)?; self.ohlc.cents.compute_from_split( starting_indexes, &self.split.open.cents, @@ -55,6 +55,7 @@ impl Vecs { indexer.vecs.outputs.value.version() + indexer.vecs.outputs.outputtype.version(); self.price .cents + .height .validate_computed_version_or_reset(source_version)?; let total_heights = indexer.vecs.blocks.timestamp.len(); @@ -64,27 +65,32 @@ impl Vecs { } // Reorg: truncate to starting_indexes - let truncate_to = self.price.cents.len().min(starting_indexes.height.to_usize()); - self.price.cents.truncate_if_needed_at(truncate_to)?; + let truncate_to = self + .price + .cents + .height + .len() + .min(starting_indexes.height.to_usize()); + self.price.cents.height.truncate_if_needed_at(truncate_to)?; - if self.price.cents.len() < START_HEIGHT { - for line in brk_oracle::PRICES.lines().skip(self.price.cents.len()) { - if self.price.cents.len() >= START_HEIGHT { + if self.price.cents.height.len() < START_HEIGHT { + for line in brk_oracle::PRICES.lines().skip(self.price.cents.height.len()) { + if self.price.cents.height.len() >= START_HEIGHT { break; } let dollars: f64 = line.parse().unwrap_or(0.0); let cents = (dollars * 100.0).round() as u64; - self.price.cents.push(Cents::new(cents)); + self.price.cents.height.push(Cents::new(cents)); } } - if self.price.cents.len() >= total_heights { + if self.price.cents.height.len() >= total_heights { return Ok(()); } let config = Config::default(); - let committed = self.price.cents.len(); - let prev_cents = self.price.cents.collect_one_at(committed - 1).unwrap(); + let committed = self.price.cents.height.len(); + let prev_cents = self.price.cents.height.collect_one_at(committed - 1).unwrap(); let seed_bin = cents_to_bin(prev_cents.inner() as f64); let warmup = config.window_size.min(committed - START_HEIGHT); let mut oracle = Oracle::from_checkpoint(seed_bin, config, |o| { @@ -100,7 +106,7 @@ impl Vecs { let ref_bins = Self::feed_blocks(&mut oracle, indexer, committed..total_heights); for (i, ref_bin) in ref_bins.into_iter().enumerate() { - self.price.cents.push(Cents::new(bin_to_cents(ref_bin))); + self.price.cents.height.push(Cents::new(bin_to_cents(ref_bin))); let progress = ((i + 1) * 100 / num_new) as u8; if i > 0 && progress > ((i * 100 / num_new) as u8) { @@ -110,10 +116,13 @@ impl Vecs { { let _lock = exit.lock(); - self.price.cents.write()?; + self.price.cents.height.write()?; } - info!("Oracle prices complete: {} committed", self.price.cents.len()); + info!( + "Oracle prices complete: {} committed", + self.price.cents.height.len() + ); Ok(()) } @@ -201,7 +210,12 @@ impl Vecs { pub fn live_oracle(&self, indexer: &Indexer) -> Result { let config = Config::default(); let height = indexer.vecs.blocks.timestamp.len(); - let last_cents = self.price.cents.collect_one_at(self.price.cents.len() - 1).unwrap(); + let last_cents = self + .price + .cents + .height + .collect_one_at(self.price.cents.height.len() - 1) + .unwrap(); let seed_bin = cents_to_bin(last_cents.inner() as f64); let window_size = config.window_size; let oracle = Oracle::from_checkpoint(seed_bin, config, |o| { diff --git a/crates/brk_computer/src/prices/mod.rs b/crates/brk_computer/src/prices/mod.rs index a05754127..63f9c03ff 100644 --- a/crates/brk_computer/src/prices/mod.rs +++ b/crates/brk_computer/src/prices/mod.rs @@ -6,16 +6,14 @@ use std::path::Path; use brk_traversable::Traversable; use brk_types::Version; -use vecdb::{ - Database, ImportableVec, LazyVecFrom1, PcoVec, ReadableCloneableVec, Rw, StorageMode, - PAGE_SIZE, -}; +use vecdb::{Database, ReadableCloneableVec, Rw, StorageMode, PAGE_SIZE}; use crate::{ indexes, internal::{ - CentsUnsignedToDollars, CentsUnsignedToSats, ComputedHeightDerivedLast, EagerIndexes, - LazyEagerIndexes, OhlcCentsToDollars, OhlcCentsToSats, + CentsUnsignedToDollars, CentsUnsignedToSats, ComputedFromHeightLast, + ComputedHeightDerivedLast, EagerIndexes, LazyEagerIndexes, LazyFromHeightLast, + OhlcCentsToDollars, OhlcCentsToSats, }, }; @@ -66,7 +64,8 @@ impl Vecs { // ── Cents (eager, stored) ─────────────────────────────────── - let price_cents = PcoVec::forced_import(db, "price_cents", version)?; + let price_cents = + ComputedFromHeightLast::forced_import(db, "price_cents", version, indexes)?; let open_cents = EagerIndexes::forced_import(db, "price_open_cents", version)?; let high_cents = EagerIndexes::forced_import(db, "price_high_cents", version)?; @@ -74,7 +73,7 @@ impl Vecs { let close_cents = ComputedHeightDerivedLast::forced_import( "price_close_cents", - price_cents.read_only_boxed_clone(), + price_cents.height.read_only_boxed_clone(), version, indexes, ); @@ -83,10 +82,11 @@ impl Vecs { // ── USD (lazy from cents) ─────────────────────────────────── - let price_usd = LazyVecFrom1::transformed::( + let price_usd = LazyFromHeightLast::from_computed::( "price", version, - price_cents.read_only_boxed_clone(), + price_cents.height.read_only_boxed_clone(), + &price_cents, ); let open_usd = LazyEagerIndexes::from_eager_indexes::( @@ -107,7 +107,7 @@ impl Vecs { let close_usd = ComputedHeightDerivedLast::forced_import( "price_close", - price_usd.read_only_boxed_clone(), + price_usd.height.read_only_boxed_clone(), version, indexes, ); @@ -120,10 +120,11 @@ impl Vecs { // ── Sats (lazy from cents, high↔low swapped) ─────────────── - let price_sats = LazyVecFrom1::transformed::( + let price_sats = LazyFromHeightLast::from_computed::( "price_sats", version, - price_cents.read_only_boxed_clone(), + price_cents.height.read_only_boxed_clone(), + &price_cents, ); let open_sats = LazyEagerIndexes::from_eager_indexes::( @@ -145,7 +146,7 @@ impl Vecs { let close_sats = ComputedHeightDerivedLast::forced_import( "price_close_sats", - price_sats.read_only_boxed_clone(), + price_sats.height.read_only_boxed_clone(), version, indexes, ); diff --git a/crates/brk_computer/src/prices/ohlcs.rs b/crates/brk_computer/src/prices/ohlcs.rs index 3d4f442bd..08c78b673 100644 --- a/crates/brk_computer/src/prices/ohlcs.rs +++ b/crates/brk_computer/src/prices/ohlcs.rs @@ -10,7 +10,7 @@ use schemars::JsonSchema; use serde::Serialize; use vecdb::{ BytesVec, BytesVecValue, Database, EagerVec, Exit, Formattable, ImportableVec, LazyVecFrom1, - ReadableCloneableVec, Rw, StorageMode, UnaryTransform, + ReadableCloneableVec, ReadableVec, Rw, StorageMode, UnaryTransform, }; use crate::{ @@ -47,7 +47,7 @@ pub struct OhlcVecs( where T: BytesVecValue + Formattable + Serialize + JsonSchema; -const EAGER_VERSION: Version = Version::ZERO; +const EAGER_VERSION: Version = Version::ONE; impl OhlcVecs where @@ -84,14 +84,22 @@ impl OhlcVecs { &high.$field, &low.$field, &close.$field, - |(idx, o, h, l, c, _)| { + |(idx, o, h, l, c, this)| { ( idx, - OHLCCents { - open: Open::new(o), - high: High::new(h), - low: Low::new(l), - close: Close::new(c.unwrap_or_default()), + if let Some(c) = c { + OHLCCents { + open: Open::new(o), + high: High::new(h), + low: Low::new(l), + close: Close::new(c), + } + } else { + // Empty period (no blocks): flat candle at previous close + let prev_close = Close::new( + this.collect_last().map_or(o, |prev| *prev.close), + ); + OHLCCents::from(prev_close) }, ) }, diff --git a/crates/brk_types/src/index.rs b/crates/brk_types/src/index.rs index dcd3d4fa1..6d8fb9bf6 100644 --- a/crates/brk_types/src/index.rs +++ b/crates/brk_types/src/index.rs @@ -17,7 +17,7 @@ use super::{ timestamp::INDEX_EPOCH, minute1::MINUTE1_INTERVAL, minute5::MINUTE5_INTERVAL, minute10::MINUTE10_INTERVAL, minute30::MINUTE30_INTERVAL, hour1::HOUR1_INTERVAL, hour4::HOUR4_INTERVAL, - hour12::HOUR12_INTERVAL, day3::DAY3_INTERVAL, + hour12::HOUR12_INTERVAL, }; /// Aggregation dimension for querying metrics. Includes time-based (date, week, month, year), diff --git a/modules/brk-client/index.js b/modules/brk-client/index.js index a814808bc..f739d2983 100644 --- a/modules/brk-client/index.js +++ b/modules/brk-client/index.js @@ -5235,9 +5235,9 @@ function createRatioPattern2(client, acc) { /** * @typedef {Object} MetricsTree_Prices_Price - * @property {MetricPattern20} cents - * @property {MetricPattern20} usd - * @property {MetricPattern20} sats + * @property {MetricPattern1} cents + * @property {MetricPattern1} usd + * @property {MetricPattern1} sats */ /** @@ -7505,9 +7505,9 @@ class BrkClient extends BrkClientBase { sats: createMetricPattern2(this, 'price_ohlc_sats'), }, price: { - cents: createMetricPattern20(this, 'price_cents'), - usd: createMetricPattern20(this, 'price'), - sats: createMetricPattern20(this, 'price_sats'), + cents: createMetricPattern1(this, 'price_cents'), + usd: createMetricPattern1(this, 'price'), + sats: createMetricPattern1(this, 'price_sats'), }, }, distribution: { diff --git a/packages/brk_client/brk_client/__init__.py b/packages/brk_client/brk_client/__init__.py index 90bc79e7d..e3710cde9 100644 --- a/packages/brk_client/brk_client/__init__.py +++ b/packages/brk_client/brk_client/__init__.py @@ -4526,9 +4526,9 @@ class MetricsTree_Prices_Price: """Metrics tree node.""" def __init__(self, client: BrkClientBase, base_path: str = ''): - self.cents: MetricPattern20[Cents] = MetricPattern20(client, 'price_cents') - self.usd: MetricPattern20[Dollars] = MetricPattern20(client, 'price') - self.sats: MetricPattern20[Sats] = MetricPattern20(client, 'price_sats') + self.cents: MetricPattern1[Cents] = MetricPattern1(client, 'price_cents') + self.usd: MetricPattern1[Dollars] = MetricPattern1(client, 'price') + self.sats: MetricPattern1[Sats] = MetricPattern1(client, 'price_sats') class MetricsTree_Prices: """Metrics tree node.""" diff --git a/website/scripts/chart/index.js b/website/scripts/chart/index.js index c62fd6e69..da1d84ab3 100644 --- a/website/scripts/chart/index.js +++ b/website/scripts/chart/index.js @@ -1383,6 +1383,25 @@ export function createChart({ parent, brk, fitContent }) { serieses.addCandlestick({ ...common, colors: blueprint.colors }), ); break; + case "Price": + if (idx === "height" || idx.startsWith("minute")) { + pane.series.push( + serieses.addLine({ + ...common, + color: colors.default, + options: { ...common.options, priceLineVisible: true }, + }), + ); + } else { + pane.series.push( + serieses.addCandlestick({ + ...common, + metric: blueprint.ohlcMetric, + colors: blueprint.colors, + }), + ); + } + break; case "Dots": pane.series.push( serieses.addDots({ diff --git a/website/scripts/options/market.js b/website/scripts/options/market.js index 41382617f..cd8cd576b 100644 --- a/website/scripts/options/market.js +++ b/website/scripts/options/market.js @@ -370,7 +370,7 @@ export function createMarketSection() { title: "Sats per Dollar", bottom: [ line({ - metric: prices.split.close.sats, + metric: prices.price.sats, name: "Sats/$", unit: Unit.sats, }), diff --git a/website/scripts/options/types.js b/website/scripts/options/types.js index 94e0b97fc..40d917c1d 100644 --- a/website/scripts/options/types.js +++ b/website/scripts/options/types.js @@ -42,7 +42,14 @@ * @property {BaselineSeriesPartialOptions} [options] * @typedef {BaseSeriesBlueprint & DotsBaselineSeriesBlueprintSpecific} DotsBaselineSeriesBlueprint * - * @typedef {BaselineSeriesBlueprint | CandlestickSeriesBlueprint | LineSeriesBlueprint | HistogramSeriesBlueprint | DotsSeriesBlueprint | DotsBaselineSeriesBlueprint} AnySeriesBlueprint + * @typedef {Object} PriceSeriesBlueprintSpecific + * @property {"Price"} type + * @property {AnyMetricPattern} ohlcMetric - OHLC metric for candlestick (>= 1h indexes) + * @property {[Color, Color]} [colors] + * @property {CandlestickSeriesPartialOptions} [options] + * @typedef {BaseSeriesBlueprint & PriceSeriesBlueprintSpecific} PriceSeriesBlueprint + * + * @typedef {BaselineSeriesBlueprint | CandlestickSeriesBlueprint | LineSeriesBlueprint | HistogramSeriesBlueprint | DotsSeriesBlueprint | DotsBaselineSeriesBlueprint | PriceSeriesBlueprint} AnySeriesBlueprint * * @typedef {AnySeriesBlueprint["type"]} SeriesType * diff --git a/website/scripts/panes/chart.js b/website/scripts/panes/chart.js index 8ad1fd2e1..7a3ba876c 100644 --- a/website/scripts/panes/chart.js +++ b/website/scripts/panes/chart.js @@ -40,24 +40,28 @@ export function init() { /** @type {Map} */ const result = new Map(); - // USD price + option blueprints - /** @type {FetchedCandlestickSeriesBlueprint} */ - const usdPrice = { - type: "Candlestick", - title: "Price", - metric: brk.metrics.prices.ohlc.usd, - }; - result.set(Unit.usd, [usdPrice, ...(optionTop.get(Unit.usd) ?? [])]); + const { ohlc, price } = brk.metrics.prices; - // Sats price + option blueprints - /** @type {FetchedCandlestickSeriesBlueprint} */ - const satsPrice = { - type: "Candlestick", - title: "Price", - metric: brk.metrics.prices.ohlc.sats, - colors: /** @type {const} */ ([colors.bi.p1[1], colors.bi.p1[0]]), - }; - result.set(Unit.sats, [satsPrice, ...(optionTop.get(Unit.sats) ?? [])]); + result.set(Unit.usd, [ + /** @type {AnyFetchedSeriesBlueprint} */ ({ + type: "Price", + title: "Price", + metric: price.usd, + ohlcMetric: ohlc.usd, + }), + ...(optionTop.get(Unit.usd) ?? []), + ]); + + result.set(Unit.sats, [ + /** @type {AnyFetchedSeriesBlueprint} */ ({ + type: "Price", + title: "Price", + metric: price.sats, + ohlcMetric: ohlc.sats, + colors: /** @type {const} */ ([colors.bi.p1[1], colors.bi.p1[0]]), + }), + ...(optionTop.get(Unit.sats) ?? []), + ]); return result; } @@ -70,9 +74,7 @@ export function init() { const unit = chart.panes[0].unit; if (!priceSeries?.hasData() || !unit) return; - const last = /** @type {CandlestickData | undefined} */ ( - priceSeries.getData().at(-1) - ); + const last = priceSeries.getData().at(-1); if (!last) return; // Convert to sats if needed @@ -81,7 +83,13 @@ export function init() { ? Math.floor(ONE_BTC_IN_SATS / latest) : latest; - priceSeries.update({ ...last, close }); + if ("close" in last) { + // Candlestick data + priceSeries.update({ ...last, close }); + } else { + // Line data + priceSeries.update({ ...last, value: close }); + } } // Set up the setOption function