global: snapshot

This commit is contained in:
nym21
2026-03-01 20:06:25 +01:00
parent 7bf0220f25
commit 4abb00b86d
71 changed files with 2432 additions and 2157 deletions
@@ -4,12 +4,14 @@ mod windows;
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Bitcoin, Dollars, Sats, Version};
use brk_types::{Bitcoin, Cents, Dollars, Sats, Version};
use vecdb::{Database, ReadableCloneableVec, Rw, StorageMode};
use crate::{
indexes,
internal::{ComputedFromHeightLast, LazyFromHeightLast, SatsToBitcoin},
internal::{
CentsUnsignedToDollars, ComputedFromHeightLast, LazyFromHeightLast, SatsToBitcoin,
},
};
pub use rolling_full::*;
@@ -19,7 +21,8 @@ pub use rolling_sum::*;
pub struct ByUnit<M: StorageMode = Rw> {
pub sats: ComputedFromHeightLast<Sats, M>,
pub btc: LazyFromHeightLast<Bitcoin, Sats>,
pub usd: ComputedFromHeightLast<Dollars, M>,
pub cents: ComputedFromHeightLast<Cents, M>,
pub usd: LazyFromHeightLast<Dollars, Cents>,
}
impl ByUnit {
@@ -38,9 +41,25 @@ impl ByUnit {
&sats,
);
let usd =
ComputedFromHeightLast::forced_import(db, &format!("{name}_usd"), version, indexes)?;
let cents = ComputedFromHeightLast::forced_import(
db,
&format!("{name}_cents"),
version,
indexes,
)?;
Ok(Self { sats, btc, usd })
let usd = LazyFromHeightLast::from_computed::<CentsUnsignedToDollars>(
&format!("{name}_usd"),
version,
cents.height.read_only_boxed_clone(),
&cents,
);
Ok(Self {
sats,
btc,
cents,
usd,
})
}
}
@@ -1,6 +1,6 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Dollars, Height, Sats, Version};
use brk_types::{Cents, Height, Sats, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{Database, Exit, ReadableVec, Rw, StorageMode};
@@ -47,11 +47,11 @@ impl RollingFullSlot {
max_from: Height,
starts: &impl ReadableVec<Height, Height>,
sats_source: &impl ReadableVec<Height, Sats>,
usd_source: &impl ReadableVec<Height, Dollars>,
cents_source: &impl ReadableVec<Height, Cents>,
exit: &Exit,
) -> Result<()> {
self.sum.sats.height.compute_rolling_sum(max_from, starts, sats_source, exit)?;
self.sum.usd.height.compute_rolling_sum(max_from, starts, usd_source, exit)?;
self.sum.cents.height.compute_rolling_sum(max_from, starts, cents_source, exit)?;
let d = &mut self.distribution;
@@ -64,11 +64,11 @@ impl RollingFullSlot {
)?;
compute_rolling_distribution_from_starts(
max_from, starts, usd_source,
&mut d.average.usd.height, &mut d.min.usd.height,
&mut d.max.usd.height, &mut d.pct10.usd.height,
&mut d.pct25.usd.height, &mut d.median.usd.height,
&mut d.pct75.usd.height, &mut d.pct90.usd.height, exit,
max_from, starts, cents_source,
&mut d.average.cents.height, &mut d.min.cents.height,
&mut d.max.cents.height, &mut d.pct10.cents.height,
&mut d.pct25.cents.height, &mut d.median.cents.height,
&mut d.pct75.cents.height, &mut d.pct90.cents.height, exit,
)?;
Ok(())
@@ -105,11 +105,11 @@ impl RollingFullByUnit {
max_from: Height,
windows: &WindowStarts<'_>,
sats_source: &impl ReadableVec<Height, Sats>,
usd_source: &impl ReadableVec<Height, Dollars>,
cents_source: &impl ReadableVec<Height, Cents>,
exit: &Exit,
) -> Result<()> {
for (slot, starts) in self.0.as_mut_array().into_iter().zip(windows.as_array()) {
slot.compute(max_from, starts, sats_source, usd_source, exit)?;
slot.compute(max_from, starts, sats_source, cents_source, exit)?;
}
Ok(())
}
@@ -1,6 +1,6 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Dollars, Height, Sats, Version};
use brk_types::{Cents, Height, Sats, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{Database, Exit, ReadableVec, Rw, StorageMode};
@@ -34,12 +34,12 @@ impl RollingSumByUnit {
max_from: Height,
windows: &WindowStarts<'_>,
sats_source: &impl ReadableVec<Height, Sats>,
usd_source: &impl ReadableVec<Height, Dollars>,
cents_source: &impl ReadableVec<Height, Cents>,
exit: &Exit,
) -> Result<()> {
for (w, starts) in self.0.as_mut_array().into_iter().zip(windows.as_array()) {
w.sats.height.compute_rolling_sum(max_from, starts, sats_source, exit)?;
w.usd.height.compute_rolling_sum(max_from, starts, usd_source, exit)?;
w.cents.height.compute_rolling_sum(max_from, starts, cents_source, exit)?;
}
Ok(())
}
@@ -1,6 +1,6 @@
use brk_error::Result;
use brk_traversable::{Traversable, TreeNode};
use brk_types::{Dollars, Height, StoredF32, Version};
use brk_types::{Cents, Height, StoredF32, Version};
use vecdb::{AnyExportableVec, Database, ReadOnlyClone, Ro, Rw, StorageMode, WritableVec};
use crate::indexes;
@@ -14,10 +14,10 @@ pub const PERCENTILES_LEN: usize = PERCENTILES.len();
/// Compute spot percentile rank by interpolating within percentile bands.
/// Returns a value between 0 and 100 indicating where spot sits in the distribution.
pub(crate) fn compute_spot_percentile_rank(
percentile_prices: &[Dollars; PERCENTILES_LEN],
spot: Dollars,
percentile_prices: &[Cents; PERCENTILES_LEN],
spot: Cents,
) -> StoredF32 {
if spot.is_nan() || percentile_prices[0].is_nan() {
if spot == Cents::ZERO && percentile_prices[0] == Cents::ZERO {
return StoredF32::NAN;
}
@@ -68,7 +68,7 @@ pub(crate) fn compute_spot_percentile_rank(
}
pub struct PercentilesVecs<M: StorageMode = Rw> {
pub vecs: [Price<ComputedFromHeightLast<Dollars, M>>; PERCENTILES_LEN],
pub vecs: [Price<ComputedFromHeightLast<Cents, M>>; PERCENTILES_LEN],
}
const VERSION: Version = Version::ONE;
@@ -94,14 +94,14 @@ impl PercentilesVecs {
Ok(Self { vecs })
}
/// Push percentile prices at this height.
/// Push percentile prices at this height (in cents).
pub(crate) fn truncate_push(
&mut self,
height: Height,
percentile_prices: &[Dollars; PERCENTILES_LEN],
percentile_prices: &[Cents; PERCENTILES_LEN],
) -> Result<()> {
for (i, v) in self.vecs.iter_mut().enumerate() {
v.usd.height.truncate_push(height, percentile_prices[i])?;
v.cents.height.truncate_push(height, percentile_prices[i])?;
}
Ok(())
}
@@ -109,7 +109,7 @@ impl PercentilesVecs {
/// Validate computed versions or reset if mismatched.
pub(crate) fn validate_computed_version_or_reset(&mut self, version: Version) -> Result<()> {
for vec in self.vecs.iter_mut() {
vec.usd.height.validate_computed_version_or_reset(version)?;
vec.cents.height.validate_computed_version_or_reset(version)?;
}
Ok(())
}
@@ -130,7 +130,7 @@ impl ReadOnlyClone for PercentilesVecs {
impl<M: StorageMode> Traversable for PercentilesVecs<M>
where
Price<ComputedFromHeightLast<Dollars, M>>: Traversable,
Price<ComputedFromHeightLast<Cents, M>>: Traversable,
{
fn to_tree_node(&self) -> TreeNode {
TreeNode::Branch(
@@ -1,65 +1,104 @@
//! Generic price wrapper with both USD and sats representations.
//! Generic price wrapper with cents, USD, and sats representations.
//!
//! All prices use this single struct with different USD types.
//! All prices use this single struct with different cents types.
//! USD is always lazily derived from cents via CentsUnsignedToDollars.
//! Sats is always lazily derived from USD via DollarsToSatsFract.
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Dollars, SatsFract, Version};
use brk_types::{Cents, Dollars, SatsFract, Version};
use schemars::JsonSchema;
use vecdb::{Database, ReadableCloneableVec, UnaryTransform};
use super::{ComputedFromHeightLast, LazyFromHeightLast};
use crate::{
indexes,
internal::{ComputedVecValue, DollarsToSatsFract, NumericValue},
internal::{CentsUnsignedToDollars, ComputedVecValue, DollarsToSatsFract, NumericValue},
};
/// Generic price metric with both USD and sats representations.
/// Generic price metric with cents, USD, and sats representations.
#[derive(Clone, Traversable)]
pub struct Price<U> {
pub usd: U,
pub struct Price<C> {
pub cents: C,
pub usd: LazyFromHeightLast<Dollars, Cents>,
pub sats: LazyFromHeightLast<SatsFract, Dollars>,
}
impl Price<ComputedFromHeightLast<Dollars>> {
impl Price<ComputedFromHeightLast<Cents>> {
/// Import from database: stored cents, lazy USD + sats.
pub(crate) fn forced_import(
db: &Database,
name: &str,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
let usd = ComputedFromHeightLast::forced_import(db, name, version, indexes)?;
let sats = LazyFromHeightLast::from_computed::<DollarsToSatsFract>(
let cents = ComputedFromHeightLast::forced_import(
db,
&format!("{name}_cents"),
version,
indexes,
)?;
let usd = LazyFromHeightLast::from_computed::<CentsUnsignedToDollars>(
&format!("{name}_usd"),
version,
cents.height.read_only_boxed_clone(),
&cents,
);
let sats = LazyFromHeightLast::from_lazy::<DollarsToSatsFract, Cents>(
&format!("{name}_sats"),
version,
usd.height.read_only_boxed_clone(),
&usd,
);
Ok(Self { usd, sats })
Ok(Self { cents, usd, sats })
}
/// Wrap an already-imported ComputedFromHeightLast<Cents> with lazy USD + sats.
pub(crate) fn from_cents(
name: &str,
version: Version,
cents: ComputedFromHeightLast<Cents>,
) -> Self {
let usd = LazyFromHeightLast::from_computed::<CentsUnsignedToDollars>(
&format!("{name}_usd"),
version,
cents.height.read_only_boxed_clone(),
&cents,
);
let sats = LazyFromHeightLast::from_lazy::<DollarsToSatsFract, Cents>(
&format!("{name}_sats"),
version,
&usd,
);
Self { cents, usd, sats }
}
}
impl<ST> Price<LazyFromHeightLast<Dollars, ST>>
impl<ST> Price<LazyFromHeightLast<Cents, ST>>
where
ST: ComputedVecValue + NumericValue + JsonSchema + 'static,
{
pub(crate) fn from_computed<F: UnaryTransform<ST, Dollars>>(
/// Create from a computed source, applying a transform to produce Cents.
pub(crate) fn from_cents_source<F: UnaryTransform<ST, Cents>>(
name: &str,
version: Version,
source: &ComputedFromHeightLast<ST>,
) -> Self {
let usd = LazyFromHeightLast::from_computed::<F>(
name,
let cents = LazyFromHeightLast::from_computed::<F>(
&format!("{name}_cents"),
version,
source.height.read_only_boxed_clone(),
source,
);
let sats = LazyFromHeightLast::from_lazy::<DollarsToSatsFract, ST>(
let usd = LazyFromHeightLast::from_lazy::<CentsUnsignedToDollars, ST>(
&format!("{name}_usd"),
version,
&cents,
);
let sats = LazyFromHeightLast::from_lazy::<DollarsToSatsFract, Cents>(
&format!("{name}_sats"),
version,
&usd,
);
Self { usd, sats }
Self { cents, usd, sats }
}
}
@@ -1,6 +1,6 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Dollars, Height, Version};
use brk_types::{Cents, Height, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{Database, Exit, ReadableVec, Rw, StorageMode};
@@ -31,22 +31,22 @@ impl ComputedFromHeightRatioExtended {
})
}
/// Compute ratio and all extended metrics from an externally-provided metric price.
/// Compute ratio and all extended metrics from an externally-provided metric price (in cents).
pub(crate) fn compute_rest(
&mut self,
blocks: &blocks::Vecs,
prices: &prices::Vecs,
starting_indexes: &ComputeIndexes,
exit: &Exit,
metric_price: &impl ReadableVec<Height, Dollars>,
metric_price: &impl ReadableVec<Height, Cents>,
) -> Result<()> {
let close_price = &prices.price.usd.height;
let close_price = &prices.price.cents.height;
self.base
.compute_ratio(starting_indexes, close_price, metric_price, exit)?;
self.extended
.compute_rest(blocks, starting_indexes, exit, &self.base.ratio.height)?;
self.extended
.compute_usd_bands(starting_indexes, metric_price, exit)?;
.compute_cents_bands(starting_indexes, metric_price, exit)?;
Ok(())
}
}
@@ -1,6 +1,6 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Dollars, Height, StoredF32, Version};
use brk_types::{Cents, Height, StoredF32, Version};
use vecdb::{AnyStoredVec, AnyVec, Database, EagerVec, Exit, PcoVec, ReadableVec, Rw, StorageMode, VecIndex, WritableVec};
use crate::{
@@ -21,12 +21,12 @@ pub struct ComputedFromHeightRatioExtension<M: StorageMode = Rw> {
pub ratio_pct5: ComputedFromHeightLast<StoredF32, M>,
pub ratio_pct2: ComputedFromHeightLast<StoredF32, M>,
pub ratio_pct1: ComputedFromHeightLast<StoredF32, M>,
pub ratio_pct99_usd: Price<ComputedFromHeightLast<Dollars, M>>,
pub ratio_pct98_usd: Price<ComputedFromHeightLast<Dollars, M>>,
pub ratio_pct95_usd: Price<ComputedFromHeightLast<Dollars, M>>,
pub ratio_pct5_usd: Price<ComputedFromHeightLast<Dollars, M>>,
pub ratio_pct2_usd: Price<ComputedFromHeightLast<Dollars, M>>,
pub ratio_pct1_usd: Price<ComputedFromHeightLast<Dollars, M>>,
pub ratio_pct99_price: Price<ComputedFromHeightLast<Cents, M>>,
pub ratio_pct98_price: Price<ComputedFromHeightLast<Cents, M>>,
pub ratio_pct95_price: Price<ComputedFromHeightLast<Cents, M>>,
pub ratio_pct5_price: Price<ComputedFromHeightLast<Cents, M>>,
pub ratio_pct2_price: Price<ComputedFromHeightLast<Cents, M>>,
pub ratio_pct1_price: Price<ComputedFromHeightLast<Cents, M>>,
pub ratio_sd: ComputedFromHeightStdDevExtended<M>,
pub ratio_4y_sd: ComputedFromHeightStdDevExtended<M>,
@@ -68,7 +68,7 @@ impl ComputedFromHeightRatioExtension {
};
}
macro_rules! import_usd {
macro_rules! import_price {
($suffix:expr) => {
Price::forced_import(db, &format!("{name}_{}", $suffix), v, indexes)?
};
@@ -87,12 +87,12 @@ impl ComputedFromHeightRatioExtension {
ratio_pct5: import!("ratio_pct5"),
ratio_pct2: import!("ratio_pct2"),
ratio_pct1: import!("ratio_pct1"),
ratio_pct99_usd: import_usd!("ratio_pct99_usd"),
ratio_pct98_usd: import_usd!("ratio_pct98_usd"),
ratio_pct95_usd: import_usd!("ratio_pct95_usd"),
ratio_pct5_usd: import_usd!("ratio_pct5_usd"),
ratio_pct2_usd: import_usd!("ratio_pct2_usd"),
ratio_pct1_usd: import_usd!("ratio_pct1_usd"),
ratio_pct99_price: import_price!("ratio_pct99"),
ratio_pct98_price: import_price!("ratio_pct98"),
ratio_pct95_price: import_price!("ratio_pct95"),
ratio_pct5_price: import_price!("ratio_pct5"),
ratio_pct2_price: import_price!("ratio_pct2"),
ratio_pct1_price: import_price!("ratio_pct1"),
})
}
@@ -219,20 +219,20 @@ impl ComputedFromHeightRatioExtension {
Ok(())
}
/// Compute USD ratio bands: usd_band = metric_price * ratio_percentile
pub(crate) fn compute_usd_bands(
/// Compute cents ratio bands: cents_band = metric_price_cents * ratio_percentile
pub(crate) fn compute_cents_bands(
&mut self,
starting_indexes: &ComputeIndexes,
metric_price: &impl ReadableVec<Height, Dollars>,
metric_price: &impl ReadableVec<Height, Cents>,
exit: &Exit,
) -> Result<()> {
use crate::internal::PriceTimesRatio;
use crate::internal::PriceTimesRatioCents;
macro_rules! compute_band {
($usd_field:ident, $band_source:expr) => {
self.$usd_field
.usd
.compute_binary::<Dollars, StoredF32, PriceTimesRatio>(
.cents
.compute_binary::<Cents, StoredF32, PriceTimesRatioCents>(
starting_indexes.height,
metric_price,
$band_source,
@@ -241,22 +241,22 @@ impl ComputedFromHeightRatioExtension {
};
}
compute_band!(ratio_pct99_usd, &self.ratio_pct99.height);
compute_band!(ratio_pct98_usd, &self.ratio_pct98.height);
compute_band!(ratio_pct95_usd, &self.ratio_pct95.height);
compute_band!(ratio_pct5_usd, &self.ratio_pct5.height);
compute_band!(ratio_pct2_usd, &self.ratio_pct2.height);
compute_band!(ratio_pct1_usd, &self.ratio_pct1.height);
compute_band!(ratio_pct99_price, &self.ratio_pct99.height);
compute_band!(ratio_pct98_price, &self.ratio_pct98.height);
compute_band!(ratio_pct95_price, &self.ratio_pct95.height);
compute_band!(ratio_pct5_price, &self.ratio_pct5.height);
compute_band!(ratio_pct2_price, &self.ratio_pct2.height);
compute_band!(ratio_pct1_price, &self.ratio_pct1.height);
// Stddev USD bands
// Stddev cents bands
self.ratio_sd
.compute_usd_bands(starting_indexes, metric_price, exit)?;
.compute_cents_bands(starting_indexes, metric_price, exit)?;
self.ratio_4y_sd
.compute_usd_bands(starting_indexes, metric_price, exit)?;
.compute_cents_bands(starting_indexes, metric_price, exit)?;
self.ratio_2y_sd
.compute_usd_bands(starting_indexes, metric_price, exit)?;
.compute_cents_bands(starting_indexes, metric_price, exit)?;
self.ratio_1y_sd
.compute_usd_bands(starting_indexes, metric_price, exit)?;
.compute_cents_bands(starting_indexes, metric_price, exit)?;
Ok(())
}
@@ -8,7 +8,7 @@ pub use price_extended::*;
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Dollars, Height, StoredF32, Version};
use brk_types::{Cents, Height, StoredF32, Version};
use vecdb::{Database, Exit, ReadableVec, Rw, StorageMode};
use crate::{ComputeIndexes, indexes};
@@ -36,12 +36,12 @@ impl ComputedFromHeightRatio {
})
}
/// Compute ratio = close_price / metric_price at height level
/// Compute ratio = close_price / metric_price at height level (both in cents)
pub(crate) fn compute_ratio(
&mut self,
starting_indexes: &ComputeIndexes,
close_price: &impl ReadableVec<Height, Dollars>,
metric_price: &impl ReadableVec<Height, Dollars>,
close_price: &impl ReadableVec<Height, Cents>,
metric_price: &impl ReadableVec<Height, Cents>,
exit: &Exit,
) -> Result<()> {
self.ratio.height.compute_transform2(
@@ -49,10 +49,10 @@ impl ComputedFromHeightRatio {
close_price,
metric_price,
|(i, close, price, ..)| {
if price == Dollars::ZERO {
if price == Cents::ZERO {
(i, StoredF32::from(1.0))
} else {
(i, StoredF32::from(close / price))
(i, StoredF32::from(f64::from(close) / f64::from(price)))
}
},
exit,
@@ -1,6 +1,6 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Dollars, Height, Version};
use brk_types::{Cents, Height, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{Database, EagerVec, Exit, PcoVec, Rw, StorageMode};
@@ -15,7 +15,7 @@ pub struct ComputedFromHeightPriceWithRatioExtended<M: StorageMode = Rw> {
#[deref_mut]
#[traversable(flatten)]
pub inner: ComputedFromHeightRatioExtended<M>,
pub price: Price<ComputedFromHeightLast<Dollars, M>>,
pub price: Price<ComputedFromHeightLast<Cents, M>>,
}
impl ComputedFromHeightPriceWithRatioExtended {
@@ -32,7 +32,7 @@ impl ComputedFromHeightPriceWithRatioExtended {
})
}
/// Compute price via closure, then compute ratio + extended metrics.
/// Compute price via closure (in cents), then compute ratio + extended metrics.
pub(crate) fn compute_all<F>(
&mut self,
blocks: &blocks::Vecs,
@@ -42,15 +42,15 @@ impl ComputedFromHeightPriceWithRatioExtended {
mut compute_price: F,
) -> Result<()>
where
F: FnMut(&mut EagerVec<PcoVec<Height, Dollars>>) -> Result<()>,
F: FnMut(&mut EagerVec<PcoVec<Height, Cents>>) -> Result<()>,
{
compute_price(&mut self.price.usd.height)?;
compute_price(&mut self.price.cents.height)?;
self.inner.compute_rest(
blocks,
prices,
starting_indexes,
exit,
&self.price.usd.height,
&self.price.cents.height,
)?;
Ok(())
}
@@ -1,6 +1,6 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Dollars, Height, StoredF32, Version};
use brk_types::{Cents, Height, StoredF32, Version};
use vecdb::{
AnyStoredVec, AnyVec, Database, EagerVec, Exit, PcoVec, ReadableVec, Rw, StorageMode, VecIndex,
WritableVec,
@@ -32,19 +32,19 @@ pub struct ComputedFromHeightStdDevExtended<M: StorageMode = Rw> {
pub m2_5sd: ComputedFromHeightLast<StoredF32, M>,
pub m3sd: ComputedFromHeightLast<StoredF32, M>,
pub _0sd_usd: Price<ComputedFromHeightLast<Dollars, M>>,
pub p0_5sd_usd: Price<ComputedFromHeightLast<Dollars, M>>,
pub p1sd_usd: Price<ComputedFromHeightLast<Dollars, M>>,
pub p1_5sd_usd: Price<ComputedFromHeightLast<Dollars, M>>,
pub p2sd_usd: Price<ComputedFromHeightLast<Dollars, M>>,
pub p2_5sd_usd: Price<ComputedFromHeightLast<Dollars, M>>,
pub p3sd_usd: Price<ComputedFromHeightLast<Dollars, M>>,
pub m0_5sd_usd: Price<ComputedFromHeightLast<Dollars, M>>,
pub m1sd_usd: Price<ComputedFromHeightLast<Dollars, M>>,
pub m1_5sd_usd: Price<ComputedFromHeightLast<Dollars, M>>,
pub m2sd_usd: Price<ComputedFromHeightLast<Dollars, M>>,
pub m2_5sd_usd: Price<ComputedFromHeightLast<Dollars, M>>,
pub m3sd_usd: Price<ComputedFromHeightLast<Dollars, M>>,
pub _0sd_price: Price<ComputedFromHeightLast<Cents, M>>,
pub p0_5sd_price: Price<ComputedFromHeightLast<Cents, M>>,
pub p1sd_price: Price<ComputedFromHeightLast<Cents, M>>,
pub p1_5sd_price: Price<ComputedFromHeightLast<Cents, M>>,
pub p2sd_price: Price<ComputedFromHeightLast<Cents, M>>,
pub p2_5sd_price: Price<ComputedFromHeightLast<Cents, M>>,
pub p3sd_price: Price<ComputedFromHeightLast<Cents, M>>,
pub m0_5sd_price: Price<ComputedFromHeightLast<Cents, M>>,
pub m1sd_price: Price<ComputedFromHeightLast<Cents, M>>,
pub m1_5sd_price: Price<ComputedFromHeightLast<Cents, M>>,
pub m2sd_price: Price<ComputedFromHeightLast<Cents, M>>,
pub m2_5sd_price: Price<ComputedFromHeightLast<Cents, M>>,
pub m3sd_price: Price<ComputedFromHeightLast<Cents, M>>,
}
impl ComputedFromHeightStdDevExtended {
@@ -68,7 +68,7 @@ impl ComputedFromHeightStdDevExtended {
};
}
macro_rules! import_usd {
macro_rules! import_price {
($suffix:expr) => {
Price::forced_import(db, &format!("{name}_{}", $suffix), version, indexes)?
};
@@ -89,19 +89,19 @@ impl ComputedFromHeightStdDevExtended {
m2sd: import!("m2sd"),
m2_5sd: import!("m2_5sd"),
m3sd: import!("m3sd"),
_0sd_usd: import_usd!("0sd_usd"),
p0_5sd_usd: import_usd!("p0_5sd_usd"),
p1sd_usd: import_usd!("p1sd_usd"),
p1_5sd_usd: import_usd!("p1_5sd_usd"),
p2sd_usd: import_usd!("p2sd_usd"),
p2_5sd_usd: import_usd!("p2_5sd_usd"),
p3sd_usd: import_usd!("p3sd_usd"),
m0_5sd_usd: import_usd!("m0_5sd_usd"),
m1sd_usd: import_usd!("m1sd_usd"),
m1_5sd_usd: import_usd!("m1_5sd_usd"),
m2sd_usd: import_usd!("m2sd_usd"),
m2_5sd_usd: import_usd!("m2_5sd_usd"),
m3sd_usd: import_usd!("m3sd_usd"),
_0sd_price: import_price!("0sd"),
p0_5sd_price: import_price!("p0_5sd"),
p1sd_price: import_price!("p1sd"),
p1_5sd_price: import_price!("p1_5sd"),
p2sd_price: import_price!("p2sd"),
p2_5sd_price: import_price!("p2_5sd"),
p3sd_price: import_price!("p3sd"),
m0_5sd_price: import_price!("m0_5sd"),
m1sd_price: import_price!("m1sd"),
m1_5sd_price: import_price!("m1_5sd"),
m2sd_price: import_price!("m2sd"),
m2_5sd_price: import_price!("m2_5sd"),
m3sd_price: import_price!("m3sd"),
})
}
@@ -217,20 +217,20 @@ impl ComputedFromHeightStdDevExtended {
Ok(())
}
/// Compute USD price bands: usd_band = metric_price * band_ratio
pub(crate) fn compute_usd_bands(
/// Compute cents price bands: cents_band = metric_price_cents * band_ratio
pub(crate) fn compute_cents_bands(
&mut self,
starting_indexes: &ComputeIndexes,
metric_price: &impl ReadableVec<Height, Dollars>,
metric_price: &impl ReadableVec<Height, Cents>,
exit: &Exit,
) -> Result<()> {
use crate::internal::PriceTimesRatio;
use crate::internal::PriceTimesRatioCents;
macro_rules! compute_band {
($usd_field:ident, $band_source:expr) => {
self.$usd_field
.usd
.compute_binary::<Dollars, StoredF32, PriceTimesRatio>(
.cents
.compute_binary::<Cents, StoredF32, PriceTimesRatioCents>(
starting_indexes.height,
metric_price,
$band_source,
@@ -239,19 +239,19 @@ impl ComputedFromHeightStdDevExtended {
};
}
compute_band!(_0sd_usd, &self.base.sma.height);
compute_band!(p0_5sd_usd, &self.p0_5sd.height);
compute_band!(p1sd_usd, &self.p1sd.height);
compute_band!(p1_5sd_usd, &self.p1_5sd.height);
compute_band!(p2sd_usd, &self.p2sd.height);
compute_band!(p2_5sd_usd, &self.p2_5sd.height);
compute_band!(p3sd_usd, &self.p3sd.height);
compute_band!(m0_5sd_usd, &self.m0_5sd.height);
compute_band!(m1sd_usd, &self.m1sd.height);
compute_band!(m1_5sd_usd, &self.m1_5sd.height);
compute_band!(m2sd_usd, &self.m2sd.height);
compute_band!(m2_5sd_usd, &self.m2_5sd.height);
compute_band!(m3sd_usd, &self.m3sd.height);
compute_band!(_0sd_price, &self.base.sma.height);
compute_band!(p0_5sd_price, &self.p0_5sd.height);
compute_band!(p1sd_price, &self.p1sd.height);
compute_band!(p1_5sd_price, &self.p1_5sd.height);
compute_band!(p2sd_price, &self.p2sd.height);
compute_band!(p2_5sd_price, &self.p2_5sd.height);
compute_band!(p3sd_price, &self.p3sd.height);
compute_band!(m0_5sd_price, &self.m0_5sd.height);
compute_band!(m1sd_price, &self.m1sd.height);
compute_band!(m1_5sd_price, &self.m1_5sd.height);
compute_band!(m2sd_price, &self.m2sd.height);
compute_band!(m2_5sd_price, &self.m2_5sd.height);
compute_band!(m3sd_price, &self.m3sd.height);
Ok(())
}
@@ -2,22 +2,25 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Bitcoin, Dollars, Height, Sats, SatsSigned, Version};
use brk_types::{Bitcoin, Cents, CentsSigned, Dollars, Height, Sats, SatsSigned, Version};
use vecdb::{Database, Exit, ReadableCloneableVec, ReadableVec, Rw, StorageMode};
use crate::{
indexes,
internal::{ComputedFromHeightLast, LazyFromHeightLast, SatsSignedToBitcoin},
internal::{
CentsSignedToDollars, ComputedFromHeightLast, LazyFromHeightLast, SatsSignedToBitcoin,
},
};
const VERSION: Version = Version::ZERO;
/// Change values indexed by height - sats (stored), btc (lazy), usd (stored).
/// Change values indexed by height - sats (stored), btc (lazy), cents (stored), usd (lazy).
#[derive(Traversable)]
pub struct ValueFromHeightChange<M: StorageMode = Rw> {
pub sats: ComputedFromHeightLast<SatsSigned, M>,
pub btc: LazyFromHeightLast<Bitcoin, SatsSigned>,
pub usd: ComputedFromHeightLast<Dollars, M>,
pub cents: ComputedFromHeightLast<CentsSigned, M>,
pub usd: LazyFromHeightLast<Dollars, CentsSigned>,
}
impl ValueFromHeightChange {
@@ -38,31 +41,38 @@ impl ValueFromHeightChange {
&sats,
);
let usd = ComputedFromHeightLast::forced_import(
let cents = ComputedFromHeightLast::forced_import(
db,
&format!("{name}_usd"),
&format!("{name}_cents"),
v,
indexes,
)?;
Ok(Self { sats, btc, usd })
let usd = LazyFromHeightLast::from_computed::<CentsSignedToDollars>(
&format!("{name}_usd"),
v,
cents.height.read_only_boxed_clone(),
&cents,
);
Ok(Self { sats, btc, cents, usd })
}
/// Compute rolling change for both sats and dollars in one call.
/// Compute rolling change for both sats and cents in one call.
pub(crate) fn compute_rolling(
&mut self,
starting_height: Height,
window_starts: &impl ReadableVec<Height, Height>,
sats_source: &impl ReadableVec<Height, Sats>,
dollars_source: &(impl ReadableVec<Height, Dollars> + Sync),
cents_source: &(impl ReadableVec<Height, Cents> + Sync),
exit: &Exit,
) -> Result<()> {
self.sats
.height
.compute_rolling_change(starting_height, window_starts, sats_source, exit)?;
self.usd
self.cents
.height
.compute_rolling_change(starting_height, window_starts, dollars_source, exit)?;
.compute_rolling_change(starting_height, window_starts, cents_source, exit)?;
Ok(())
}
}
@@ -1,11 +1,11 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Dollars, Height, Sats, Version};
use brk_types::{Cents, Height, Sats, Version};
use vecdb::{Database, Exit, Rw, StorageMode};
use crate::{
indexes,
internal::{ByUnit, SatsToDollars},
internal::{ByUnit, SatsToCents},
prices,
};
@@ -44,18 +44,18 @@ impl ValueFromHeightCumulative {
.compute_cumulative(max_from, &self.base.sats.height, exit)?;
self.base
.usd
.compute_binary::<Sats, Dollars, SatsToDollars>(
.cents
.compute_binary::<Sats, Cents, SatsToCents>(
max_from,
&self.base.sats.height,
&prices.price.usd.height,
&prices.price.cents.height,
exit,
)?;
self.cumulative
.usd
.cents
.height
.compute_cumulative(max_from, &self.base.usd.height, exit)?;
.compute_cumulative(max_from, &self.base.cents.height, exit)?;
Ok(())
}
@@ -1,11 +1,11 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Dollars, Height, Sats, Version};
use brk_types::{Cents, Height, Sats, Version};
use vecdb::{Database, EagerVec, Exit, PcoVec, Rw, StorageMode};
use crate::{
indexes,
internal::{ByUnit, RollingFullByUnit, SatsToDollars, WindowStarts},
internal::{ByUnit, RollingFullByUnit, SatsToCents, WindowStarts},
prices,
};
@@ -51,25 +51,25 @@ impl ValueFromHeightFull {
.compute_cumulative(max_from, &self.base.sats.height, exit)?;
self.base
.usd
.cents
.height
.compute_binary::<Sats, Dollars, SatsToDollars>(
.compute_binary::<Sats, Cents, SatsToCents>(
max_from,
&self.base.sats.height,
&prices.price.usd.height,
&prices.price.cents.height,
exit,
)?;
self.cumulative
.usd
.cents
.height
.compute_cumulative(max_from, &self.base.usd.height, exit)?;
.compute_cumulative(max_from, &self.base.cents.height, exit)?;
self.rolling.compute(
max_from,
windows,
&self.base.sats.height,
&self.base.usd.height,
&self.base.cents.height,
exit,
)?;
@@ -1,12 +1,12 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Dollars, Height, Sats, Version};
use brk_types::{Cents, Height, Sats, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{Database, Exit, ReadableVec, Rw, StorageMode};
use crate::{
indexes, prices,
internal::{ByUnit, SatsToDollars},
internal::{ByUnit, SatsToCents},
};
#[derive(Deref, DerefMut, Traversable)]
@@ -38,10 +38,10 @@ impl ValueFromHeightLast {
max_from: Height,
exit: &Exit,
) -> Result<()> {
self.base.usd.compute_binary::<Sats, Dollars, SatsToDollars>(
self.base.cents.compute_binary::<Sats, Cents, SatsToCents>(
max_from,
&self.base.sats.height,
&prices.price.usd.height,
&prices.price.cents.height,
exit,
)?;
Ok(())
@@ -52,7 +52,7 @@ impl ValueFromHeightLast {
max_from: Height,
window_starts: &impl ReadableVec<Height, Height>,
sats_source: &impl ReadableVec<Height, Sats>,
usd_source: &impl ReadableVec<Height, Dollars>,
cents_source: &impl ReadableVec<Height, Cents>,
exit: &Exit,
) -> Result<()> {
self.base
@@ -60,9 +60,9 @@ impl ValueFromHeightLast {
.height
.compute_rolling_sum(max_from, window_starts, sats_source, exit)?;
self.base
.usd
.cents
.height
.compute_rolling_sum(max_from, window_starts, usd_source, exit)?;
.compute_rolling_sum(max_from, window_starts, cents_source, exit)?;
Ok(())
}
@@ -71,7 +71,7 @@ impl ValueFromHeightLast {
starting_height: Height,
window_starts: &impl ReadableVec<Height, Height>,
sats_source: &impl ReadableVec<Height, Sats>,
dollars_source: &(impl ReadableVec<Height, Dollars> + Sync),
cents_source: &(impl ReadableVec<Height, Cents> + Sync),
exit: &Exit,
) -> Result<()> {
self.base
@@ -79,9 +79,9 @@ impl ValueFromHeightLast {
.height
.compute_rolling_ema(starting_height, window_starts, sats_source, exit)?;
self.base
.usd
.cents
.height
.compute_rolling_ema(starting_height, window_starts, dollars_source, exit)?;
.compute_rolling_ema(starting_height, window_starts, cents_source, exit)?;
Ok(())
}
}
@@ -41,7 +41,7 @@ impl ValueFromHeightLastRolling {
})
}
/// Compute sats height via closure, then USD from price, then rolling windows.
/// Compute sats height via closure, then cents from price, then rolling windows.
pub(crate) fn compute(
&mut self,
max_from: Height,
@@ -51,12 +51,12 @@ impl ValueFromHeightLastRolling {
compute_sats: impl FnOnce(&mut EagerVec<PcoVec<Height, Sats>>) -> Result<()>,
) -> Result<()> {
compute_sats(&mut self.value.sats)?;
self.value.compute_usd(prices, max_from, exit)?;
self.value.compute_cents(prices, max_from, exit)?;
self.rolling.compute_rolling_sum(
max_from,
windows,
&self.value.sats,
&self.value.usd,
&self.value.cents,
exit,
)?;
Ok(())
@@ -1,11 +1,11 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Dollars, Height, Sats, Version};
use brk_types::{Cents, Height, Sats, Version};
use vecdb::{Database, EagerVec, Exit, PcoVec, Rw, StorageMode};
use crate::{
indexes,
internal::{ByUnit, RollingSumByUnit, SatsToDollars, WindowStarts},
internal::{ByUnit, RollingSumByUnit, SatsToCents, WindowStarts},
prices,
};
@@ -50,25 +50,25 @@ impl ValueFromHeightSumCumulative {
.compute_cumulative(max_from, &self.base.sats.height, exit)?;
self.base
.usd
.cents
.height
.compute_binary::<Sats, Dollars, SatsToDollars>(
.compute_binary::<Sats, Cents, SatsToCents>(
max_from,
&self.base.sats.height,
&prices.price.usd.height,
&prices.price.cents.height,
exit,
)?;
self.cumulative
.usd
.cents
.height
.compute_cumulative(max_from, &self.base.usd.height, exit)?;
.compute_cumulative(max_from, &self.base.cents.height, exit)?;
self.sum.compute_rolling_sum(
max_from,
windows,
&self.base.sats.height,
&self.base.usd.height,
&self.base.cents.height,
exit,
)?;
@@ -1,17 +1,17 @@
//! Value type with height-level data only (no period-derived views).
//!
//! Stores sats and USD per height, plus a lazy btc transform.
//! Stores sats and cents per height, plus lazy btc and usd transforms.
//! Use when period views are unnecessary (e.g., rolling windows provide windowed data).
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Bitcoin, Dollars, Height, Sats, Version};
use brk_types::{Bitcoin, Cents, Dollars, Height, Sats, Version};
use vecdb::{
Database, EagerVec, Exit, ImportableVec, LazyVecFrom1, PcoVec, ReadableCloneableVec, Rw,
StorageMode,
};
use crate::{internal::{SatsToBitcoin, SatsToDollars}, prices};
use crate::{internal::{CentsUnsignedToDollars, SatsToBitcoin, SatsToCents}, prices};
const VERSION: Version = Version::TWO; // Match ValueFromHeightLast versioning
@@ -19,7 +19,8 @@ const VERSION: Version = Version::TWO; // Match ValueFromHeightLast versioning
pub struct ValueFromHeight<M: StorageMode = Rw> {
pub sats: M::Stored<EagerVec<PcoVec<Height, Sats>>>,
pub btc: LazyVecFrom1<Height, Bitcoin, Height, Sats>,
pub usd: M::Stored<EagerVec<PcoVec<Height, Dollars>>>,
pub cents: M::Stored<EagerVec<PcoVec<Height, Cents>>>,
pub usd: LazyVecFrom1<Height, Dollars, Height, Cents>,
}
impl ValueFromHeight {
@@ -36,22 +37,28 @@ impl ValueFromHeight {
v,
sats.read_only_boxed_clone(),
);
let usd = EagerVec::forced_import(db, &format!("{name}_usd"), v)?;
let cents: EagerVec<PcoVec<Height, Cents>> =
EagerVec::forced_import(db, &format!("{name}_cents"), v)?;
let usd = LazyVecFrom1::transformed::<CentsUnsignedToDollars>(
&format!("{name}_usd"),
v,
cents.read_only_boxed_clone(),
);
Ok(Self { sats, btc, usd })
Ok(Self { sats, btc, cents, usd })
}
/// Eagerly compute USD height values: sats[h] * price[h].
pub(crate) fn compute_usd(
/// Eagerly compute cents height values: sats[h] * price_cents[h] / 1e8.
pub(crate) fn compute_cents(
&mut self,
prices: &prices::Vecs,
max_from: Height,
exit: &Exit,
) -> Result<()> {
self.usd.compute_binary::<Sats, Dollars, SatsToDollars>(
self.cents.compute_binary::<Sats, Cents, SatsToCents>(
max_from,
&self.sats,
&prices.price.usd.height,
&prices.price.cents.height,
exit,
)?;
Ok(())
@@ -1,7 +1,7 @@
//! Lazy value type for Last pattern across all height-derived indexes.
use brk_traversable::Traversable;
use brk_types::{Bitcoin, Dollars, Sats, Version};
use brk_types::{Bitcoin, Cents, Dollars, Sats, Version};
use vecdb::UnaryTransform;
use crate::internal::{LazyHeightDerivedLast, ValueFromHeightLast};
@@ -40,7 +40,7 @@ impl LazyValueHeightDerivedLast {
&source.sats.rest,
);
let usd = LazyHeightDerivedLast::from_derived_computed::<DollarsTransform>(
let usd = LazyHeightDerivedLast::from_lazy::<DollarsTransform, Cents>(
&format!("{name}_usd"),
v,
&source.usd.rest,
@@ -10,7 +10,7 @@ use brk_types::{Height, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{Database, Exit, ReadableVec, Rw, StorageMode};
use brk_types::{Dollars, Sats};
use brk_types::{Cents, Sats};
use crate::{
indexes,
@@ -69,11 +69,11 @@ impl ValueFromHeightLastWindows {
max_from: Height,
windows: &WindowStarts<'_>,
sats_source: &impl ReadableVec<Height, Sats>,
usd_source: &impl ReadableVec<Height, Dollars>,
cents_source: &impl ReadableVec<Height, Cents>,
exit: &Exit,
) -> Result<()> {
for (w, starts) in self.0.as_mut_array().into_iter().zip(windows.as_array()) {
w.compute_rolling_sum(max_from, starts, sats_source, usd_source, exit)?;
w.compute_rolling_sum(max_from, starts, sats_source, cents_source, exit)?;
}
Ok(())
}
@@ -0,0 +1,13 @@
use brk_types::Cents;
use vecdb::BinaryTransform;
/// (Cents, Cents) -> Cents addition
/// Used for computing total = profit + loss
pub struct CentsPlus;
impl BinaryTransform<Cents, Cents, Cents> for CentsPlus {
#[inline(always)]
fn apply(lhs: Cents, rhs: Cents) -> Cents {
lhs + rhs
}
}
@@ -0,0 +1,12 @@
use brk_types::{CentsSigned, Dollars};
use vecdb::UnaryTransform;
/// CentsSigned -> Dollars (convert signed cents to dollars for display)
pub struct CentsSignedToDollars;
impl UnaryTransform<CentsSigned, Dollars> for CentsSignedToDollars {
#[inline(always)]
fn apply(cents: CentsSigned) -> Dollars {
cents.into()
}
}
@@ -0,0 +1,13 @@
use brk_types::Cents;
use vecdb::UnaryTransform;
/// Cents * (V/10) -> Cents (e.g., V=8 -> * 0.8, V=24 -> * 2.4)
pub struct CentsTimesTenths<const V: u16>;
impl<const V: u16> UnaryTransform<Cents, Cents> for CentsTimesTenths<V> {
#[inline(always)]
fn apply(c: Cents) -> Cents {
// Use u128 to avoid overflow: c * V / 10
Cents::from(c.as_u128() * V as u128 / 10)
}
}
@@ -1,13 +0,0 @@
use brk_types::Dollars;
use vecdb::BinaryTransform;
/// (Dollars, Dollars) -> Dollars addition
/// Used for computing total = profit + loss
pub struct DollarsPlus;
impl BinaryTransform<Dollars, Dollars, Dollars> for DollarsPlus {
#[inline(always)]
fn apply(lhs: Dollars, rhs: Dollars) -> Dollars {
lhs + rhs
}
}
@@ -1,12 +0,0 @@
use brk_types::Dollars;
use vecdb::UnaryTransform;
/// Dollars * (V/10) -> Dollars (e.g., V=8 -> * 0.8, V=24 -> * 2.4)
pub struct DollarsTimesTenths<const V: u16>;
impl<const V: u16> UnaryTransform<Dollars, Dollars> for DollarsTimesTenths<V> {
#[inline(always)]
fn apply(d: Dollars) -> Dollars {
d * (V as f64 / 10.0)
}
}
@@ -1,68 +1,80 @@
mod block_count_target;
mod cents_plus;
mod cents_signed_to_dollars;
mod cents_times_tenths;
mod cents_to_dollars;
mod cents_to_sats;
mod dollar_halve;
mod dollar_identity;
mod dollar_plus;
mod dollar_times_tenths;
mod dollars_to_sats_fract;
mod f32_identity;
mod neg_cents_to_dollars;
mod ohlc_cents_to_dollars;
mod ohlc_cents_to_sats;
mod percentage_cents_f32;
mod percentage_cents_signed_dollars_f32;
mod percentage_cents_signed_f32;
mod percentage_diff_close_cents;
mod percentage_diff_close_dollars;
mod percentage_dollars_f32;
mod percentage_dollars_f32_neg;
mod percentage_sats_f64;
mod percentage_u32_f32;
mod price_times_ratio;
mod price_times_ratio_cents;
mod ratio32;
mod ratio64;
mod ratio_cents64;
mod ratio_u64_f32;
mod return_f32_tenths;
mod return_i8;
mod return_u16;
mod sats_to_cents;
mod sat_halve;
mod sat_halve_to_bitcoin;
mod sat_identity;
mod sat_mask;
mod sat_to_bitcoin;
mod sats_to_dollars;
mod u16_to_years;
mod volatility_sqrt30;
mod volatility_sqrt365;
mod volatility_sqrt7;
pub use block_count_target::*;
pub use cents_plus::*;
pub use cents_signed_to_dollars::*;
pub use cents_times_tenths::*;
pub use cents_to_dollars::*;
pub use cents_to_sats::*;
pub use neg_cents_to_dollars::*;
pub use ohlc_cents_to_dollars::*;
pub use ohlc_cents_to_sats::*;
pub use percentage_cents_f32::*;
pub use percentage_cents_signed_dollars_f32::*;
pub use percentage_cents_signed_f32::*;
pub use dollar_halve::*;
pub use dollar_identity::*;
pub use dollar_plus::*;
pub use dollar_times_tenths::*;
pub use dollars_to_sats_fract::*;
pub use f32_identity::*;
pub use percentage_diff_close_cents::*;
pub use percentage_diff_close_dollars::*;
pub use percentage_dollars_f32::*;
pub use percentage_dollars_f32_neg::*;
pub use percentage_sats_f64::*;
pub use percentage_u32_f32::*;
pub use price_times_ratio::*;
pub use price_times_ratio_cents::*;
pub use ratio32::*;
pub use ratio64::*;
pub use ratio_cents64::*;
pub use ratio_u64_f32::*;
pub use return_f32_tenths::*;
pub use return_i8::*;
pub use return_u16::*;
pub use sats_to_cents::*;
pub use sat_halve::*;
pub use sat_halve_to_bitcoin::*;
pub use sat_identity::*;
pub use sat_mask::*;
pub use sat_to_bitcoin::*;
pub use sats_to_dollars::*;
pub use u16_to_years::*;
pub use volatility_sqrt7::*;
pub use volatility_sqrt30::*;
@@ -0,0 +1,13 @@
use brk_types::{Cents, Dollars};
use vecdb::UnaryTransform;
/// Cents -> -Dollars (negate after converting to dollars)
/// Avoids lazy-from-lazy by combining both transforms.
pub struct NegCentsUnsignedToDollars;
impl UnaryTransform<Cents, Dollars> for NegCentsUnsignedToDollars {
#[inline(always)]
fn apply(cents: Cents) -> Dollars {
-Dollars::from(cents)
}
}
@@ -0,0 +1,16 @@
use brk_types::{Cents, StoredF32};
use vecdb::BinaryTransform;
/// (Cents, Cents) -> StoredF32 percentage (a/b × 100)
pub struct PercentageCentsF32;
impl BinaryTransform<Cents, Cents, StoredF32> for PercentageCentsF32 {
#[inline(always)]
fn apply(numerator: Cents, denominator: Cents) -> StoredF32 {
if denominator == Cents::ZERO {
StoredF32::default()
} else {
StoredF32::from(numerator.inner() as f64 / denominator.inner() as f64 * 100.0)
}
}
}
@@ -0,0 +1,17 @@
use brk_types::{CentsSigned, Dollars, StoredF32};
use vecdb::BinaryTransform;
/// (CentsSigned, Dollars) -> StoredF32 percentage (a/b × 100)
/// For cross-type percentage when numerator is CentsSigned and denominator is Dollars.
pub struct PercentageCentsSignedDollarsF32;
impl BinaryTransform<CentsSigned, Dollars, StoredF32> for PercentageCentsSignedDollarsF32 {
#[inline(always)]
fn apply(numerator: CentsSigned, denominator: Dollars) -> StoredF32 {
if denominator == Dollars::ZERO {
StoredF32::default()
} else {
StoredF32::from(numerator.inner() as f64 / *denominator * 100.0)
}
}
}
@@ -0,0 +1,17 @@
use brk_types::{Cents, CentsSigned, StoredF32};
use vecdb::BinaryTransform;
/// (CentsSigned, Cents) -> StoredF32 percentage (a/b × 100)
/// For cross-type percentage when numerator is signed.
pub struct PercentageCentsSignedCentsF32;
impl BinaryTransform<CentsSigned, Cents, StoredF32> for PercentageCentsSignedCentsF32 {
#[inline(always)]
fn apply(numerator: CentsSigned, denominator: Cents) -> StoredF32 {
if denominator == Cents::ZERO {
StoredF32::default()
} else {
StoredF32::from(numerator.inner() as f64 / denominator.inner() as f64 * 100.0)
}
}
}
@@ -0,0 +1,17 @@
use brk_types::{Cents, StoredF32};
use vecdb::BinaryTransform;
/// (Cents, Cents) -> StoredF32 percentage difference ((a/b - 1) * 100)
pub struct PercentageDiffCents;
impl BinaryTransform<Cents, Cents, StoredF32> for PercentageDiffCents {
#[inline(always)]
fn apply(close: Cents, base: Cents) -> StoredF32 {
let base_f64 = f64::from(base);
if base_f64 == 0.0 {
StoredF32::default()
} else {
StoredF32::from((f64::from(close) / base_f64 - 1.0) * 100.0)
}
}
}
@@ -1,12 +0,0 @@
use brk_types::{Dollars, StoredF32};
use vecdb::BinaryTransform;
/// Dollars * StoredF32 -> Dollars (price × ratio)
pub struct PriceTimesRatio;
impl BinaryTransform<Dollars, StoredF32, Dollars> for PriceTimesRatio {
#[inline(always)]
fn apply(price: Dollars, ratio: StoredF32) -> Dollars {
price * ratio
}
}
@@ -0,0 +1,11 @@
use brk_types::{Cents, StoredF32};
use vecdb::BinaryTransform;
pub struct PriceTimesRatioCents;
impl BinaryTransform<Cents, StoredF32, Cents> for PriceTimesRatioCents {
#[inline(always)]
fn apply(price: Cents, ratio: StoredF32) -> Cents {
Cents::from(f64::from(price) * f64::from(ratio))
}
}
@@ -1,13 +0,0 @@
use brk_types::{Dollars, StoredF64};
use vecdb::BinaryTransform;
/// (Dollars, Dollars) -> StoredF64 ratio
/// Used for computing ratios like SOPR where f64 precision is needed.
pub struct Ratio64;
impl BinaryTransform<Dollars, Dollars, StoredF64> for Ratio64 {
#[inline(always)]
fn apply(numerator: Dollars, denominator: Dollars) -> StoredF64 {
numerator / denominator
}
}
@@ -0,0 +1,17 @@
use brk_types::{Cents, StoredF64};
use vecdb::BinaryTransform;
/// (Cents, Cents) -> StoredF64 ratio
/// Used for computing ratios like SOPR where f64 precision is needed.
pub struct RatioCents64;
impl BinaryTransform<Cents, Cents, StoredF64> for RatioCents64 {
#[inline(always)]
fn apply(numerator: Cents, denominator: Cents) -> StoredF64 {
if denominator == Cents::ZERO {
StoredF64::from(1.0)
} else {
StoredF64::from(numerator.inner() as f64 / denominator.inner() as f64)
}
}
}
@@ -0,0 +1,13 @@
use brk_types::{Cents, Sats};
use vecdb::BinaryTransform;
/// Sats × Cents → Cents (sats × price_cents / 1e8)
/// Uses u128 intermediate to avoid overflow.
pub struct SatsToCents;
impl BinaryTransform<Sats, Cents, Cents> for SatsToCents {
#[inline(always)]
fn apply(sats: Sats, price_cents: Cents) -> Cents {
Cents::from(sats.as_u128() * price_cents.as_u128() / Sats::ONE_BTC_U128)
}
}
@@ -1,12 +0,0 @@
use brk_types::{Dollars, Sats};
use vecdb::BinaryTransform;
/// Sats × Dollars → Dollars (price * sats)
pub struct SatsToDollars;
impl BinaryTransform<Sats, Dollars, Dollars> for SatsToDollars {
#[inline(always)]
fn apply(sats: Sats, price: Dollars) -> Dollars {
price * sats
}
}