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..634874ffa --- /dev/null +++ b/crates/brk_computer/src/cointime/reserve_risk/compute.rs @@ -0,0 +1,100 @@ +use brk_error::Result; +use brk_types::{Close, Dollars, StoredF64}; +use vecdb::Exit; + +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<()> { + let vocdd_dateindex_sum = &value.vocdd.dateindex.sum.0; + + self.vocdd_365d_sma.compute_sma( + starting_indexes.dateindex, + vocdd_dateindex_sum, + 365, + exit, + )?; + + let price_close = &price.usd.split.close.dateindex; + + 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 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(()) + } +} + +#[cfg(test)] +mod tests { + #[test] + fn test_hodl_bank_formula() { + 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 += prices[i] - vocdd_sma[i]; + expected.push(hodl_bank); + } + + 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] + fn test_reserve_risk_formula() { + let price = 100.0_f64; + let hodl_bank = 1000.0_f64; + let reserve_risk = price / hodl_bank; + assert!((reserve_risk - 0.1).abs() < 0.0001); + } + + #[test] + fn test_reserve_risk_interpretation() { + let high_confidence = 100.0 / 10000.0; + let low_confidence = 100.0 / 100.0; + assert!(high_confidence < low_confidence); + } + + #[test] + fn test_hodl_bank_negative_contribution() { + 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]; + } + + assert!((hodl_bank - 35.0).abs() < 0.001); + } +} 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..fa5af9c3c --- /dev/null +++ b/crates/brk_computer/src/cointime/reserve_risk/vecs.rs @@ -0,0 +1,12 @@ +use brk_traversable::Traversable; +use brk_types::{DateIndex, StoredF64}; +use vecdb::{EagerVec, PcoVec}; + +use crate::internal::ComputedFromDateLast; + +#[derive(Clone, Traversable)] +pub struct Vecs { + pub vocdd_365d_sma: EagerVec>, + pub hodl_bank: EagerVec>, + 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..d49782198 100644 --- a/crates/brk_computer/src/cointime/value/vecs.rs +++ b/crates/brk_computer/src/cointime/value/vecs.rs @@ -8,4 +8,5 @@ pub struct Vecs { pub cointime_value_destroyed: ComputedFromHeightSumCum, pub cointime_value_created: ComputedFromHeightSumCum, pub cointime_value_stored: ComputedFromHeightSumCum, + pub vocdd: ComputedFromHeightSumCum, }