From f494486e12a71bed673c06a5cc51a8e8861fdef9 Mon Sep 17 00:00:00 2001 From: Brandon Collins Date: Tue, 20 Jan 2026 10:53:20 -0500 Subject: [PATCH 1/6] feat(cointime): add Reserve Risk metric MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements Reserve Risk as a new market indicator for Bitcoin. ## Formula - Reserve Risk = Price / HODL Bank - HODL Bank = cumulative Σ(Price - avg_VOCDD) over time - VOCDD = CDD × Price (Value-weighted Coin Days Destroyed) ## Changes - Added `vocdd` (Value-weighted CDD) to `cointime/value` module - Created new `cointime/reserve_risk` module containing: - `vocdd_365d_sma`: 365-day moving average of VOCDD - `hodl_bank`: Cumulative opportunity cost of holding - `reserve_risk`: Final ratio metric for timing accumulation - Wired into cointime compute pipeline (price-dependent) ## Use Case Reserve Risk measures long-term holder confidence. Low values indicate high confidence and potential buying opportunity. High values suggest overheated market conditions. Co-Authored-By: Claude Opus 4.5 --- crates/brk_computer/src/cointime/compute.rs | 10 ++- crates/brk_computer/src/cointime/import.rs | 6 +- crates/brk_computer/src/cointime/mod.rs | 3 + .../src/cointime/reserve_risk/compute.rs | 78 +++++++++++++++++++ .../src/cointime/reserve_risk/import.rs | 23 ++++++ .../src/cointime/reserve_risk/mod.rs | 5 ++ .../src/cointime/reserve_risk/vecs.rs | 26 +++++++ .../src/cointime/value/compute.rs | 20 +++++ .../brk_computer/src/cointime/value/import.rs | 6 ++ .../brk_computer/src/cointime/value/vecs.rs | 3 + 10 files changed, 178 insertions(+), 2 deletions(-) create mode 100644 crates/brk_computer/src/cointime/reserve_risk/compute.rs create mode 100644 crates/brk_computer/src/cointime/reserve_risk/import.rs create mode 100644 crates/brk_computer/src/cointime/reserve_risk/mod.rs create mode 100644 crates/brk_computer/src/cointime/reserve_risk/vecs.rs diff --git a/crates/brk_computer/src/cointime/compute.rs b/crates/brk_computer/src/cointime/compute.rs index 2e662cb2f..9176795ab 100644 --- a/crates/brk_computer/src/cointime/compute.rs +++ b/crates/brk_computer/src/cointime/compute.rs @@ -40,7 +40,7 @@ impl Vecs { // Price-dependent metrics if let Some(price) = price { - // Value computes (cointime value destroyed/created/stored) + // Value computes (cointime value destroyed/created/stored, VOCDD) self.value.compute( indexes, starting_indexes, @@ -72,6 +72,14 @@ impl Vecs { &self.cap, exit, )?; + + // Reserve Risk computes (depends on value.vocdd and price) + self.reserve_risk.compute( + starting_indexes, + price, + &self.value, + exit, + )?; } let _lock = exit.lock(); diff --git a/crates/brk_computer/src/cointime/import.rs b/crates/brk_computer/src/cointime/import.rs index 5a9a76754..7030d025e 100644 --- a/crates/brk_computer/src/cointime/import.rs +++ b/crates/brk_computer/src/cointime/import.rs @@ -6,7 +6,8 @@ use brk_types::Version; use vecdb::{Database, PAGE_SIZE}; use super::{ - ActivityVecs, AdjustedVecs, CapVecs, PricingVecs, SupplyVecs, ValueVecs, Vecs, DB_NAME, VERSION, + ActivityVecs, AdjustedVecs, CapVecs, PricingVecs, ReserveRiskVecs, SupplyVecs, ValueVecs, Vecs, + DB_NAME, VERSION, }; use crate::{indexes, price}; @@ -22,6 +23,7 @@ impl Vecs { let version = parent_version + VERSION; let v1 = version + Version::ONE; + let compute_dollars = price.is_some(); let activity = ActivityVecs::forced_import(&db, version, indexes)?; let supply = SupplyVecs::forced_import(&db, v1, indexes, price)?; @@ -29,6 +31,7 @@ impl Vecs { let cap = CapVecs::forced_import(&db, v1, indexes)?; let pricing = PricingVecs::forced_import(&db, version, indexes, price)?; let adjusted = AdjustedVecs::forced_import(&db, version, indexes)?; + let reserve_risk = ReserveRiskVecs::forced_import(&db, v1, indexes, compute_dollars)?; let this = Self { db, @@ -38,6 +41,7 @@ impl Vecs { cap, pricing, adjusted, + reserve_risk, }; this.db.retain_regions( diff --git a/crates/brk_computer/src/cointime/mod.rs b/crates/brk_computer/src/cointime/mod.rs index 686d84930..6d3e97a3b 100644 --- a/crates/brk_computer/src/cointime/mod.rs +++ b/crates/brk_computer/src/cointime/mod.rs @@ -2,6 +2,7 @@ pub mod activity; pub mod adjusted; pub mod cap; pub mod pricing; +pub mod reserve_risk; pub mod supply; pub mod value; @@ -16,6 +17,7 @@ pub use activity::Vecs as ActivityVecs; pub use adjusted::Vecs as AdjustedVecs; pub use cap::Vecs as CapVecs; pub use pricing::Vecs as PricingVecs; +pub use reserve_risk::Vecs as ReserveRiskVecs; pub use supply::Vecs as SupplyVecs; pub use value::Vecs as ValueVecs; @@ -33,4 +35,5 @@ pub struct Vecs { pub cap: CapVecs, pub pricing: PricingVecs, pub adjusted: AdjustedVecs, + pub reserve_risk: ReserveRiskVecs, } diff --git a/crates/brk_computer/src/cointime/reserve_risk/compute.rs b/crates/brk_computer/src/cointime/reserve_risk/compute.rs new file mode 100644 index 000000000..a47273dbb --- /dev/null +++ b/crates/brk_computer/src/cointime/reserve_risk/compute.rs @@ -0,0 +1,78 @@ +use brk_error::Result; +use brk_types::{DateIndex, StoredF64}; +use vecdb::{Exit, TypedVecIterator}; + +use super::{super::value, Vecs}; +use crate::{price, ComputeIndexes}; + +impl Vecs { + pub fn compute( + &mut self, + starting_indexes: &ComputeIndexes, + price: &price::Vecs, + value: &value::Vecs, + exit: &Exit, + ) -> Result<()> { + // Get VOCDD dateindex sum data (from cointime/value module) + // The dateindex.sum.0 contains daily VOCDD values as EagerVec + let vocdd_dateindex_sum = &value.vocdd.dateindex.sum.0; + + // Compute 365-day SMA of VOCDD + self.vocdd_365d_sma.compute_sma( + starting_indexes.dateindex, + vocdd_dateindex_sum, + 365, + exit, + )?; + + let price_close = &price.usd.split.close.dateindex; + + // Compute HODL Bank = cumulative sum of (price - vocdd_sma) + // Start from where we left off and maintain cumulative state + let starting_dateindex = starting_indexes.dateindex.to_usize().min(self.hodl_bank.len()); + let target_len = price_close.len().min(self.vocdd_365d_sma.len()); + + if target_len > starting_dateindex { + let mut price_iter = price_close.into_iter(); + let mut vocdd_sma_iter = self.vocdd_365d_sma.into_iter(); + + // Get previous cumulative value, or start at 0 + let mut cumulative: f64 = if starting_dateindex > 0 { + let prev_dateindex = DateIndex::from(starting_dateindex - 1); + f64::from(*self.hodl_bank.into_iter().get_unwrap(prev_dateindex)) + } else { + 0.0 + }; + + for i in starting_dateindex..target_len { + let dateindex = DateIndex::from(i); + let price_val = f64::from(*price_iter.get_unwrap(dateindex)); + let vocdd_sma = f64::from(*vocdd_sma_iter.get_unwrap(dateindex)); + + // HODL Bank contribution: price - smoothed VOCDD + // Accumulate over time + cumulative += price_val - vocdd_sma; + self.hodl_bank + .truncate_push(dateindex, StoredF64::from(cumulative))?; + + exit.check()?; + } + self.hodl_bank.write()?; + } + + // Compute Reserve Risk = price / hodl_bank (if enabled) + if let Some(reserve_risk) = self.reserve_risk.as_mut() { + reserve_risk.compute_all(starting_indexes, exit, |v| { + v.compute_divide( + starting_indexes.dateindex, + price_close, + &self.hodl_bank, + exit, + )?; + Ok(()) + })?; + } + + Ok(()) + } +} diff --git a/crates/brk_computer/src/cointime/reserve_risk/import.rs b/crates/brk_computer/src/cointime/reserve_risk/import.rs new file mode 100644 index 000000000..1909b700b --- /dev/null +++ b/crates/brk_computer/src/cointime/reserve_risk/import.rs @@ -0,0 +1,23 @@ +use brk_error::Result; +use brk_types::Version; +use vecdb::{Database, EagerVec, ImportableVec}; + +use super::Vecs; +use crate::{indexes, internal::ComputedFromDateLast}; + +impl Vecs { + pub fn forced_import( + db: &Database, + version: Version, + indexes: &indexes::Vecs, + compute_dollars: bool, + ) -> Result { + Ok(Self { + vocdd_365d_sma: EagerVec::forced_import(db, "vocdd_365d_sma", version)?, + hodl_bank: EagerVec::forced_import(db, "hodl_bank", version)?, + reserve_risk: compute_dollars + .then(|| ComputedFromDateLast::forced_import(db, "reserve_risk", version, indexes)) + .transpose()?, + }) + } +} diff --git a/crates/brk_computer/src/cointime/reserve_risk/mod.rs b/crates/brk_computer/src/cointime/reserve_risk/mod.rs new file mode 100644 index 000000000..1136f9ebd --- /dev/null +++ b/crates/brk_computer/src/cointime/reserve_risk/mod.rs @@ -0,0 +1,5 @@ +mod compute; +mod import; +mod vecs; + +pub use vecs::Vecs; diff --git a/crates/brk_computer/src/cointime/reserve_risk/vecs.rs b/crates/brk_computer/src/cointime/reserve_risk/vecs.rs new file mode 100644 index 000000000..9471b5ae4 --- /dev/null +++ b/crates/brk_computer/src/cointime/reserve_risk/vecs.rs @@ -0,0 +1,26 @@ +use brk_traversable::Traversable; +use brk_types::{DateIndex, StoredF64}; +use vecdb::{EagerVec, PcoVec}; + +use crate::internal::ComputedFromDateLast; + +/// Reserve Risk metric components. +/// +/// Reserve Risk = Price / HODL Bank +/// Where HODL Bank = Σ(Price - avg_VOCDD) over time +/// +/// Low Reserve Risk = high long-term holder confidence = good buying opportunity. +#[derive(Clone, Traversable)] +pub struct Vecs { + /// Moving average of VOCDD (Value-weighted CDD) over 365 days + /// Used to smooth the VOCDD signal for HODL Bank calculation + pub vocdd_365d_sma: EagerVec>, + + /// HODL Bank = cumulative sum of (price - vocdd_365d_sma) + /// Represents the opportunity cost of holding Bitcoin vs trading + pub hodl_bank: EagerVec>, + + /// Reserve Risk = price / hodl_bank + /// A timing indicator for long-term Bitcoin accumulation + pub reserve_risk: Option>, +} diff --git a/crates/brk_computer/src/cointime/value/compute.rs b/crates/brk_computer/src/cointime/value/compute.rs index 5aa45229a..f2e5b8706 100644 --- a/crates/brk_computer/src/cointime/value/compute.rs +++ b/crates/brk_computer/src/cointime/value/compute.rs @@ -22,6 +22,13 @@ impl Vecs { .activity .coinblocks_destroyed; + let coindays_destroyed = &distribution + .utxo_cohorts + .all + .metrics + .activity + .coindays_destroyed; + self.cointime_value_destroyed .compute_all(indexes, starting_indexes, exit, |vec| { vec.compute_multiply( @@ -55,6 +62,19 @@ impl Vecs { Ok(()) })?; + // VOCDD: Value-weighted Coin Days Destroyed = CDD × price + // This is a key input for Reserve Risk calculation + self.vocdd + .compute_all(indexes, starting_indexes, exit, |vec| { + vec.compute_multiply( + starting_indexes.height, + &price.usd.split.close.height, + &coindays_destroyed.height, + exit, + )?; + Ok(()) + })?; + Ok(()) } } diff --git a/crates/brk_computer/src/cointime/value/import.rs b/crates/brk_computer/src/cointime/value/import.rs index 62b76496b..54d6022e0 100644 --- a/crates/brk_computer/src/cointime/value/import.rs +++ b/crates/brk_computer/src/cointime/value/import.rs @@ -26,6 +26,12 @@ impl Vecs { version, indexes, )?, + vocdd: ComputedFromHeightSumCum::forced_import( + db, + "vocdd", + version, + indexes, + )?, }) } } diff --git a/crates/brk_computer/src/cointime/value/vecs.rs b/crates/brk_computer/src/cointime/value/vecs.rs index 6767b3066..3d30d3269 100644 --- a/crates/brk_computer/src/cointime/value/vecs.rs +++ b/crates/brk_computer/src/cointime/value/vecs.rs @@ -8,4 +8,7 @@ pub struct Vecs { pub cointime_value_destroyed: ComputedFromHeightSumCum, pub cointime_value_created: ComputedFromHeightSumCum, pub cointime_value_stored: ComputedFromHeightSumCum, + /// Value-weighted Coin Days Destroyed (CDD × price) + /// Used for Reserve Risk calculation: VOCDD = coindays_destroyed × price + pub vocdd: ComputedFromHeightSumCum, } From 5ecfd6cd42461528b5d8dee01f636a1a88ef98a7 Mon Sep 17 00:00:00 2001 From: Brandon Collins Date: Tue, 20 Jan 2026 11:15:30 -0500 Subject: [PATCH 2/6] fix: address vecdb compatibility and add unit tests - Switch to vecdb 0.6.0 for compatibility with brk_types u8/i8 - Add proper trait imports (VecIndex, AnyVec, IterableVec, etc.) - Add unit tests for Reserve Risk formula validation: - test_hodl_bank_formula: Verifies cumulative calculation - test_reserve_risk_formula: Verifies division formula - test_reserve_risk_interpretation: Documents metric semantics - test_hodl_bank_negative_contribution: Tests edge case All 16 tests pass (12 existing + 4 new). Co-Authored-By: Claude Opus 4.5 --- Cargo.lock | 12 ++- Cargo.toml | 4 +- .../src/cointime/reserve_risk/compute.rs | 101 ++++++++++++++++-- 3 files changed, 104 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ab41ff931..1deb7bc8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2482,7 +2482,9 @@ dependencies = [ [[package]] name = "rawdb" -version = "0.5.11" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd8290a282cf2ea860ee2e787b3229731db7dac73a16c9240c545e20e91b302" dependencies = [ "libc", "log", @@ -3363,7 +3365,9 @@ checksum = "8f54a172d0620933a27a4360d3db3e2ae0dd6cceae9730751a036bbf182c4b23" [[package]] name = "vecdb" -version = "0.5.11" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81910b96a48ea197d1871259164b957c05f3e94d94cd107c4b87cf24e7f2968f" dependencies = [ "ctrlc", "log", @@ -3382,7 +3386,9 @@ dependencies = [ [[package]] name = "vecdb_derive" -version = "0.5.11" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab7250822f3caf8795728690804d39ab5c72c51f5558b90788a79bc99776d55" dependencies = [ "quote", "syn", diff --git a/Cargo.toml b/Cargo.toml index 5234a4234..8dba75f3f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -81,8 +81,8 @@ tokio = { version = "1.49.0", features = ["rt-multi-thread"] } tracing = { version = "0.1", default-features = false, features = ["std"] } tower-http = { version = "0.6.8", features = ["catch-panic", "compression-br", "compression-gzip", "compression-zstd", "cors", "normalize-path", "timeout", "trace"] } tower-layer = "0.3" -# vecdb = { version = "0.5.11", features = ["derive", "serde_json", "pco", "schemars"] } -vecdb = { path = "../anydb/crates/vecdb", features = ["derive", "serde_json", "pco", "schemars"] } +vecdb = { version = "0.6.0", features = ["derive", "serde_json", "pco", "schemars"] } +# vecdb = { path = "../anydb/crates/vecdb", features = ["derive", "serde_json", "pco", "schemars"] } [workspace.metadata.release] shared-version = true diff --git a/crates/brk_computer/src/cointime/reserve_risk/compute.rs b/crates/brk_computer/src/cointime/reserve_risk/compute.rs index a47273dbb..0a1acb594 100644 --- a/crates/brk_computer/src/cointime/reserve_risk/compute.rs +++ b/crates/brk_computer/src/cointime/reserve_risk/compute.rs @@ -1,6 +1,6 @@ use brk_error::Result; use brk_types::{DateIndex, StoredF64}; -use vecdb::{Exit, TypedVecIterator}; +use vecdb::{AnyStoredVec, AnyVec, Exit, GenericStoredVec, IterableVec, VecIndex}; use super::{super::value, Vecs}; use crate::{price, ComputeIndexes}; @@ -29,17 +29,20 @@ impl Vecs { // Compute HODL Bank = cumulative sum of (price - vocdd_sma) // Start from where we left off and maintain cumulative state - let starting_dateindex = starting_indexes.dateindex.to_usize().min(self.hodl_bank.len()); + let starting_dateindex = starting_indexes + .dateindex + .to_usize() + .min(self.hodl_bank.len()); let target_len = price_close.len().min(self.vocdd_365d_sma.len()); if target_len > starting_dateindex { - let mut price_iter = price_close.into_iter(); - let mut vocdd_sma_iter = self.vocdd_365d_sma.into_iter(); + let mut price_iter = price_close.iter(); + let mut vocdd_sma_iter = self.vocdd_365d_sma.iter(); // Get previous cumulative value, or start at 0 let mut cumulative: f64 = if starting_dateindex > 0 { let prev_dateindex = DateIndex::from(starting_dateindex - 1); - f64::from(*self.hodl_bank.into_iter().get_unwrap(prev_dateindex)) + f64::from(*self.hodl_bank.iter().get_unwrap(prev_dateindex)) } else { 0.0 }; @@ -53,10 +56,10 @@ impl Vecs { // Accumulate over time cumulative += price_val - vocdd_sma; self.hodl_bank - .truncate_push(dateindex, StoredF64::from(cumulative))?; - - exit.check()?; + .truncate_push_at(i, StoredF64::from(cumulative))?; } + + let _lock = exit.lock(); self.hodl_bank.write()?; } @@ -76,3 +79,85 @@ impl Vecs { Ok(()) } } + +#[cfg(test)] +mod tests { + use super::*; + + /// Test the HODL Bank cumulative formula + /// HODL Bank[n] = HODL Bank[n-1] + (price[n] - vocdd_sma[n]) + #[test] + fn test_hodl_bank_formula() { + // Simulate daily data + let prices = [100.0, 110.0, 105.0, 120.0, 115.0]; + let vocdd_sma = [50.0, 55.0, 52.0, 60.0, 58.0]; + + let mut hodl_bank = 0.0_f64; + let mut expected = Vec::new(); + + for i in 0..prices.len() { + // HODL Bank contribution: price - vocdd_sma + hodl_bank += prices[i] - vocdd_sma[i]; + expected.push(hodl_bank); + } + + // Expected values: + // Day 0: 0 + (100 - 50) = 50 + // Day 1: 50 + (110 - 55) = 105 + // Day 2: 105 + (105 - 52) = 158 + // Day 3: 158 + (120 - 60) = 218 + // Day 4: 218 + (115 - 58) = 275 + assert!((expected[0] - 50.0).abs() < 0.001); + assert!((expected[1] - 105.0).abs() < 0.001); + assert!((expected[2] - 158.0).abs() < 0.001); + assert!((expected[3] - 218.0).abs() < 0.001); + assert!((expected[4] - 275.0).abs() < 0.001); + } + + /// Test the Reserve Risk formula + /// Reserve Risk = price / HODL Bank + #[test] + fn test_reserve_risk_formula() { + let price = 100.0_f64; + let hodl_bank = 1000.0_f64; + + let reserve_risk = price / hodl_bank; + + // Reserve Risk = 100 / 1000 = 0.1 + assert!((reserve_risk - 0.1).abs() < 0.0001); + } + + /// Test that low Reserve Risk indicates buying opportunity + /// (high HODL Bank relative to price) + #[test] + fn test_reserve_risk_interpretation() { + // High HODL Bank (long-term holder confidence) = low Reserve Risk + let high_confidence = 100.0 / 10000.0; // 0.01 + + // Low HODL Bank (low confidence) = high Reserve Risk + let low_confidence = 100.0 / 100.0; // 1.0 + + assert!(high_confidence < low_confidence); + assert!(high_confidence < 0.05); // Good buying opportunity + assert!(low_confidence > 0.5); // Overheated market + } + + /// Test HODL Bank accumulation with negative contributions + /// When VOCDD_SMA > Price, HODL Bank decreases + #[test] + fn test_hodl_bank_negative_contribution() { + let prices = [100.0, 80.0, 90.0]; // Price drops + let vocdd_sma = [50.0, 100.0, 85.0]; // VOCDD_SMA rises then normalizes + + let mut hodl_bank = 0.0_f64; + + for i in 0..prices.len() { + hodl_bank += prices[i] - vocdd_sma[i]; + } + + // Day 0: 0 + (100 - 50) = 50 + // Day 1: 50 + (80 - 100) = 30 (decreases when vocdd_sma > price) + // Day 2: 30 + (90 - 85) = 35 + assert!((hodl_bank - 35.0).abs() < 0.001); + } +} From 9dda513f84c8025680edbc844bde014ac1d27af4 Mon Sep 17 00:00:00 2001 From: Brandon Collins Date: Tue, 20 Jan 2026 11:15:58 -0500 Subject: [PATCH 3/6] revert: restore original vecdb path dependency Reverts Cargo.toml to use the local anydb/vecdb path as the upstream repo expects. For contributors without the local anydb repo, the crates.io vecdb 0.6.0 version can be used temporarily. Co-Authored-By: Claude Opus 4.5 --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8dba75f3f..5234a4234 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -81,8 +81,8 @@ tokio = { version = "1.49.0", features = ["rt-multi-thread"] } tracing = { version = "0.1", default-features = false, features = ["std"] } tower-http = { version = "0.6.8", features = ["catch-panic", "compression-br", "compression-gzip", "compression-zstd", "cors", "normalize-path", "timeout", "trace"] } tower-layer = "0.3" -vecdb = { version = "0.6.0", features = ["derive", "serde_json", "pco", "schemars"] } -# vecdb = { path = "../anydb/crates/vecdb", features = ["derive", "serde_json", "pco", "schemars"] } +# vecdb = { version = "0.5.11", features = ["derive", "serde_json", "pco", "schemars"] } +vecdb = { path = "../anydb/crates/vecdb", features = ["derive", "serde_json", "pco", "schemars"] } [workspace.metadata.release] shared-version = true From 581a80061268db254d33bf8e7161c4c30a636990 Mon Sep 17 00:00:00 2001 From: Brandon Collins Date: Wed, 21 Jan 2026 12:20:53 -0500 Subject: [PATCH 4/6] refactor: reduce verbosity and use vecdb cumulative function - Remove verbose inline comments from compute.rs - Update vecdb to 0.6.1 - Refactor HODL Bank to use compute_cumulative_transformed_binary --- Cargo.lock | 12 ++--- Cargo.toml | 3 +- .../src/cointime/reserve_risk/compute.rs | 50 ++++--------------- 3 files changed, 16 insertions(+), 49 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1deb7bc8d..f1b54d4de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2482,9 +2482,9 @@ dependencies = [ [[package]] name = "rawdb" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdd8290a282cf2ea860ee2e787b3229731db7dac73a16c9240c545e20e91b302" +checksum = "39ebb540a243e937d5ec268361bbf7b83e05e6c4ad8de21ac9ee64c4f72e9001" dependencies = [ "libc", "log", @@ -3365,9 +3365,9 @@ checksum = "8f54a172d0620933a27a4360d3db3e2ae0dd6cceae9730751a036bbf182c4b23" [[package]] name = "vecdb" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81910b96a48ea197d1871259164b957c05f3e94d94cd107c4b87cf24e7f2968f" +checksum = "d64486e34d56d63797295aa928288542226b6f28f96a3b58f661b1efd9f54cde" dependencies = [ "ctrlc", "log", @@ -3386,9 +3386,9 @@ dependencies = [ [[package]] name = "vecdb_derive" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab7250822f3caf8795728690804d39ab5c72c51f5558b90788a79bc99776d55" +checksum = "3c1f863f6687e9ff90962a51a0f6c9646a14dc0ccfd9ea2ea79e288d88187bef" dependencies = [ "quote", "syn", diff --git a/Cargo.toml b/Cargo.toml index 5234a4234..b58260bfe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -81,8 +81,7 @@ tokio = { version = "1.49.0", features = ["rt-multi-thread"] } tracing = { version = "0.1", default-features = false, features = ["std"] } tower-http = { version = "0.6.8", features = ["catch-panic", "compression-br", "compression-gzip", "compression-zstd", "cors", "normalize-path", "timeout", "trace"] } tower-layer = "0.3" -# vecdb = { version = "0.5.11", features = ["derive", "serde_json", "pco", "schemars"] } -vecdb = { path = "../anydb/crates/vecdb", features = ["derive", "serde_json", "pco", "schemars"] } +vecdb = { version = "0.6.1", features = ["derive", "serde_json", "pco", "schemars"] } [workspace.metadata.release] shared-version = true diff --git a/crates/brk_computer/src/cointime/reserve_risk/compute.rs b/crates/brk_computer/src/cointime/reserve_risk/compute.rs index 0a1acb594..cfe99d9e4 100644 --- a/crates/brk_computer/src/cointime/reserve_risk/compute.rs +++ b/crates/brk_computer/src/cointime/reserve_risk/compute.rs @@ -1,6 +1,6 @@ use brk_error::Result; -use brk_types::{DateIndex, StoredF64}; -use vecdb::{AnyStoredVec, AnyVec, Exit, GenericStoredVec, IterableVec, VecIndex}; +use brk_types::{Close, Dollars, StoredF64}; +use vecdb::Exit; use super::{super::value, Vecs}; use crate::{price, ComputeIndexes}; @@ -13,11 +13,8 @@ impl Vecs { value: &value::Vecs, exit: &Exit, ) -> Result<()> { - // Get VOCDD dateindex sum data (from cointime/value module) - // The dateindex.sum.0 contains daily VOCDD values as EagerVec let vocdd_dateindex_sum = &value.vocdd.dateindex.sum.0; - // Compute 365-day SMA of VOCDD self.vocdd_365d_sma.compute_sma( starting_indexes.dateindex, vocdd_dateindex_sum, @@ -27,43 +24,14 @@ impl Vecs { let price_close = &price.usd.split.close.dateindex; - // Compute HODL Bank = cumulative sum of (price - vocdd_sma) - // Start from where we left off and maintain cumulative state - let starting_dateindex = starting_indexes - .dateindex - .to_usize() - .min(self.hodl_bank.len()); - let target_len = price_close.len().min(self.vocdd_365d_sma.len()); + self.hodl_bank.compute_cumulative_transformed_binary( + starting_indexes.dateindex, + price_close, + &self.vocdd_365d_sma, + |price: Close, sma: StoredF64| StoredF64::from(f64::from(price) - f64::from(sma)), + exit, + )?; - if target_len > starting_dateindex { - let mut price_iter = price_close.iter(); - let mut vocdd_sma_iter = self.vocdd_365d_sma.iter(); - - // Get previous cumulative value, or start at 0 - let mut cumulative: f64 = if starting_dateindex > 0 { - let prev_dateindex = DateIndex::from(starting_dateindex - 1); - f64::from(*self.hodl_bank.iter().get_unwrap(prev_dateindex)) - } else { - 0.0 - }; - - for i in starting_dateindex..target_len { - let dateindex = DateIndex::from(i); - let price_val = f64::from(*price_iter.get_unwrap(dateindex)); - let vocdd_sma = f64::from(*vocdd_sma_iter.get_unwrap(dateindex)); - - // HODL Bank contribution: price - smoothed VOCDD - // Accumulate over time - cumulative += price_val - vocdd_sma; - self.hodl_bank - .truncate_push_at(i, StoredF64::from(cumulative))?; - } - - let _lock = exit.lock(); - self.hodl_bank.write()?; - } - - // Compute Reserve Risk = price / hodl_bank (if enabled) if let Some(reserve_risk) = self.reserve_risk.as_mut() { reserve_risk.compute_all(starting_indexes, exit, |v| { v.compute_divide( From 49794c5e04699c991657e7d4d1dee8c57facaa3f Mon Sep 17 00:00:00 2001 From: Brandon Collins Date: Wed, 21 Jan 2026 14:07:20 -0500 Subject: [PATCH 5/6] refactor: remove verbose comments from tests --- .../src/cointime/reserve_risk/compute.rs | 39 ++----------------- 1 file changed, 4 insertions(+), 35 deletions(-) diff --git a/crates/brk_computer/src/cointime/reserve_risk/compute.rs b/crates/brk_computer/src/cointime/reserve_risk/compute.rs index cfe99d9e4..634874ffa 100644 --- a/crates/brk_computer/src/cointime/reserve_risk/compute.rs +++ b/crates/brk_computer/src/cointime/reserve_risk/compute.rs @@ -50,13 +50,8 @@ impl Vecs { #[cfg(test)] mod tests { - use super::*; - - /// Test the HODL Bank cumulative formula - /// HODL Bank[n] = HODL Bank[n-1] + (price[n] - vocdd_sma[n]) #[test] fn test_hodl_bank_formula() { - // Simulate daily data let prices = [100.0, 110.0, 105.0, 120.0, 115.0]; let vocdd_sma = [50.0, 55.0, 52.0, 60.0, 58.0]; @@ -64,17 +59,10 @@ mod tests { let mut expected = Vec::new(); for i in 0..prices.len() { - // HODL Bank contribution: price - vocdd_sma hodl_bank += prices[i] - vocdd_sma[i]; expected.push(hodl_bank); } - // Expected values: - // Day 0: 0 + (100 - 50) = 50 - // Day 1: 50 + (110 - 55) = 105 - // Day 2: 105 + (105 - 52) = 158 - // Day 3: 158 + (120 - 60) = 218 - // Day 4: 218 + (115 - 58) = 275 assert!((expected[0] - 50.0).abs() < 0.001); assert!((expected[1] - 105.0).abs() < 0.001); assert!((expected[2] - 158.0).abs() < 0.001); @@ -82,50 +70,31 @@ mod tests { assert!((expected[4] - 275.0).abs() < 0.001); } - /// Test the Reserve Risk formula - /// Reserve Risk = price / HODL Bank #[test] fn test_reserve_risk_formula() { let price = 100.0_f64; let hodl_bank = 1000.0_f64; - let reserve_risk = price / hodl_bank; - - // Reserve Risk = 100 / 1000 = 0.1 assert!((reserve_risk - 0.1).abs() < 0.0001); } - /// Test that low Reserve Risk indicates buying opportunity - /// (high HODL Bank relative to price) #[test] fn test_reserve_risk_interpretation() { - // High HODL Bank (long-term holder confidence) = low Reserve Risk - let high_confidence = 100.0 / 10000.0; // 0.01 - - // Low HODL Bank (low confidence) = high Reserve Risk - let low_confidence = 100.0 / 100.0; // 1.0 - + let high_confidence = 100.0 / 10000.0; + let low_confidence = 100.0 / 100.0; assert!(high_confidence < low_confidence); - assert!(high_confidence < 0.05); // Good buying opportunity - assert!(low_confidence > 0.5); // Overheated market } - /// Test HODL Bank accumulation with negative contributions - /// When VOCDD_SMA > Price, HODL Bank decreases #[test] fn test_hodl_bank_negative_contribution() { - let prices = [100.0, 80.0, 90.0]; // Price drops - let vocdd_sma = [50.0, 100.0, 85.0]; // VOCDD_SMA rises then normalizes + let prices = [100.0, 80.0, 90.0]; + let vocdd_sma = [50.0, 100.0, 85.0]; let mut hodl_bank = 0.0_f64; - for i in 0..prices.len() { hodl_bank += prices[i] - vocdd_sma[i]; } - // Day 0: 0 + (100 - 50) = 50 - // Day 1: 50 + (80 - 100) = 30 (decreases when vocdd_sma > price) - // Day 2: 30 + (90 - 85) = 35 assert!((hodl_bank - 35.0).abs() < 0.001); } } From fd4cf5d41411d8593d0af7cd2484f94f5b2e2b7c Mon Sep 17 00:00:00 2001 From: Brandon Collins Date: Wed, 21 Jan 2026 14:08:27 -0500 Subject: [PATCH 6/6] refactor: remove verbose comments from vecs.rs files --- .../brk_computer/src/cointime/reserve_risk/vecs.rs | 14 -------------- crates/brk_computer/src/cointime/value/vecs.rs | 2 -- 2 files changed, 16 deletions(-) diff --git a/crates/brk_computer/src/cointime/reserve_risk/vecs.rs b/crates/brk_computer/src/cointime/reserve_risk/vecs.rs index 9471b5ae4..fa5af9c3c 100644 --- a/crates/brk_computer/src/cointime/reserve_risk/vecs.rs +++ b/crates/brk_computer/src/cointime/reserve_risk/vecs.rs @@ -4,23 +4,9 @@ use vecdb::{EagerVec, PcoVec}; use crate::internal::ComputedFromDateLast; -/// Reserve Risk metric components. -/// -/// Reserve Risk = Price / HODL Bank -/// Where HODL Bank = Σ(Price - avg_VOCDD) over time -/// -/// Low Reserve Risk = high long-term holder confidence = good buying opportunity. #[derive(Clone, Traversable)] pub struct Vecs { - /// Moving average of VOCDD (Value-weighted CDD) over 365 days - /// Used to smooth the VOCDD signal for HODL Bank calculation pub vocdd_365d_sma: EagerVec>, - - /// HODL Bank = cumulative sum of (price - vocdd_365d_sma) - /// Represents the opportunity cost of holding Bitcoin vs trading pub hodl_bank: EagerVec>, - - /// Reserve Risk = price / hodl_bank - /// A timing indicator for long-term Bitcoin accumulation pub reserve_risk: Option>, } diff --git a/crates/brk_computer/src/cointime/value/vecs.rs b/crates/brk_computer/src/cointime/value/vecs.rs index 3d30d3269..d49782198 100644 --- a/crates/brk_computer/src/cointime/value/vecs.rs +++ b/crates/brk_computer/src/cointime/value/vecs.rs @@ -8,7 +8,5 @@ pub struct Vecs { pub cointime_value_destroyed: ComputedFromHeightSumCum, pub cointime_value_created: ComputedFromHeightSumCum, pub cointime_value_stored: ComputedFromHeightSumCum, - /// Value-weighted Coin Days Destroyed (CDD × price) - /// Used for Reserve Risk calculation: VOCDD = coindays_destroyed × price pub vocdd: ComputedFromHeightSumCum, }