global: snapshot

This commit is contained in:
nym21
2026-03-04 17:10:15 +01:00
parent 891f0dad9e
commit 9e23de4ba1
313 changed files with 9087 additions and 4918 deletions
@@ -1,21 +1,14 @@
//! Compute functions for aggregation - take optional vecs, compute what's needed.
//!
//! These functions replace the Option-based compute logic in flexible builders.
//! Each function takes optional mutable references and computes only for Some() vecs.
use brk_error::Result;
use brk_types::{CheckedSub, StoredU64};
use schemars::JsonSchema;
use vecdb::{
AnyStoredVec, AnyVec, EagerVec, Exit, WritableVec, ReadableVec, PcoVec, VecIndex,
VecValue,
AnyStoredVec, AnyVec, EagerVec, Exit, PcoVec, ReadableVec, VecIndex, VecValue, WritableVec,
};
use brk_types::get_percentile;
use crate::internal::ComputedVecValue;
/// Helper to validate and get starting index for a single vec
fn validate_and_start<I: VecIndex, T: ComputedVecValue + JsonSchema>(
vec: &mut EagerVec<PcoVec<I, T>>,
combined_version: vecdb::Version,
@@ -25,14 +18,6 @@ fn validate_and_start<I: VecIndex, T: ComputedVecValue + JsonSchema>(
Ok(current_start.min(I::from(vec.len())))
}
/// Compute aggregations from a source vec into target vecs.
///
/// This function computes all requested aggregations in a single pass when possible,
/// optimizing for the common case where multiple aggregations are needed.
///
/// The `skip_count` parameter allows skipping the first N items from ALL calculations.
/// This is useful for excluding coinbase transactions (which have 0 fee) from
/// fee/feerate aggregations.
#[allow(clippy::too_many_arguments)]
pub(crate) fn compute_aggregations<I, T, A>(
max_from: I,
@@ -97,7 +82,9 @@ where
let mut cumulative_val = cumulative.as_ref().map(|cumulative_vec| {
index.decremented().map_or(T::from(0_usize), |idx| {
cumulative_vec.collect_one_at(idx.to_usize()).unwrap_or(T::from(0_usize))
cumulative_vec
.collect_one_at(idx.to_usize())
.unwrap_or(T::from(0_usize))
})
});
@@ -106,7 +93,11 @@ where
let first_indexes_batch: Vec<A> = first_indexes.collect_range_at(start, fi_len);
let count_indexes_batch: Vec<StoredU64> = count_indexes.collect_range_at(start, fi_len);
first_indexes_batch.into_iter().zip(count_indexes_batch).enumerate().try_for_each(|(j, (first_index, count_index))| -> Result<()> {
first_indexes_batch
.into_iter()
.zip(count_indexes_batch)
.enumerate()
.try_for_each(|(j, (first_index, count_index))| -> Result<()> {
let idx = start + j;
let count = u64::from(count_index) as usize;
@@ -116,7 +107,9 @@ where
if let Some(ref mut first_vec) = first {
let f = if effective_count > 0 {
source.collect_one_at(effective_first_index.to_usize()).unwrap()
source
.collect_one_at(effective_first_index.to_usize())
.unwrap()
} else {
T::from(0_usize)
};
@@ -259,10 +252,19 @@ where
} else if needs_aggregates {
// Aggregates only (sum/average/cumulative) — no Vec allocation needed
let efi = effective_first_index.to_usize();
let (sum_val, len) = source.fold_range_at(efi, efi + effective_count, (T::from(0_usize), 0_usize), |(acc, cnt), val| (acc + val, cnt + 1));
let (sum_val, len) = source.fold_range_at(
efi,
efi + effective_count,
(T::from(0_usize), 0_usize),
|(acc, cnt), val| (acc + val, cnt + 1),
);
if let Some(ref mut average_vec) = average {
let avg = if len > 0 { sum_val / len } else { T::from(0_usize) };
let avg = if len > 0 {
sum_val / len
} else {
T::from(0_usize)
};
average_vec.truncate_push_at(idx, avg)?;
}
@@ -296,10 +298,6 @@ where
Ok(())
}
/// Compute distribution stats from a fixed n-block rolling window.
///
/// For each height `h`, aggregates all source items from blocks `max(0, h - n_blocks + 1)..=h`
/// and computes average, min, max, median, and percentiles across the full window.
#[allow(clippy::too_many_arguments)]
pub(crate) fn compute_aggregations_nblock_window<I, T, A>(
max_from: I,
@@ -322,11 +320,19 @@ where
T: ComputedVecValue + JsonSchema,
A: VecIndex + VecValue + CheckedSub<A>,
{
let combined_version =
source.version() + first_indexes.version() + count_indexes.version();
let combined_version = source.version() + first_indexes.version() + count_indexes.version();
let mut idx = max_from;
for vec in [&mut *min, &mut *max, &mut *average, &mut *median, &mut *pct10, &mut *pct25, &mut *pct75, &mut *pct90] {
for vec in [
&mut *min,
&mut *max,
&mut *average,
&mut *median,
&mut *pct10,
&mut *pct25,
&mut *pct75,
&mut *pct90,
] {
idx = validate_and_start(vec, combined_version, idx)?;
}
let index = idx;
@@ -362,7 +368,16 @@ where
let effective_count = range_end_usize.saturating_sub(range_start_usize);
if effective_count == 0 {
for vec in [&mut *min, &mut *max, &mut *average, &mut *median, &mut *pct10, &mut *pct25, &mut *pct75, &mut *pct90] {
for vec in [
&mut *min,
&mut *max,
&mut *average,
&mut *median,
&mut *pct10,
&mut *pct25,
&mut *pct75,
&mut *pct90,
] {
vec.truncate_push_at(idx, zero)?;
}
} else {
@@ -0,0 +1,52 @@
use brk_error::Result;
use brk_types::BasisPointsSigned16;
use vecdb::{EagerVec, Exit, PcoVec, ReadableVec, VecIndex, VecValue};
pub trait ComputeDrawdown<I: VecIndex> {
fn compute_drawdown<C, A>(
&mut self,
max_from: I,
current: &impl ReadableVec<I, C>,
ath: &impl ReadableVec<I, A>,
exit: &Exit,
) -> Result<()>
where
C: VecValue,
A: VecValue,
f64: From<C> + From<A>;
}
impl<I> ComputeDrawdown<I> for EagerVec<PcoVec<I, BasisPointsSigned16>>
where
I: VecIndex,
{
fn compute_drawdown<C, A>(
&mut self,
max_from: I,
current: &impl ReadableVec<I, C>,
ath: &impl ReadableVec<I, A>,
exit: &Exit,
) -> Result<()>
where
C: VecValue,
A: VecValue,
f64: From<C> + From<A>,
{
self.compute_transform2(
max_from,
current,
ath,
|(i, current, ath, _)| {
let ath_f64 = f64::from(ath);
let drawdown = if ath_f64 == 0.0 {
BasisPointsSigned16::default()
} else {
BasisPointsSigned16::from((f64::from(current) - ath_f64) / ath_f64)
};
(i, drawdown)
},
exit,
)?;
Ok(())
}
}
@@ -1,6 +1,12 @@
mod aggregation;
mod drawdown;
mod sliding_distribution;
mod sliding_median;
pub(crate) mod sliding_window;
mod tdigest;
pub(crate) use aggregation::*;
pub(crate) use drawdown::*;
pub(crate) use sliding_distribution::*;
pub(crate) use sliding_median::*;
pub(crate) use tdigest::*;
@@ -0,0 +1,158 @@
use brk_error::Result;
use vecdb::{
AnyStoredVec, AnyVec, EagerVec, Exit, PcoVec, PcoVecValue, ReadableVec, VecIndex, VecValue,
WritableVec,
};
use super::sliding_window::SlidingWindowSorted;
/// Compute all 8 rolling distribution stats (avg, min, max, p10, p25, median, p75, p90)
/// in a single sorted-vec pass per window.
#[allow(clippy::too_many_arguments)]
pub fn compute_rolling_distribution_from_starts<I, T, A>(
max_from: I,
window_starts: &impl ReadableVec<I, I>,
values: &impl ReadableVec<I, A>,
average_out: &mut EagerVec<PcoVec<I, T>>,
min_out: &mut EagerVec<PcoVec<I, T>>,
max_out: &mut EagerVec<PcoVec<I, T>>,
p10_out: &mut EagerVec<PcoVec<I, T>>,
p25_out: &mut EagerVec<PcoVec<I, T>>,
median_out: &mut EagerVec<PcoVec<I, T>>,
p75_out: &mut EagerVec<PcoVec<I, T>>,
p90_out: &mut EagerVec<PcoVec<I, T>>,
exit: &Exit,
) -> Result<()>
where
I: VecIndex,
T: PcoVecValue + From<f64>,
A: VecValue + Copy,
f64: From<A>,
{
let version = window_starts.version() + values.version();
for v in [
&mut *average_out,
&mut *min_out,
&mut *max_out,
&mut *p10_out,
&mut *p25_out,
&mut *median_out,
&mut *p75_out,
&mut *p90_out,
] {
v.validate_and_truncate(version, max_from)?;
}
let skip = [
average_out.len(),
min_out.len(),
max_out.len(),
p10_out.len(),
p25_out.len(),
median_out.len(),
p75_out.len(),
p90_out.len(),
]
.into_iter()
.min()
.unwrap();
let end = window_starts.len().min(values.len());
if skip >= end {
return Ok(());
}
let range_start = if skip > 0 {
window_starts.collect_one_at(skip - 1).unwrap().to_usize()
} else {
0
};
let partial_values: Vec<f64> = values
.collect_range_at(range_start, end)
.into_iter()
.map(|a| f64::from(a))
.collect();
let capacity = if skip > 0 && skip < end {
let first_start = window_starts.collect_one_at(skip).unwrap().to_usize();
(skip + 1).saturating_sub(first_start)
} else if !partial_values.is_empty() {
partial_values.len().min(1024)
} else {
0
};
let mut window = SlidingWindowSorted::with_capacity(capacity);
if skip > 0 {
window.reconstruct(&partial_values, range_start, skip);
}
let starts_batch = window_starts.collect_range_at(skip, end);
for (j, start) in starts_batch.into_iter().enumerate() {
let i = skip + j;
let v = partial_values[i - range_start];
let start_usize = start.to_usize();
window.advance(v, start_usize, &partial_values, range_start);
if window.is_empty() {
let zero = T::from(0.0);
for v in [
&mut *average_out,
&mut *min_out,
&mut *max_out,
&mut *p10_out,
&mut *p25_out,
&mut *median_out,
&mut *p75_out,
&mut *p90_out,
] {
v.checked_push_at(i, zero)?;
}
} else {
average_out.checked_push_at(i, T::from(window.average()))?;
min_out.checked_push_at(i, T::from(window.min()))?;
max_out.checked_push_at(i, T::from(window.max()))?;
p10_out.checked_push_at(i, T::from(window.percentile(0.10)))?;
p25_out.checked_push_at(i, T::from(window.percentile(0.25)))?;
median_out.checked_push_at(i, T::from(window.percentile(0.50)))?;
p75_out.checked_push_at(i, T::from(window.percentile(0.75)))?;
p90_out.checked_push_at(i, T::from(window.percentile(0.90)))?;
}
if average_out.batch_limit_reached() {
let _lock = exit.lock();
for v in [
&mut *average_out,
&mut *min_out,
&mut *max_out,
&mut *p10_out,
&mut *p25_out,
&mut *median_out,
&mut *p75_out,
&mut *p90_out,
] {
v.write()?;
}
}
}
// Final flush
let _lock = exit.lock();
for v in [
average_out,
min_out,
max_out,
p10_out,
p25_out,
median_out,
p75_out,
p90_out,
] {
v.write()?;
}
Ok(())
}
@@ -0,0 +1,90 @@
use brk_error::Result;
use vecdb::{
AnyVec, EagerVec, Exit, PcoVec, PcoVecValue, ReadableVec, VecIndex, VecValue, WritableVec,
};
use super::sliding_window::SlidingWindowSorted;
pub trait ComputeRollingMedianFromStarts<I: VecIndex, T> {
fn compute_rolling_median_from_starts<A>(
&mut self,
max_from: I,
window_starts: &impl ReadableVec<I, I>,
values: &impl ReadableVec<I, A>,
exit: &Exit,
) -> Result<()>
where
A: VecValue + Copy,
f64: From<A>;
}
impl<I, T> ComputeRollingMedianFromStarts<I, T> for EagerVec<PcoVec<I, T>>
where
I: VecIndex,
T: PcoVecValue + From<f64>,
{
fn compute_rolling_median_from_starts<A>(
&mut self,
max_from: I,
window_starts: &impl ReadableVec<I, I>,
values: &impl ReadableVec<I, A>,
exit: &Exit,
) -> Result<()>
where
A: VecValue + Copy,
f64: From<A>,
{
self.validate_and_truncate(window_starts.version() + values.version(), max_from)?;
self.repeat_until_complete(exit, |this| {
let skip = this.len();
let end = window_starts.len().min(values.len());
let range_start = if skip > 0 {
window_starts.collect_one_at(skip - 1).unwrap().to_usize()
} else {
0
};
let partial_values: Vec<f64> = values
.collect_range_at(range_start, end)
.into_iter()
.map(|a| f64::from(a))
.collect();
let capacity = if skip > 0 && skip < end {
let first_start = window_starts.collect_one_at(skip).unwrap().to_usize();
(skip + 1).saturating_sub(first_start)
} else if !partial_values.is_empty() {
partial_values.len().min(1024)
} else {
0
};
let mut window = SlidingWindowSorted::with_capacity(capacity);
if skip > 0 {
window.reconstruct(&partial_values, range_start, skip);
}
let starts_batch = window_starts.collect_range_at(skip, end);
for (j, start) in starts_batch.into_iter().enumerate() {
let i = skip + j;
let v = partial_values[i - range_start];
let start_usize = start.to_usize();
window.advance(v, start_usize, &partial_values, range_start);
let median = window.percentile(0.50);
this.checked_push_at(i, T::from(median))?;
if this.batch_limit_reached() {
break;
}
}
Ok(())
})?;
Ok(())
}
}
@@ -37,9 +37,11 @@ impl SortedBlocks {
}
// Find the block where value belongs: first block whose max >= value
let block_idx = self.blocks.iter().position(|b| {
*b.last().unwrap() >= value
}).unwrap_or(self.blocks.len() - 1);
let block_idx = self
.blocks
.iter()
.position(|b| *b.last().unwrap() >= value)
.unwrap_or(self.blocks.len() - 1);
let block = &mut self.blocks[block_idx];
let pos = block.partition_point(|a| *a < value);
@@ -131,7 +133,13 @@ impl SlidingWindowSorted {
}
/// Add a new value and remove all expired values up to `new_start`.
pub fn advance(&mut self, value: f64, new_start: usize, partial_values: &[f64], range_start: usize) {
pub fn advance(
&mut self,
value: f64,
new_start: usize,
partial_values: &[f64],
range_start: usize,
) {
self.running_sum += value;
self.sorted.insert(value);
@@ -159,12 +167,20 @@ impl SlidingWindowSorted {
#[inline]
pub fn min(&self) -> f64 {
if self.sorted.is_empty() { 0.0 } else { self.sorted.first() }
if self.sorted.is_empty() {
0.0
} else {
self.sorted.first()
}
}
#[inline]
pub fn max(&self) -> f64 {
if self.sorted.is_empty() { 0.0 } else { self.sorted.last() }
if self.sorted.is_empty() {
0.0
} else {
self.sorted.last()
}
}
/// Extract a percentile (0.0-1.0) using linear interpolation.
@@ -67,9 +67,11 @@ impl TDigest {
}
// Single binary search: unclamped position doubles as insert point
let search = self
.centroids
.binary_search_by(|c| c.mean.partial_cmp(&value).unwrap_or(std::cmp::Ordering::Equal));
let search = self.centroids.binary_search_by(|c| {
c.mean
.partial_cmp(&value)
.unwrap_or(std::cmp::Ordering::Equal)
});
let insert_pos = match search {
Ok(i) | Err(i) => i,
};
@@ -0,0 +1,26 @@
use std::path::Path;
use brk_error::Result;
use brk_traversable::Traversable;
use vecdb::{Database, PAGE_SIZE};
pub(crate) fn open_db(
parent_path: &Path,
db_name: &str,
page_multiplier: usize,
) -> Result<Database> {
let db = Database::open(&parent_path.join(db_name))?;
db.set_min_len(PAGE_SIZE * page_multiplier)?;
Ok(db)
}
pub(crate) fn finalize_db(db: &Database, traversable: &impl Traversable) -> Result<()> {
db.retain_regions(
traversable
.iter_any_exportable()
.flat_map(|v| v.region_names())
.collect(),
)?;
db.compact()?;
Ok(())
}
@@ -1,5 +1,3 @@
//! ComputedHeightDerived — sparse time periods + dense epochs (last value).
use brk_traversable::Traversable;
use brk_types::{
Day1, Day3, DifficultyEpoch, FromCoarserIndex, HalvingEpoch, Height, Hour1, Hour4, Hour12,
@@ -12,7 +10,7 @@ use vecdb::{
};
use crate::{
indexes, indexes_from,
indexes,
internal::{ComputedVecValue, NumericValue, PerPeriod},
};
@@ -41,7 +39,6 @@ pub struct ComputedHeightDerived<T>(
where
T: ComputedVecValue + PartialOrd + JsonSchema;
/// Already read-only (no StorageMode); cloning is sufficient.
impl<T> ReadOnlyClone for ComputedHeightDerived<T>
where
T: ComputedVecValue + PartialOrd + JsonSchema,
@@ -116,6 +113,22 @@ where
};
}
Self(indexes_from!(period, epoch))
Self(PerPeriod {
minute10: period!(minute10),
minute30: period!(minute30),
hour1: period!(hour1),
hour4: period!(hour4),
hour12: period!(hour12),
day1: period!(day1),
day3: period!(day3),
week1: period!(week1),
month1: period!(month1),
month3: period!(month3),
month6: period!(month6),
year1: period!(year1),
year10: period!(year10),
halvingepoch: epoch!(halvingepoch),
difficultyepoch: epoch!(difficultyepoch),
})
}
}
@@ -1,59 +1,20 @@
//! LazyHeightDerived — unary transform of height-derived last values.
use std::marker::PhantomData;
use brk_traversable::Traversable;
use brk_types::{
Day1, Day3, DifficultyEpoch, HalvingEpoch, Height, Hour1, Hour4, Hour12,
Minute10, Minute30, Month1, Month3, Month6, Version, Week1, Year1, Year10,
Day1, Day3, DifficultyEpoch, HalvingEpoch, Height, Hour1, Hour4, Hour12, Minute10, Minute30,
Month1, Month3, Month6, Version, Week1, Year1, Year10,
};
use derive_more::{Deref, DerefMut};
use schemars::JsonSchema;
use vecdb::{
LazyVecFrom1, ReadableBoxedVec, ReadableCloneableVec, UnaryTransform, VecIndex, VecValue,
};
use vecdb::{ReadableBoxedVec, ReadableCloneableVec, UnaryTransform, VecValue};
use crate::{
indexes, indexes_from,
indexes,
internal::{
ComputedFromHeight, ComputedHeightDerived, ComputedVecValue, NumericValue, PerPeriod,
},
};
#[derive(Clone, Deref, DerefMut, Traversable)]
#[traversable(transparent)]
pub struct LazyTransformLast<I, T, S1T = T>(pub LazyVecFrom1<I, T, I, S1T>)
where
I: VecIndex,
T: VecValue + PartialOrd + JsonSchema,
S1T: VecValue;
impl<I, T, S1T> LazyTransformLast<I, T, S1T>
where
I: VecIndex,
T: VecValue + PartialOrd + JsonSchema + 'static,
S1T: VecValue + JsonSchema,
{
fn from_boxed<F: UnaryTransform<S1T, T>>(
name: &str,
version: Version,
source: ReadableBoxedVec<I, S1T>,
) -> Self {
Self(LazyVecFrom1::transformed::<F>(name, version, source))
}
}
struct MapOption<F>(PhantomData<F>);
impl<F, S, T> UnaryTransform<Option<S>, Option<T>> for MapOption<F>
where
F: UnaryTransform<S, T>,
{
#[inline(always)]
fn apply(value: Option<S>) -> Option<T> {
value.map(F::apply)
}
}
use super::{LazyTransformLast, MapOption};
#[derive(Clone, Deref, DerefMut, Traversable)]
#[traversable(transparent)]
@@ -106,8 +67,7 @@ where
where
S1T: NumericValue,
{
let derived =
ComputedHeightDerived::forced_import(name, height_source, version, indexes);
let derived = ComputedHeightDerived::forced_import(name, height_source, version, indexes);
Self::from_derived_computed::<F>(name, version, &derived)
}
@@ -135,7 +95,23 @@ where
};
}
Self(indexes_from!(period, epoch))
Self(PerPeriod {
minute10: period!(minute10),
minute30: period!(minute30),
hour1: period!(hour1),
hour4: period!(hour4),
hour12: period!(hour12),
day1: period!(day1),
day3: period!(day3),
week1: period!(week1),
month1: period!(month1),
month3: period!(month3),
month6: period!(month6),
year1: period!(year1),
year10: period!(year10),
halvingepoch: epoch!(halvingepoch),
difficultyepoch: epoch!(difficultyepoch),
})
}
pub(crate) fn from_lazy<F, S2T>(
@@ -163,6 +139,22 @@ where
};
}
Self(indexes_from!(period, epoch))
Self(PerPeriod {
minute10: period!(minute10),
minute30: period!(minute30),
hour1: period!(hour1),
hour4: period!(hour4),
hour12: period!(hour12),
day1: period!(day1),
day3: period!(day3),
week1: period!(week1),
month1: period!(month1),
month3: period!(month3),
month6: period!(month6),
year1: period!(year1),
year10: period!(year10),
halvingepoch: epoch!(halvingepoch),
difficultyepoch: epoch!(difficultyepoch),
})
}
}
@@ -1,5 +1,3 @@
//! Lazy value type for Last pattern across all height-derived indexes.
use brk_traversable::Traversable;
use brk_types::{Bitcoin, Cents, Dollars, Sats, Version};
use vecdb::UnaryTransform;
@@ -15,7 +13,12 @@ pub struct LazyValueHeightDerived {
}
impl LazyValueHeightDerived {
pub(crate) fn from_block_source<SatsTransform, BitcoinTransform, CentsTransform, DollarsTransform>(
pub(crate) fn from_block_source<
SatsTransform,
BitcoinTransform,
CentsTransform,
DollarsTransform,
>(
name: &str,
source: &ValueFromHeight,
version: Version,
@@ -50,6 +53,11 @@ impl LazyValueHeightDerived {
&source.usd.rest,
);
Self { sats, btc, cents, usd }
Self {
sats,
btc,
cents,
usd,
}
}
}
@@ -0,0 +1,15 @@
use std::marker::PhantomData;
use vecdb::UnaryTransform;
pub struct MapOption<F>(PhantomData<F>);
impl<F, S, T> UnaryTransform<Option<S>, Option<T>> for MapOption<F>
where
F: UnaryTransform<S, T>,
{
#[inline(always)]
fn apply(value: Option<S>) -> Option<T> {
value.map(F::apply)
}
}
@@ -2,8 +2,12 @@ mod full;
mod last;
mod lazy_last;
mod lazy_value;
mod map_option;
mod transform_last;
pub use full::*;
pub use last::*;
pub use lazy_last::*;
pub use lazy_value::*;
pub use map_option::*;
pub use transform_last::*;
@@ -0,0 +1,29 @@
use brk_traversable::Traversable;
use derive_more::{Deref, DerefMut};
use schemars::JsonSchema;
use vecdb::{LazyVecFrom1, ReadableBoxedVec, UnaryTransform, VecIndex, VecValue};
use brk_types::Version;
#[derive(Clone, Deref, DerefMut, Traversable)]
#[traversable(transparent)]
pub struct LazyTransformLast<I, T, S1T = T>(pub LazyVecFrom1<I, T, I, S1T>)
where
I: VecIndex,
T: VecValue + PartialOrd + JsonSchema,
S1T: VecValue;
impl<I, T, S1T> LazyTransformLast<I, T, S1T>
where
I: VecIndex,
T: VecValue + PartialOrd + JsonSchema + 'static,
S1T: VecValue + JsonSchema,
{
pub(crate) fn from_boxed<F: UnaryTransform<S1T, T>>(
name: &str,
version: Version,
source: ReadableBoxedVec<I, S1T>,
) -> Self {
Self(LazyVecFrom1::transformed::<F>(name, version, source))
}
}
@@ -13,7 +13,9 @@ pub struct DistributionStats<A> {
}
impl<A> DistributionStats<A> {
pub const SUFFIXES: [&'static str; 8] = ["average", "min", "max", "p10", "p25", "median", "p75", "p90"];
pub const SUFFIXES: [&'static str; 8] = [
"average", "min", "max", "p10", "p25", "median", "p75", "p90",
];
pub fn try_from_fn<E>(
mut f: impl FnMut(&str) -> std::result::Result<A, E>,
@@ -31,7 +33,10 @@ impl<A> DistributionStats<A> {
}
/// Apply a fallible operation to each of the 8 fields.
pub fn try_for_each_mut(&mut self, mut f: impl FnMut(&mut A) -> brk_error::Result<()>) -> brk_error::Result<()> {
pub fn try_for_each_mut(
&mut self,
mut f: impl FnMut(&mut A) -> brk_error::Result<()>,
) -> brk_error::Result<()> {
f(&mut self.average)?;
f(&mut self.min)?;
f(&mut self.max)?;
@@ -1,14 +1,9 @@
//! EagerIndexes - newtype on PerPeriod with EagerVec<PcoVec<I, T>> per field.
//!
//! Used for data eagerly computed and stored per period during indexing,
//! such as timestamp (first value per period) and OHLC (first/min/max per period).
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{
Day1, Day3, DifficultyEpoch, HalvingEpoch, Height, Hour1, Hour4, Hour12,
Indexes, Minute10, Minute30, Month1, Month3, Month6, Version, Week1, Year1, Year10,
Day1, Day3, DifficultyEpoch, HalvingEpoch, Height, Hour1, Hour4, Hour12, Indexes, Minute10,
Minute30, Month1, Month3, Month6, Version, Week1, Year1, Year10,
};
use derive_more::{Deref, DerefMut};
use schemars::JsonSchema;
@@ -18,7 +13,7 @@ use vecdb::{
};
use crate::{
indexes, indexes_apply, indexes_from,
indexes,
internal::{ComputedVecValue, NumericValue, PerPeriod},
};
@@ -52,16 +47,31 @@ where
T: NumericValue + JsonSchema,
{
pub(crate) fn forced_import(db: &Database, name: &str, version: Version) -> Result<Self> {
macro_rules! period {
($idx:ident) => {
macro_rules! per_period {
() => {
ImportableVec::forced_import(db, name, version)?
};
}
Ok(Self(indexes_from!(period)))
Ok(Self(PerPeriod {
minute10: per_period!(),
minute30: per_period!(),
hour1: per_period!(),
hour4: per_period!(),
hour12: per_period!(),
day1: per_period!(),
day3: per_period!(),
week1: per_period!(),
month1: per_period!(),
month3: per_period!(),
month6: per_period!(),
year1: per_period!(),
year10: per_period!(),
halvingepoch: per_period!(),
difficultyepoch: per_period!(),
}))
}
/// Compute "first value per period" — for each period, looks up `source[first_height[period]]`.
pub(crate) fn compute_first(
&mut self,
starting_indexes: &Indexes,
@@ -74,7 +84,11 @@ where
macro_rules! period {
($field:ident) => {
self.0.$field.compute_indirect_sequential(
indexes.height.$field.collect_one(prev_height).unwrap_or_default(),
indexes
.height
.$field
.collect_one(prev_height)
.unwrap_or_default(),
&indexes.$field.first_height,
height_source,
exit,
@@ -82,12 +96,25 @@ where
};
}
indexes_apply!(period);
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);
period!(halvingepoch);
period!(difficultyepoch);
Ok(())
}
/// Compute "max value per period" — for each period, finds `max(source[first_height[period]..first_height[period+1]])`.
pub(crate) fn compute_max(
&mut self,
starting_indexes: &Indexes,
@@ -102,7 +129,11 @@ where
($field:ident) => {
compute_period_extremum(
&mut self.0.$field,
indexes.height.$field.collect_one(prev_height).unwrap_or_default(),
indexes
.height
.$field
.collect_one(prev_height)
.unwrap_or_default(),
&indexes.$field.first_height,
height_source,
src_len,
@@ -112,12 +143,25 @@ where
};
}
indexes_apply!(period);
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);
period!(halvingepoch);
period!(difficultyepoch);
Ok(())
}
/// Compute "min value per period" — for each period, finds `min(source[first_height[period]..first_height[period+1]])`.
pub(crate) fn compute_min(
&mut self,
starting_indexes: &Indexes,
@@ -132,7 +176,11 @@ where
($field:ident) => {
compute_period_extremum(
&mut self.0.$field,
indexes.height.$field.collect_one(prev_height).unwrap_or_default(),
indexes
.height
.$field
.collect_one(prev_height)
.unwrap_or_default(),
&indexes.$field.first_height,
height_source,
src_len,
@@ -142,16 +190,26 @@ where
};
}
indexes_apply!(period);
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);
period!(halvingepoch);
period!(difficultyepoch);
Ok(())
}
}
/// Compute per-period extremum (max or min) of height_source values.
///
/// Each period's range is `[fh[i]..fh[i+1])` of height_source.
/// Uses a cursor on height_source so each page is decompressed at most once.
fn compute_period_extremum<I: VecIndex, T: ComputedVecValue + JsonSchema>(
out: &mut EagerVec<PcoVec<I, T>>,
starting_index: I,
@@ -58,12 +58,8 @@ where
f64: From<T>,
{
compute_full(&mut self.full)?;
self.rolling.compute(
max_from,
windows,
&self.full.sum,
exit,
)?;
self.rolling
.compute(max_from, windows, &self.full.sum, exit)?;
Ok(())
}
}
@@ -1,5 +1,3 @@
//! ComputedFromHeight using only Last aggregation.
use brk_error::Result;
use brk_traversable::Traversable;
@@ -66,12 +64,8 @@ where
S2T: VecValue,
F: BinaryTransform<S1T, S2T, T>,
{
self.height.compute_binary::<S1T, S2T, F>(
max_from,
source1,
source2,
exit,
)?;
self.height
.compute_binary::<S1T, S2T, F>(max_from, source1, source2, exit)?;
Ok(())
}
}
@@ -40,12 +40,8 @@ impl ByUnit {
&sats,
);
let cents = ComputedFromHeight::forced_import(
db,
&format!("{name}_cents"),
version,
indexes,
)?;
let cents =
ComputedFromHeight::forced_import(db, &format!("{name}_cents"), version, indexes)?;
let usd = LazyFromHeight::from_computed::<CentsUnsignedToDollars>(
&format!("{name}_usd"),
@@ -6,8 +6,9 @@ use vecdb::{Database, Exit, ReadableVec, Rw, StorageMode};
use crate::{
indexes,
internal::{ByUnit, DistributionStats, WindowStarts, Windows},
traits::compute_rolling_distribution_from_starts,
internal::{
ByUnit, DistributionStats, WindowStarts, Windows, compute_rolling_distribution_from_starts,
},
};
/// One window slot: sum + 8 distribution stats, each a ByUnit.
@@ -43,19 +44,32 @@ impl RollingFullSlot {
cents_source: &impl ReadableVec<Height, Cents>,
exit: &Exit,
) -> Result<()> {
self.sum.sats.height.compute_rolling_sum(max_from, starts, sats_source, exit)?;
self.sum.cents.height.compute_rolling_sum(max_from, starts, cents_source, exit)?;
self.sum
.sats
.height
.compute_rolling_sum(max_from, starts, sats_source, exit)?;
self.sum
.cents
.height
.compute_rolling_sum(max_from, starts, cents_source, exit)?;
let d = &mut self.distribution;
macro_rules! compute_unit {
($unit:ident, $source:expr) => {
compute_rolling_distribution_from_starts(
max_from, starts, $source,
&mut d.average.$unit.height, &mut d.min.$unit.height,
&mut d.max.$unit.height, &mut d.pct10.$unit.height,
&mut d.pct25.$unit.height, &mut d.median.$unit.height,
&mut d.pct75.$unit.height, &mut d.pct90.$unit.height, exit,
max_from,
starts,
$source,
&mut d.average.$unit.height,
&mut d.min.$unit.height,
&mut d.max.$unit.height,
&mut d.pct10.$unit.height,
&mut d.pct25.$unit.height,
&mut d.median.$unit.height,
&mut d.pct75.$unit.height,
&mut d.pct90.$unit.height,
exit,
)?
};
}
@@ -23,7 +23,12 @@ impl RollingSumByUnit {
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
Ok(Self(Windows::<ByUnit>::forced_import(db, &format!("{name}_sum"), version, indexes)?))
Ok(Self(Windows::<ByUnit>::forced_import(
db,
&format!("{name}_sum"),
version,
indexes,
)?))
}
pub(crate) fn compute_rolling_sum(
@@ -35,8 +40,12 @@ impl RollingSumByUnit {
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.cents.height.compute_rolling_sum(max_from, *starts, cents_source, exit)?;
w.sats
.height
.compute_rolling_sum(max_from, *starts, sats_source, exit)?;
w.cents
.height
.compute_rolling_sum(max_from, *starts, cents_source, exit)?;
}
Ok(())
}
@@ -1,16 +1,14 @@
use brk_traversable::Traversable;
use brk_types::{
Day1, Day3, DifficultyEpoch, HalvingEpoch, Height, Hour1, Hour12, Hour4,
Minute10, Minute30, Month1, Month3, Month6, Version, Week1, Year1, Year10,
Day1, Day3, DifficultyEpoch, HalvingEpoch, Height, Hour1, Hour4, Hour12, Minute10, Minute30,
Month1, Month3, Month6, Version, Week1, Year1, Year10,
};
use schemars::JsonSchema;
use serde::Serialize;
use vecdb::{Formattable, ReadableCloneableVec, LazyVecFrom1, UnaryTransform, VecValue};
use vecdb::{Formattable, LazyVecFrom1, ReadableCloneableVec, UnaryTransform, VecValue};
use crate::indexes;
/// Lazy constant vecs for all index levels.
/// Uses const generic transforms to return the same value for every index.
#[derive(Clone, Traversable)]
#[traversable(merge)]
pub struct ConstantVecs<T>
@@ -36,7 +34,6 @@ where
}
impl<T: VecValue + Formattable + Serialize + JsonSchema> ConstantVecs<T> {
/// Create constant vecs using a transform that ignores input and returns a constant.
pub(crate) fn new<F>(name: &str, version: Version, indexes: &indexes::Vecs) -> Self
where
F: UnaryTransform<Height, T>
@@ -57,7 +54,7 @@ impl<T: VecValue + Formattable + Serialize + JsonSchema> ConstantVecs<T> {
+ UnaryTransform<DifficultyEpoch, T>,
{
macro_rules! period {
($idx:ident, $I:ty) => {
($idx:ident) => {
LazyVecFrom1::transformed::<F>(
name,
version,
@@ -67,26 +64,22 @@ impl<T: VecValue + Formattable + Serialize + JsonSchema> ConstantVecs<T> {
}
Self {
height: LazyVecFrom1::transformed::<F>(
name,
version,
indexes.height.identity.read_only_boxed_clone(),
),
minute10: period!(minute10, Minute10),
minute30: period!(minute30, Minute30),
hour1: period!(hour1, Hour1),
hour4: period!(hour4, Hour4),
hour12: period!(hour12, Hour12),
day1: period!(day1, Day1),
day3: period!(day3, Day3),
week1: period!(week1, Week1),
month1: period!(month1, Month1),
month3: period!(month3, Month3),
month6: period!(month6, Month6),
year1: period!(year1, Year1),
year10: period!(year10, Year10),
halvingepoch: period!(halvingepoch, HalvingEpoch),
difficultyepoch: period!(difficultyepoch, DifficultyEpoch),
height: period!(height),
minute10: period!(minute10),
minute30: period!(minute30),
hour1: period!(hour1),
hour4: period!(hour4),
hour12: period!(hour12),
day1: period!(day1),
day3: period!(day3),
week1: period!(week1),
month1: period!(month1),
month3: period!(month3),
month6: period!(month6),
year1: period!(year1),
year10: period!(year10),
halvingepoch: period!(halvingepoch),
difficultyepoch: period!(difficultyepoch),
}
}
}
@@ -38,12 +38,8 @@ impl<C: CentsType> FiatFromHeight<C> {
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
let cents = ComputedFromHeight::forced_import(
db,
&format!("{name}_cents"),
version,
indexes,
)?;
let cents =
ComputedFromHeight::forced_import(db, &format!("{name}_cents"), version, indexes)?;
let usd = LazyFromHeight::from_computed::<C::ToDollars>(
&format!("{name}_usd"),
version,
@@ -1,5 +1,3 @@
//! Lazy unary transform from height with Last aggregation.
use brk_traversable::Traversable;
use brk_types::{Height, Version};
use derive_more::{Deref, DerefMut};
@@ -75,7 +73,11 @@ where
S2T: ComputedVecValue + JsonSchema,
{
Self {
height: LazyVecFrom1::transformed::<F>(name, version, source.height.read_only_boxed_clone()),
height: LazyVecFrom1::transformed::<F>(
name,
version,
source.height.read_only_boxed_clone(),
),
rest: Box::new(LazyHeightDerived::from_lazy::<F, S2T>(
name,
version,
@@ -1,12 +1,13 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Height, StoredF32, Version};
use vecdb::{BinaryTransform, Database, Exit, ReadableCloneableVec, ReadableVec, Rw, StorageMode, VecValue};
use vecdb::{
BinaryTransform, Database, Exit, ReadableCloneableVec, ReadableVec, Rw, StorageMode, VecValue,
};
use crate::{
indexes,
internal::BpsType,
traits::ComputeDrawdown,
internal::{BpsType, ComputeDrawdown},
};
use super::{ComputedFromHeight, LazyFromHeight};
@@ -47,7 +48,11 @@ impl<B: BpsType> PercentFromHeight<B> {
&bps,
);
Ok(Self { bps, ratio, percent })
Ok(Self {
bps,
ratio,
percent,
})
}
pub(crate) fn compute_binary<S1T, S2T, F>(
@@ -62,7 +67,8 @@ impl<B: BpsType> PercentFromHeight<B> {
S2T: VecValue,
F: BinaryTransform<S1T, S2T, B>,
{
self.bps.compute_binary::<S1T, S2T, F>(max_from, source1, source2, exit)
self.bps
.compute_binary::<S1T, S2T, F>(max_from, source1, source2, exit)
}
pub(crate) fn compute_drawdown<C, A>(
@@ -78,6 +84,8 @@ impl<B: BpsType> PercentFromHeight<B> {
f64: From<C> + From<A>,
vecdb::EagerVec<vecdb::PcoVec<Height, B>>: ComputeDrawdown<Height>,
{
self.bps.height.compute_drawdown(max_from, current, ath, exit)
self.bps
.height
.compute_drawdown(max_from, current, ath, exit)
}
}
@@ -3,7 +3,10 @@ use brk_traversable::Traversable;
use brk_types::{Height, StoredF32, Version};
use vecdb::{Database, EagerVec, Exit, PcoVec, ReadableCloneableVec, Rw, StorageMode};
use crate::{indexes, internal::{BpsType, WindowStarts}};
use crate::{
indexes,
internal::{BpsType, WindowStarts},
};
use super::{ComputedFromHeightDistribution, LazyFromHeight};
@@ -22,7 +25,12 @@ impl<B: BpsType> PercentFromHeightDistribution<B> {
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
let bps = ComputedFromHeightDistribution::forced_import(db, &format!("{name}_bps"), version, indexes)?;
let bps = ComputedFromHeightDistribution::forced_import(
db,
&format!("{name}_bps"),
version,
indexes,
)?;
let ratio = LazyFromHeight::from_height_source::<B::ToRatio>(
&format!("{name}_ratio"),
@@ -38,7 +46,11 @@ impl<B: BpsType> PercentFromHeightDistribution<B> {
indexes,
);
Ok(Self { bps, ratio, percent })
Ok(Self {
bps,
ratio,
percent,
})
}
pub(crate) fn compute(
@@ -109,7 +109,9 @@ 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.cents.height.validate_computed_version_or_reset(version)?;
vec.cents
.height
.validate_computed_version_or_reset(version)?;
}
Ok(())
}
@@ -120,10 +122,7 @@ impl ReadOnlyClone for PercentilesVecs {
fn read_only_clone(&self) -> Self::ReadOnly {
PercentilesVecs {
vecs: self
.vecs
.each_ref()
.map(|v| v.read_only_clone()),
vecs: self.vecs.each_ref().map(|v| v.read_only_clone()),
}
}
}
@@ -143,8 +142,6 @@ where
}
fn iter_any_exportable(&self) -> impl Iterator<Item = &dyn AnyExportableVec> {
self.vecs
.iter()
.flat_map(|p| p.iter_any_exportable())
self.vecs.iter().flat_map(|p| p.iter_any_exportable())
}
}
@@ -1,14 +1,17 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{BasisPoints32, Cents, Height, Indexes, StoredF32, Version};
use vecdb::{AnyStoredVec, AnyVec, Database, EagerVec, Exit, PcoVec, ReadableVec, Rw, StorageMode, VecIndex, WritableVec};
use vecdb::{
AnyStoredVec, AnyVec, Database, EagerVec, Exit, PcoVec, ReadableVec, Rw, StorageMode, VecIndex,
WritableVec,
};
use crate::{
blocks, indexes,
internal::{ComputedFromHeightStdDevExtended, Price, TDigest},
};
use super::{ComputedFromHeightRatio, super::ComputedFromHeight};
use super::{super::ComputedFromHeight, ComputedFromHeightRatio};
#[derive(Traversable)]
pub struct ComputedFromHeightRatioExtension<M: StorageMode = Rw> {
@@ -100,7 +103,6 @@ impl ComputedFromHeightRatioExtension {
})
}
/// Compute extended ratio metrics from an externally-provided ratio source.
pub(crate) fn compute_rest(
&mut self,
blocks: &blocks::Vecs,
@@ -124,11 +126,10 @@ impl ComputedFromHeightRatioExtension {
)?;
let ratio_version = ratio_source.version();
self.mut_pct_vecs()
.try_for_each(|v| -> Result<()> {
v.validate_computed_version_or_reset(ratio_version)?;
Ok(())
})?;
self.mut_pct_vecs().try_for_each(|v| -> Result<()> {
v.validate_computed_version_or_reset(ratio_version)?;
Ok(())
})?;
let starting_height = self
.mut_pct_vecs()
@@ -177,19 +178,22 @@ impl ComputedFromHeightRatioExtension {
{
let _lock = exit.lock();
self.mut_pct_vecs()
.try_for_each(|v| v.flush())?;
self.mut_pct_vecs().try_for_each(|v| v.flush())?;
}
// Compute stddev at height level
for sd in [&mut self.ratio_sd, &mut self.ratio_sd_4y, &mut self.ratio_sd_2y, &mut self.ratio_sd_1y] {
for sd in [
&mut self.ratio_sd,
&mut self.ratio_sd_4y,
&mut self.ratio_sd_2y,
&mut self.ratio_sd_1y,
] {
sd.compute_all(blocks, starting_indexes, exit, ratio_source)?;
}
Ok(())
}
/// Compute cents ratio bands: cents_band = metric_price_cents * ratio_percentile
pub(crate) fn compute_cents_bands(
&mut self,
starting_indexes: &Indexes,
@@ -219,7 +223,12 @@ impl ComputedFromHeightRatioExtension {
compute_band!(ratio_pct1_price, &self.ratio_pct1.bps.height);
// Stddev cents bands
for sd in [&mut self.ratio_sd, &mut self.ratio_sd_4y, &mut self.ratio_sd_2y, &mut self.ratio_sd_1y] {
for sd in [
&mut self.ratio_sd,
&mut self.ratio_sd_4y,
&mut self.ratio_sd_2y,
&mut self.ratio_sd_1y,
] {
sd.compute_cents_bands(starting_indexes, metric_price, exit)?;
}
@@ -53,7 +53,6 @@ impl ComputedFromHeightRatio {
Ok(Self { bps, ratio })
}
/// Compute ratio = close_price / metric_price at height level (both in cents)
pub(crate) fn compute_ratio(
&mut self,
starting_indexes: &Indexes,
@@ -77,7 +77,14 @@ impl ComputedFromHeightStdDevExtended {
}
Ok(Self {
base: ComputedFromHeightStdDev::forced_import(db, name, period, days, parent_version, indexes)?,
base: ComputedFromHeightStdDev::forced_import(
db,
name,
period,
days,
parent_version,
indexes,
)?,
zscore: import!("zscore"),
p0_5sd: import!("p0_5sd"),
p1sd: import!("p1sd"),
@@ -162,7 +169,9 @@ impl ComputedFromHeightStdDevExtended {
.height
.collect_range_at(start, self.base.sd.height.len());
const MULTIPLIERS: [f32; 12] = [0.5, 1.0, 1.5, 2.0, 2.5, 3.0, -0.5, -1.0, -1.5, -2.0, -2.5, -3.0];
const MULTIPLIERS: [f32; 12] = [
0.5, 1.0, 1.5, 2.0, 2.5, 3.0, -0.5, -1.0, -1.5, -2.0, -2.5, -3.0,
];
let band_vecs: Vec<_> = self.mut_band_height_vecs().collect();
for (vec, mult) in band_vecs.into_iter().zip(MULTIPLIERS) {
for (offset, _) in source_data.iter().enumerate() {
@@ -199,7 +208,6 @@ impl ComputedFromHeightStdDevExtended {
Ok(())
}
/// Compute cents price bands: cents_band = metric_price_cents * band_ratio
pub(crate) fn compute_cents_bands(
&mut self,
starting_indexes: &Indexes,
@@ -38,18 +38,9 @@ impl ComputedFromHeightStdDev {
let version = parent_version + Version::TWO;
let p = period_suffix(period);
let sma = ComputedFromHeight::forced_import(
db,
&format!("{name}_sma{p}"),
version,
indexes,
)?;
let sd = ComputedFromHeight::forced_import(
db,
&format!("{name}_sd{p}"),
version,
indexes,
)?;
let sma =
ComputedFromHeight::forced_import(db, &format!("{name}_sma{p}"), version, indexes)?;
let sd = ComputedFromHeight::forced_import(db, &format!("{name}_sd{p}"), version, indexes)?;
Ok(Self { days, sma, sd })
}
@@ -5,8 +5,9 @@ use derive_more::{Deref, DerefMut};
use vecdb::{Database, Exit, ReadableVec, Rw, StorageMode};
use crate::{
indexes, prices,
indexes,
internal::{ByUnit, SatsToCents},
prices,
};
#[derive(Deref, DerefMut, Traversable)]
@@ -74,14 +75,18 @@ impl ValueFromHeight {
cents_source: &(impl ReadableVec<Height, Cents> + Sync),
exit: &Exit,
) -> Result<()> {
self.base
.sats
.height
.compute_rolling_ema(starting_height, window_starts, sats_source, exit)?;
self.base
.cents
.height
.compute_rolling_ema(starting_height, window_starts, cents_source, exit)?;
self.base.sats.height.compute_rolling_ema(
starting_height,
window_starts,
sats_source,
exit,
)?;
self.base.cents.height.compute_rolling_ema(
starting_height,
window_starts,
cents_source,
exit,
)?;
Ok(())
}
}
@@ -7,9 +7,7 @@ use vecdb::{Database, Exit, ReadableCloneableVec, ReadableVec, Rw, StorageMode};
use crate::{
indexes,
internal::{
CentsSignedToDollars, ComputedFromHeight, LazyFromHeight, SatsSignedToBitcoin,
},
internal::{CentsSignedToDollars, ComputedFromHeight, LazyFromHeight, SatsSignedToBitcoin},
};
/// Change values indexed by height - sats (stored), btc (lazy), cents (stored), usd (lazy).
@@ -37,12 +35,8 @@ impl ValueFromHeightChange {
&sats,
);
let cents = ComputedFromHeight::forced_import(
db,
&format!("{name}_cents"),
version,
indexes,
)?;
let cents =
ComputedFromHeight::forced_import(db, &format!("{name}_cents"), version, indexes)?;
let usd = LazyFromHeight::from_computed::<CentsSignedToDollars>(
&format!("{name}_usd"),
@@ -51,7 +45,12 @@ impl ValueFromHeightChange {
&cents,
);
Ok(Self { sats, btc, cents, usd })
Ok(Self {
sats,
btc,
cents,
usd,
})
}
/// Compute rolling change for both sats and cents in one call.
@@ -63,12 +62,18 @@ impl ValueFromHeightChange {
cents_source: &(impl ReadableVec<Height, Cents> + Sync),
exit: &Exit,
) -> Result<()> {
self.sats
.height
.compute_rolling_change(starting_height, window_starts, sats_source, exit)?;
self.cents
.height
.compute_rolling_change(starting_height, window_starts, cents_source, exit)?;
self.sats.height.compute_rolling_change(
starting_height,
window_starts,
sats_source,
exit,
)?;
self.cents.height.compute_rolling_change(
starting_height,
window_starts,
cents_source,
exit,
)?;
Ok(())
}
}
@@ -43,14 +43,12 @@ impl ValueFromHeightCumulative {
.height
.compute_cumulative(max_from, &self.base.sats.height, exit)?;
self.base
.cents
.compute_binary::<Sats, Cents, SatsToCents>(
max_from,
&self.base.sats.height,
&prices.price.cents.height,
exit,
)?;
self.base.cents.compute_binary::<Sats, Cents, SatsToCents>(
max_from,
&self.base.sats.height,
&prices.price.cents.height,
exit,
)?;
self.cumulative
.cents
@@ -20,7 +20,12 @@ pub struct LazyValueFromHeight {
}
impl LazyValueFromHeight {
pub(crate) fn from_block_source<SatsTransform, BitcoinTransform, CentsTransform, DollarsTransform>(
pub(crate) fn from_block_source<
SatsTransform,
BitcoinTransform,
CentsTransform,
DollarsTransform,
>(
name: &str,
source: &ValueFromHeight,
version: Version,
@@ -31,14 +36,23 @@ impl LazyValueFromHeight {
CentsTransform: UnaryTransform<Cents, Cents>,
DollarsTransform: UnaryTransform<Dollars, Dollars>,
{
let height =
LazyValue::from_block_source::<SatsTransform, BitcoinTransform, CentsTransform, DollarsTransform>(name, source, version);
let height = LazyValue::from_block_source::<
SatsTransform,
BitcoinTransform,
CentsTransform,
DollarsTransform,
>(name, source, version);
let rest =
LazyValueHeightDerived::from_block_source::<SatsTransform, BitcoinTransform, CentsTransform, DollarsTransform>(
name, source, version,
);
let rest = LazyValueHeightDerived::from_block_source::<
SatsTransform,
BitcoinTransform,
CentsTransform,
DollarsTransform,
>(name, source, version);
Self { height, rest: Box::new(rest) }
Self {
height,
rest: Box::new(rest),
}
}
}
@@ -11,7 +11,7 @@ use vecdb::{Database, EagerVec, Exit, PcoVec, Rw, StorageMode};
use crate::{
indexes,
internal::{ValueFromHeightWindows, Value, WindowStarts},
internal::{Value, ValueFromHeightWindows, WindowStarts},
prices,
};
@@ -30,11 +30,7 @@ impl<T> BlockRollingDistribution<T>
where
T: NumericValue + JsonSchema,
{
pub(crate) fn forced_import(
db: &Database,
name: &str,
version: Version,
) -> Result<Self> {
pub(crate) fn forced_import(db: &Database, name: &str, version: Version) -> Result<Self> {
Ok(Self {
_6b: Distribution::forced_import(db, &format!("{name}_6b"), version)?,
})
@@ -74,14 +70,7 @@ where
T: Copy + Ord + From<f64> + Default,
f64: From<T>,
{
self.derive_from_with_skip(
indexer,
indexes,
starting_indexes,
txindex_source,
exit,
0,
)
self.derive_from_with_skip(indexer, indexes, starting_indexes, txindex_source, exit, 0)
}
/// Derive from source, skipping first N transactions per block from per-block stats.
@@ -1,5 +1,3 @@
//! LazyFromTxDistribution - lazy txindex source + computed distribution.
use brk_error::Result;
use brk_indexer::Indexer;
use brk_traversable::Traversable;
@@ -55,12 +53,7 @@ where
f64: From<T>,
LazyVecFrom2<TxIndex, T, TxIndex, S1, TxIndex, S2>: ReadableVec<TxIndex, T>,
{
self.distribution.derive_from(
indexer,
indexes,
starting_indexes,
&self.txindex,
exit,
)
self.distribution
.derive_from(indexer, indexes, starting_indexes, &self.txindex, exit)
}
}
@@ -1,86 +0,0 @@
//! Base generic struct with 15 type parameters — one per time period/epoch index.
//!
//! Foundation for all per-index types. Replaces the repetitive 15-field pattern
//! found throughout height_derived types.
use brk_traversable::Traversable;
#[derive(Clone, Traversable)]
#[traversable(merge)]
pub struct PerPeriod<M10, M30, H1, H4, H12, D1, D3, W1, Mo1, Mo3, Mo6, Y1, Y10, HE, DE> {
pub minute10: M10,
pub minute30: M30,
pub hour1: H1,
pub hour4: H4,
pub hour12: H12,
pub day1: D1,
pub day3: D3,
pub week1: W1,
pub month1: Mo1,
pub month3: Mo3,
pub month6: Mo6,
pub year1: Y1,
pub year10: Y10,
pub halvingepoch: HE,
pub difficultyepoch: DE,
}
/// Helper macro to construct a `PerPeriod` by applying a macro to each field.
///
/// Usage:
/// ```ignore
/// indexes_from!(period, epoch)
/// ```
/// where `period!($field)` and `epoch!($field)` are locally-defined macros.
#[macro_export]
macro_rules! indexes_from {
($period:ident, $epoch:ident) => {
$crate::internal::PerPeriod {
minute10: $period!(minute10),
minute30: $period!(minute30),
hour1: $period!(hour1),
hour4: $period!(hour4),
hour12: $period!(hour12),
day1: $period!(day1),
day3: $period!(day3),
week1: $period!(week1),
month1: $period!(month1),
month3: $period!(month3),
month6: $period!(month6),
year1: $period!(year1),
year10: $period!(year10),
halvingepoch: $epoch!(halvingepoch),
difficultyepoch: $epoch!(difficultyepoch),
}
};
// Variant where period and epoch use the same macro
($m:ident) => {
$crate::indexes_from!($m, $m)
};
}
/// Imperative counterpart to `indexes_from!` — calls `$period!(field)` for each
/// period field and `$epoch!(field)` for each epoch field.
#[macro_export]
macro_rules! indexes_apply {
($period:ident, $epoch:ident) => {
$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);
$epoch!(halvingepoch);
$epoch!(difficultyepoch);
};
($m:ident) => {
$crate::indexes_apply!($m, $m)
};
}
@@ -1,21 +1,13 @@
//! LazyEagerIndexes - lazy per-period transform of EagerIndexes.
//!
//! Used for lazy currency transforms (e.g., cents→dollars, cents→sats)
//! of eagerly computed per-period data like OHLC.
use brk_traversable::Traversable;
use brk_types::{
Day1, Day3, DifficultyEpoch, HalvingEpoch, Hour1, Hour4, Hour12,
Minute10, Minute30, Month1, Month3, Month6, Version, Week1, Year1, Year10,
Day1, Day3, DifficultyEpoch, HalvingEpoch, Hour1, Hour4, Hour12, Minute10, Minute30, Month1,
Month3, Month6, Version, Week1, Year1, Year10,
};
use derive_more::{Deref, DerefMut};
use schemars::JsonSchema;
use vecdb::{LazyVecFrom1, ReadableCloneableVec, UnaryTransform};
use crate::{
indexes_from,
internal::{ComputedVecValue, EagerIndexes, PerPeriod},
};
use crate::internal::{ComputedVecValue, EagerIndexes, PerPeriod};
#[derive(Clone, Deref, DerefMut, Traversable)]
#[traversable(transparent)]
@@ -48,7 +40,6 @@ where
T: ComputedVecValue + PartialOrd + JsonSchema,
S: ComputedVecValue + PartialOrd + JsonSchema,
{
/// Create lazy per-period transforms from an EagerIndexes source.
pub(crate) fn from_eager_indexes<Transform: UnaryTransform<S, T>>(
name: &str,
version: Version,
@@ -64,6 +55,22 @@ where
};
}
Self(indexes_from!(period))
Self(PerPeriod {
minute10: period!(minute10),
minute30: period!(minute30),
hour1: period!(hour1),
hour4: period!(hour4),
hour12: period!(hour12),
day1: period!(day1),
day3: period!(day3),
week1: period!(week1),
month1: period!(month1),
month3: period!(month3),
month6: period!(month6),
year1: period!(year1),
year10: period!(year10),
halvingepoch: period!(halvingepoch),
difficultyepoch: period!(difficultyepoch),
})
}
}
+18 -7
View File
@@ -1,8 +1,6 @@
//! Fully lazy value type.
use brk_traversable::Traversable;
use brk_types::{Bitcoin, Cents, Dollars, Height, Sats, Version};
use vecdb::{ReadableCloneableVec, LazyVecFrom1, UnaryTransform, VecIndex};
use vecdb::{LazyVecFrom1, ReadableCloneableVec, UnaryTransform, VecIndex};
use crate::internal::ValueFromHeight;
@@ -18,7 +16,12 @@ pub struct LazyValue<I: VecIndex> {
}
impl LazyValue<Height> {
pub(crate) fn from_block_source<SatsTransform, BitcoinTransform, CentsTransform, DollarsTransform>(
pub(crate) fn from_block_source<
SatsTransform,
BitcoinTransform,
CentsTransform,
DollarsTransform,
>(
name: &str,
source: &ValueFromHeight,
version: Version,
@@ -29,8 +32,11 @@ impl LazyValue<Height> {
CentsTransform: UnaryTransform<Cents, Cents>,
DollarsTransform: UnaryTransform<Dollars, Dollars>,
{
let sats =
LazyVecFrom1::transformed::<SatsTransform>(name, version, source.sats.height.read_only_boxed_clone());
let sats = LazyVecFrom1::transformed::<SatsTransform>(
name,
version,
source.sats.height.read_only_boxed_clone(),
);
let btc = LazyVecFrom1::transformed::<BitcoinTransform>(
&format!("{name}_btc"),
@@ -50,6 +56,11 @@ impl LazyValue<Height> {
source.usd.height.read_only_boxed_clone(),
);
Self { sats, btc, cents, usd }
Self {
sats,
btc,
cents,
usd,
}
}
}
+7 -5
View File
@@ -1,31 +1,33 @@
pub(crate) mod algo;
mod aggregate;
pub(crate) mod algo;
mod db_utils;
mod derived;
mod distribution_stats;
mod eager_indexes;
mod emas;
mod from_height;
mod from_tx;
mod indexes;
mod lazy_eager_indexes;
mod lazy_value;
mod per_period;
mod rolling;
mod traits;
mod transform;
pub mod transform;
mod value;
mod windows;
pub(crate) use algo::*;
pub(crate) use aggregate::*;
pub(crate) use algo::*;
pub(crate) use db_utils::*;
pub(crate) use derived::*;
pub(crate) use distribution_stats::*;
pub(crate) use eager_indexes::*;
pub(crate) use emas::*;
pub(crate) use from_height::*;
pub(crate) use from_tx::*;
pub(crate) use indexes::*;
pub(crate) use lazy_eager_indexes::*;
pub(crate) use lazy_value::*;
pub(crate) use per_period::*;
pub(crate) use rolling::*;
pub(crate) use traits::*;
pub use transform::*;
@@ -0,0 +1,21 @@
use brk_traversable::Traversable;
#[derive(Clone, Traversable)]
#[traversable(merge)]
pub struct PerPeriod<M10, M30, H1, H4, H12, D1, D3, W1, Mo1, Mo3, Mo6, Y1, Y10, HE, DE> {
pub minute10: M10,
pub minute30: M30,
pub hour1: H1,
pub hour4: H4,
pub hour12: H12,
pub day1: D1,
pub day3: D3,
pub week1: W1,
pub month1: Mo1,
pub month3: Mo3,
pub month6: Mo6,
pub year1: Y1,
pub year10: Y10,
pub halvingepoch: HE,
pub difficultyepoch: DE,
}
@@ -1,8 +1,3 @@
//! RollingDistribution - 8 distribution stats, each a RollingWindows.
//!
//! Computes average, min, max, p10, p25, median, p75, p90 rolling windows
//! from a single source vec in a single sorted-vec pass per window.
use brk_error::Result;
use brk_traversable::Traversable;
@@ -13,11 +8,12 @@ use vecdb::{Database, Exit, ReadableVec, Rw, StorageMode};
use crate::{
indexes,
internal::{ComputedVecValue, DistributionStats, NumericValue, RollingWindows, WindowStarts},
traits::compute_rolling_distribution_from_starts,
internal::{
ComputedVecValue, DistributionStats, NumericValue, RollingWindows, WindowStarts,
compute_rolling_distribution_from_starts,
},
};
/// 8 distribution stats × 4 windows = 32 stored height vecs, each with 17 index views.
#[derive(Deref, DerefMut, Traversable)]
#[traversable(transparent)]
pub struct RollingDistribution<T, M: StorageMode = Rw>(pub DistributionStats<RollingWindows<T, M>>)
@@ -39,12 +35,6 @@ where
})?))
}
/// Compute all 8 distribution stats across all 4 windows from a single source.
///
/// Uses a single sorted-vec pass per window that extracts all 8 stats:
/// - average: running sum / count
/// - min/max: first/last of sorted vec
/// - p10/p25/median/p75/p90: percentile interpolation from sorted vec
pub(crate) fn compute_distribution(
&mut self,
max_from: Height,
@@ -59,11 +49,18 @@ where
macro_rules! compute_window {
($w:ident) => {
compute_rolling_distribution_from_starts(
max_from, windows.$w, source,
&mut self.0.average.$w.height, &mut self.0.min.$w.height,
&mut self.0.max.$w.height, &mut self.0.pct10.$w.height,
&mut self.0.pct25.$w.height, &mut self.0.median.$w.height,
&mut self.0.pct75.$w.height, &mut self.0.pct90.$w.height, exit,
max_from,
windows.$w,
source,
&mut self.0.average.$w.height,
&mut self.0.min.$w.height,
&mut self.0.max.$w.height,
&mut self.0.pct10.$w.height,
&mut self.0.pct25.$w.height,
&mut self.0.median.$w.height,
&mut self.0.pct75.$w.height,
&mut self.0.pct90.$w.height,
exit,
)?
};
}
@@ -13,7 +13,9 @@ use crate::{
/// each storing basis points with lazy ratio and percent float views.
#[derive(Deref, DerefMut, Traversable)]
#[traversable(transparent)]
pub struct PercentRollingEmas1w1m<B: BpsType, M: StorageMode = Rw>(pub Emas1w1m<PercentFromHeight<B, M>>);
pub struct PercentRollingEmas1w1m<B: BpsType, M: StorageMode = Rw>(
pub Emas1w1m<PercentFromHeight<B, M>>,
);
impl<B: BpsType> PercentRollingEmas1w1m<B> {
pub(crate) fn forced_import(
@@ -23,12 +25,7 @@ impl<B: BpsType> PercentRollingEmas1w1m<B> {
indexes: &indexes::Vecs,
) -> Result<Self> {
Ok(Self(Emas1w1m::try_from_fn(|suffix| {
PercentFromHeight::forced_import(
db,
&format!("{name}_{suffix}"),
version,
indexes,
)
PercentFromHeight::forced_import(db, &format!("{name}_{suffix}"), version, indexes)
})?))
}
@@ -13,7 +13,9 @@ use crate::{
/// with lazy ratio and percent float views.
#[derive(Deref, DerefMut, Traversable)]
#[traversable(transparent)]
pub struct PercentRollingWindows<B: BpsType, M: StorageMode = Rw>(pub Windows<PercentFromHeight<B, M>>);
pub struct PercentRollingWindows<B: BpsType, M: StorageMode = Rw>(
pub Windows<PercentFromHeight<B, M>>,
);
impl<B: BpsType> PercentRollingWindows<B> {
pub(crate) fn forced_import(
@@ -23,12 +25,7 @@ impl<B: BpsType> PercentRollingWindows<B> {
indexes: &indexes::Vecs,
) -> Result<Self> {
Ok(Self(Windows::try_from_fn(|suffix| {
PercentFromHeight::forced_import(
db,
&format!("{name}_{suffix}"),
version,
indexes,
)
PercentFromHeight::forced_import(db, &format!("{name}_{suffix}"), version, indexes)
})?))
}
}
@@ -22,9 +22,7 @@ use crate::{
/// Each window contains `ValueFromHeight` (sats + btc lazy + usd).
#[derive(Deref, DerefMut, Traversable)]
#[traversable(transparent)]
pub struct ValueFromHeightWindows<M: StorageMode = Rw>(
pub Windows<ValueFromHeight<M>>,
);
pub struct ValueFromHeightWindows<M: StorageMode = Rw>(pub Windows<ValueFromHeight<M>>);
impl ValueFromHeightWindows {
pub(crate) fn forced_import(
@@ -54,7 +54,8 @@ where
T: Default + SubAssign,
{
for (w, starts) in self.0.as_mut_array().into_iter().zip(windows.as_array()) {
w.height.compute_rolling_sum(max_from, *starts, source, exit)?;
w.height
.compute_rolling_sum(max_from, *starts, source, exit)?;
}
Ok(())
}
@@ -0,0 +1,89 @@
use std::marker::PhantomData;
use brk_types::{Bitcoin, Cents, Dollars, Sats, StoredF32, StoredI8, StoredU16, StoredU32};
use vecdb::{BinaryTransform, UnaryTransform, VecValue};
pub struct Identity<T>(PhantomData<T>);
impl<T: VecValue> UnaryTransform<T, T> for Identity<T> {
#[inline(always)]
fn apply(v: T) -> T {
v
}
}
pub struct HalveSats;
impl UnaryTransform<Sats, Sats> for HalveSats {
#[inline(always)]
fn apply(sats: Sats) -> Sats {
sats / 2
}
}
pub struct HalveSatsToBitcoin;
impl UnaryTransform<Sats, Bitcoin> for HalveSatsToBitcoin {
#[inline(always)]
fn apply(sats: Sats) -> Bitcoin {
Bitcoin::from(sats / 2)
}
}
pub struct HalveCents;
impl UnaryTransform<Cents, Cents> for HalveCents {
#[inline(always)]
fn apply(cents: Cents) -> Cents {
cents / 2u64
}
}
pub struct HalveDollars;
impl UnaryTransform<Dollars, Dollars> for HalveDollars {
#[inline(always)]
fn apply(dollars: Dollars) -> Dollars {
dollars.halved()
}
}
pub struct MaskSats;
impl BinaryTransform<StoredU32, Sats, Sats> for MaskSats {
#[inline(always)]
fn apply(mask: StoredU32, value: Sats) -> Sats {
if mask == StoredU32::ONE {
value
} else {
Sats::ZERO
}
}
}
pub struct ReturnF32Tenths<const V: u16>;
impl<S, const V: u16> UnaryTransform<S, StoredF32> for ReturnF32Tenths<V> {
#[inline(always)]
fn apply(_: S) -> StoredF32 {
StoredF32::from(V as f32 / 10.0)
}
}
pub struct ReturnU16<const V: u16>;
impl<S, const V: u16> UnaryTransform<S, StoredU16> for ReturnU16<V> {
#[inline(always)]
fn apply(_: S) -> StoredU16 {
StoredU16::new(V)
}
}
pub struct ReturnI8<const V: i8>;
impl<S, const V: i8> UnaryTransform<S, StoredI8> for ReturnI8<V> {
#[inline(always)]
fn apply(_: S) -> StoredI8 {
StoredI8::new(V)
}
}
@@ -1,4 +1,6 @@
use brk_types::{BasisPoints16, BasisPoints32, BasisPointsSigned16, BasisPointsSigned32, StoredF32};
use brk_types::{
BasisPoints16, BasisPoints32, BasisPointsSigned16, BasisPointsSigned32, StoredF32,
};
use vecdb::UnaryTransform;
pub struct Bp16ToFloat;
@@ -36,3 +38,30 @@ impl UnaryTransform<BasisPointsSigned32, StoredF32> for Bps32ToFloat {
StoredF32::from(bp.to_f32())
}
}
pub struct Bp16ToPercent;
impl UnaryTransform<BasisPoints16, StoredF32> for Bp16ToPercent {
#[inline(always)]
fn apply(bp: BasisPoints16) -> StoredF32 {
StoredF32::from(bp.inner() as f32 / 100.0)
}
}
pub struct Bps16ToPercent;
impl UnaryTransform<BasisPointsSigned16, StoredF32> for Bps16ToPercent {
#[inline(always)]
fn apply(bp: BasisPointsSigned16) -> StoredF32 {
StoredF32::from(bp.inner() as f32 / 100.0)
}
}
pub struct Bps32ToPercent;
impl UnaryTransform<BasisPointsSigned32, StoredF32> for Bps32ToPercent {
#[inline(always)]
fn apply(bp: BasisPointsSigned32) -> StoredF32 {
StoredF32::from(bp.inner() as f32 / 100.0)
}
}
@@ -1,29 +0,0 @@
use brk_types::{BasisPoints16, BasisPointsSigned16, BasisPointsSigned32, StoredF32};
use vecdb::UnaryTransform;
pub struct Bp16ToPercent;
impl UnaryTransform<BasisPoints16, StoredF32> for Bp16ToPercent {
#[inline(always)]
fn apply(bp: BasisPoints16) -> StoredF32 {
StoredF32::from(bp.inner() as f32 / 100.0)
}
}
pub struct Bps16ToPercent;
impl UnaryTransform<BasisPointsSigned16, StoredF32> for Bps16ToPercent {
#[inline(always)]
fn apply(bp: BasisPointsSigned16) -> StoredF32 {
StoredF32::from(bp.inner() as f32 / 100.0)
}
}
pub struct Bps32ToPercent;
impl UnaryTransform<BasisPointsSigned32, StoredF32> for Bps32ToPercent {
#[inline(always)]
fn apply(bp: BasisPointsSigned32) -> StoredF32 {
StoredF32::from(bp.inner() as f32 / 100.0)
}
}
@@ -1,48 +0,0 @@
use brk_types::{Cents, CentsSigned, Dollars, Sats};
use vecdb::UnaryTransform;
/// CentsUnsigned -> Dollars (convert cents to dollars for display)
pub struct CentsUnsignedToDollars;
impl UnaryTransform<Cents, Dollars> for CentsUnsignedToDollars {
#[inline(always)]
fn apply(cents: Cents) -> Dollars {
cents.into()
}
}
/// 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)
}
}
/// 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()
}
}
/// CentsUnsigned -> Sats (sats per dollar: 1 BTC / price)
pub struct CentsUnsignedToSats;
impl UnaryTransform<Cents, Sats> for CentsUnsignedToSats {
#[inline(always)]
fn apply(cents: Cents) -> Sats {
let dollars = Dollars::from(cents);
if dollars == Dollars::ZERO {
Sats::ZERO
} else {
Sats::ONE_BTC / dollars
}
}
}
@@ -1,13 +0,0 @@
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
}
}
@@ -1,13 +0,0 @@
use brk_types::{Cents, CentsSigned};
use vecdb::BinaryTransform;
/// (Cents, Cents) -> CentsSigned (a - b)
/// Produces a signed result from two unsigned inputs.
pub struct CentsSubtractToCentsSigned;
impl BinaryTransform<Cents, Cents, CentsSigned> for CentsSubtractToCentsSigned {
#[inline(always)]
fn apply(a: Cents, b: Cents) -> CentsSigned {
CentsSigned::from(a.inner() as i64 - b.inner() as i64)
}
}
@@ -1,13 +0,0 @@
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)
}
}
@@ -0,0 +1,106 @@
use brk_types::{Bitcoin, Cents, CentsSigned, Dollars, Sats, SatsFract, SatsSigned};
use vecdb::{BinaryTransform, UnaryTransform};
pub struct SatsToBitcoin;
impl UnaryTransform<Sats, Bitcoin> for SatsToBitcoin {
#[inline(always)]
fn apply(sats: Sats) -> Bitcoin {
Bitcoin::from(sats)
}
}
pub struct SatsSignedToBitcoin;
impl UnaryTransform<SatsSigned, Bitcoin> for SatsSignedToBitcoin {
#[inline(always)]
fn apply(sats: SatsSigned) -> Bitcoin {
Bitcoin::from(sats)
}
}
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)
}
}
pub struct CentsUnsignedToDollars;
impl UnaryTransform<Cents, Dollars> for CentsUnsignedToDollars {
#[inline(always)]
fn apply(cents: Cents) -> Dollars {
cents.into()
}
}
pub struct NegCentsUnsignedToDollars;
impl UnaryTransform<Cents, Dollars> for NegCentsUnsignedToDollars {
#[inline(always)]
fn apply(cents: Cents) -> Dollars {
-Dollars::from(cents)
}
}
pub struct CentsSignedToDollars;
impl UnaryTransform<CentsSigned, Dollars> for CentsSignedToDollars {
#[inline(always)]
fn apply(cents: CentsSigned) -> Dollars {
cents.into()
}
}
pub struct CentsUnsignedToSats;
impl UnaryTransform<Cents, Sats> for CentsUnsignedToSats {
#[inline(always)]
fn apply(cents: Cents) -> Sats {
let dollars = Dollars::from(cents);
if dollars == Dollars::ZERO {
Sats::ZERO
} else {
Sats::ONE_BTC / dollars
}
}
}
pub struct CentsPlus;
impl BinaryTransform<Cents, Cents, Cents> for CentsPlus {
#[inline(always)]
fn apply(lhs: Cents, rhs: Cents) -> Cents {
lhs + rhs
}
}
pub struct CentsSubtractToCentsSigned;
impl BinaryTransform<Cents, Cents, CentsSigned> for CentsSubtractToCentsSigned {
#[inline(always)]
fn apply(a: Cents, b: Cents) -> CentsSigned {
CentsSigned::from(a.inner() as i64 - b.inner() as i64)
}
}
pub struct CentsTimesTenths<const V: u16>;
impl<const V: u16> UnaryTransform<Cents, Cents> for CentsTimesTenths<V> {
#[inline(always)]
fn apply(c: Cents) -> Cents {
Cents::from(c.as_u128() * V as u128 / 10)
}
}
pub struct DollarsToSatsFract;
impl UnaryTransform<Dollars, SatsFract> for DollarsToSatsFract {
#[inline(always)]
fn apply(usd: Dollars) -> SatsFract {
SatsFract::ONE_BTC / usd
}
}
@@ -1,11 +0,0 @@
use brk_types::StoredF32;
use vecdb::UnaryTransform;
pub struct DaysToYears;
impl UnaryTransform<StoredF32, StoredF32> for DaysToYears {
#[inline(always)]
fn apply(v: StoredF32) -> StoredF32 {
StoredF32::from(*v / 365.0)
}
}
@@ -0,0 +1,86 @@
use std::marker::PhantomData;
use brk_types::{BasisPoints32, Cents, StoredF32, StoredF64, StoredU64, Timestamp};
use vecdb::{BinaryTransform, UnaryTransform};
pub struct PerSec;
impl BinaryTransform<StoredU64, Timestamp, StoredF32> for PerSec {
#[inline(always)]
fn apply(count: StoredU64, interval: Timestamp) -> StoredF32 {
let interval_f64 = f64::from(*interval);
if interval_f64 > 0.0 {
StoredF32::from(*count as f64 / interval_f64)
} else {
StoredF32::NAN
}
}
}
pub struct DaysToYears;
impl UnaryTransform<StoredF32, StoredF32> for DaysToYears {
#[inline(always)]
fn apply(v: StoredF32) -> StoredF32 {
StoredF32::from(*v / 365.0)
}
}
pub trait SqrtDays {
const FACTOR: f32;
}
pub struct Days7;
impl SqrtDays for Days7 {
const FACTOR: f32 = 2.6457513; // 7.0_f32.sqrt()
}
pub struct Days30;
impl SqrtDays for Days30 {
const FACTOR: f32 = 5.477226; // 30.0_f32.sqrt()
}
pub struct Days365;
impl SqrtDays for Days365 {
const FACTOR: f32 = 19.104973; // 365.0_f32.sqrt()
}
pub struct TimesSqrt<D: SqrtDays>(PhantomData<D>);
impl<D: SqrtDays> UnaryTransform<StoredF32, StoredF32> for TimesSqrt<D> {
#[inline(always)]
fn apply(v: StoredF32) -> StoredF32 {
(*v * D::FACTOR).into()
}
}
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))
}
}
pub struct PriceTimesRatioBp32Cents;
impl BinaryTransform<Cents, BasisPoints32, Cents> for PriceTimesRatioBp32Cents {
#[inline(always)]
fn apply(price: Cents, ratio: BasisPoints32) -> Cents {
Cents::from(f64::from(price) * f64::from(ratio))
}
}
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)
}
}
}
@@ -1,13 +0,0 @@
use brk_types::{Dollars, SatsFract};
use vecdb::UnaryTransform;
/// Dollars -> SatsFract (exchange rate: sats per dollar at this price level)
/// Formula: sats = 100_000_000 / usd_price
pub struct DollarsToSatsFract;
impl UnaryTransform<Dollars, SatsFract> for DollarsToSatsFract {
#[inline(always)]
fn apply(usd: Dollars) -> SatsFract {
SatsFract::ONE_BTC / usd
}
}
@@ -1,43 +0,0 @@
use brk_types::{Bitcoin, Cents, Dollars, Sats};
use vecdb::UnaryTransform;
/// Sats -> Sats/2 (for supply_halved)
pub struct HalveSats;
impl UnaryTransform<Sats, Sats> for HalveSats {
#[inline(always)]
fn apply(sats: Sats) -> Sats {
sats / 2
}
}
/// Sats -> Bitcoin/2 (halve then convert to bitcoin)
/// Avoids lazy-from-lazy by combining both transforms
pub struct HalveSatsToBitcoin;
impl UnaryTransform<Sats, Bitcoin> for HalveSatsToBitcoin {
#[inline(always)]
fn apply(sats: Sats) -> Bitcoin {
Bitcoin::from(sats / 2)
}
}
/// Cents -> Cents/2 (for supply_halved_cents)
pub struct HalveCents;
impl UnaryTransform<Cents, Cents> for HalveCents {
#[inline(always)]
fn apply(cents: Cents) -> Cents {
cents / 2u64
}
}
/// Dollars -> Dollars/2 (for supply_halved_usd)
pub struct HalveDollars;
impl UnaryTransform<Dollars, Dollars> for HalveDollars {
#[inline(always)]
fn apply(dollars: Dollars) -> Dollars {
dollars.halved()
}
}
@@ -1,13 +0,0 @@
use std::marker::PhantomData;
use vecdb::{UnaryTransform, VecValue};
/// T -> T (identity transform for lazy references)
pub struct Identity<T>(PhantomData<T>);
impl<T: VecValue> UnaryTransform<T, T> for Identity<T> {
#[inline(always)]
fn apply(v: T) -> T {
v
}
}
@@ -1,43 +1,30 @@
mod block_count_target;
mod bps_to_float;
mod bps_to_percent;
mod cents_convert;
mod cents_plus;
mod cents_subtract_to_cents_signed;
mod cents_times_tenths;
mod days_to_years;
mod dollars_to_sats_fract;
mod halve;
mod identity;
mod ohlc;
mod per_sec;
mod price_times_ratio_cents;
mod arithmetic;
mod bps;
mod currency;
mod derived;
mod ratio;
mod ratio_cents64;
mod return_const;
mod sat_mask;
mod sat_to_bitcoin;
mod sats_to_cents;
mod volatility;
mod specialized;
pub use block_count_target::*;
pub use bps_to_float::*;
pub use bps_to_percent::*;
pub use cents_convert::*;
pub use cents_plus::*;
pub use cents_subtract_to_cents_signed::*;
pub use cents_times_tenths::*;
pub use days_to_years::*;
pub use dollars_to_sats_fract::*;
pub use halve::*;
pub use identity::*;
pub use ohlc::*;
pub use per_sec::*;
pub use price_times_ratio_cents::*;
pub use ratio::*;
pub use ratio_cents64::*;
pub use return_const::*;
pub use sat_mask::*;
pub use sat_to_bitcoin::*;
pub use sats_to_cents::*;
pub use volatility::*;
pub use arithmetic::{
HalveCents, HalveDollars, HalveSats, HalveSatsToBitcoin, Identity, MaskSats, ReturnF32Tenths,
ReturnI8, ReturnU16,
};
pub use bps::{
Bp16ToFloat, Bp16ToPercent, Bp32ToFloat, Bps16ToFloat, Bps16ToPercent, Bps32ToFloat,
Bps32ToPercent,
};
pub use currency::{
CentsPlus, CentsSignedToDollars, CentsSubtractToCentsSigned, CentsTimesTenths,
CentsUnsignedToDollars, CentsUnsignedToSats, DollarsToSatsFract, NegCentsUnsignedToDollars,
SatsSignedToBitcoin, SatsToBitcoin, SatsToCents,
};
pub use derived::{
Days7, Days30, Days365, DaysToYears, PerSec, PriceTimesRatioBp32Cents, PriceTimesRatioCents,
RatioCents64, TimesSqrt,
};
pub use ratio::{
NegRatioDollarsBps16, RatioCentsBp16, RatioCentsSignedCentsBps16, RatioCentsSignedDollarsBps16,
RatioDiffCentsBps32, RatioDiffDollarsBps32, RatioDiffF32Bps32, RatioDollarsBp16,
RatioDollarsBp32, RatioDollarsBps16, RatioSatsBp16, RatioU32Bp16, RatioU64Bp16,
};
pub use specialized::{BlockCountTarget, OhlcCentsToDollars, OhlcCentsToSats};
@@ -1,28 +0,0 @@
use brk_types::{Close, High, Low, OHLCCents, OHLCDollars, OHLCSats, Open};
use vecdb::UnaryTransform;
use super::CentsUnsignedToSats;
pub struct OhlcCentsToDollars;
impl UnaryTransform<OHLCCents, OHLCDollars> for OhlcCentsToDollars {
#[inline(always)]
fn apply(cents: OHLCCents) -> OHLCDollars {
OHLCDollars::from(cents)
}
}
/// OHLCCents -> OHLCSats with high/low swapped (inverse price relationship).
pub struct OhlcCentsToSats;
impl UnaryTransform<OHLCCents, OHLCSats> for OhlcCentsToSats {
#[inline(always)]
fn apply(cents: OHLCCents) -> OHLCSats {
OHLCSats {
open: Open::new(CentsUnsignedToSats::apply(*cents.open)),
high: High::new(CentsUnsignedToSats::apply(*cents.low)),
low: Low::new(CentsUnsignedToSats::apply(*cents.high)),
close: Close::new(CentsUnsignedToSats::apply(*cents.close)),
}
}
}
@@ -1,17 +0,0 @@
use brk_types::{StoredF32, StoredU64, Timestamp};
use vecdb::BinaryTransform;
/// (StoredU64, Timestamp) -> StoredF32 rate (count / interval_seconds)
pub struct PerSec;
impl BinaryTransform<StoredU64, Timestamp, StoredF32> for PerSec {
#[inline(always)]
fn apply(count: StoredU64, interval: Timestamp) -> StoredF32 {
let interval_f64 = f64::from(*interval);
if interval_f64 > 0.0 {
StoredF32::from(*count as f64 / interval_f64)
} else {
StoredF32::NAN
}
}
}
@@ -1,20 +0,0 @@
use brk_types::{BasisPoints32, 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))
}
}
pub struct PriceTimesRatioBp32Cents;
impl BinaryTransform<Cents, BasisPoints32, Cents> for PriceTimesRatioBp32Cents {
#[inline(always)]
fn apply(price: Cents, ratio: BasisPoints32) -> Cents {
Cents::from(f64::from(price) * f64::from(ratio))
}
}
@@ -4,9 +4,6 @@ use brk_types::{
};
use vecdb::BinaryTransform;
// === BasisPoints16 (unsigned) ratios ===
/// (StoredU64, StoredU64) -> BasisPoints16 ratio (a/b × 10000)
pub struct RatioU64Bp16;
impl BinaryTransform<StoredU64, StoredU64, BasisPoints16> for RatioU64Bp16 {
@@ -20,7 +17,6 @@ impl BinaryTransform<StoredU64, StoredU64, BasisPoints16> for RatioU64Bp16 {
}
}
/// (Sats, Sats) -> BasisPoints16 ratio (a/b × 10000)
pub struct RatioSatsBp16;
impl BinaryTransform<Sats, Sats, BasisPoints16> for RatioSatsBp16 {
@@ -34,7 +30,6 @@ impl BinaryTransform<Sats, Sats, BasisPoints16> for RatioSatsBp16 {
}
}
/// (Cents, Cents) -> BasisPoints16 ratio (a/b × 10000)
pub struct RatioCentsBp16;
impl BinaryTransform<Cents, Cents, BasisPoints16> for RatioCentsBp16 {
@@ -48,7 +43,6 @@ impl BinaryTransform<Cents, Cents, BasisPoints16> for RatioCentsBp16 {
}
}
/// (StoredU32, StoredU32) -> BasisPoints16 ratio (a/b × 10000)
pub struct RatioU32Bp16;
impl BinaryTransform<StoredU32, StoredU32, BasisPoints16> for RatioU32Bp16 {
@@ -62,7 +56,6 @@ impl BinaryTransform<StoredU32, StoredU32, BasisPoints16> for RatioU32Bp16 {
}
}
/// (Dollars, Dollars) -> BasisPoints16 ratio (a/b × 10000)
pub struct RatioDollarsBp16;
impl BinaryTransform<Dollars, Dollars, BasisPoints16> for RatioDollarsBp16 {
@@ -77,9 +70,6 @@ impl BinaryTransform<Dollars, Dollars, BasisPoints16> for RatioDollarsBp16 {
}
}
// === BasisPointsSigned16 (signed) ratios ===
/// (Dollars, Dollars) -> BasisPointsSigned16 ratio (a/b × 10000)
pub struct RatioDollarsBps16;
impl BinaryTransform<Dollars, Dollars, BasisPointsSigned16> for RatioDollarsBps16 {
@@ -94,7 +84,6 @@ impl BinaryTransform<Dollars, Dollars, BasisPointsSigned16> for RatioDollarsBps1
}
}
/// (Dollars, Dollars) -> BasisPointsSigned16 negated ratio (-(a/b) × 10000)
pub struct NegRatioDollarsBps16;
impl BinaryTransform<Dollars, Dollars, BasisPointsSigned16> for NegRatioDollarsBps16 {
@@ -109,7 +98,6 @@ impl BinaryTransform<Dollars, Dollars, BasisPointsSigned16> for NegRatioDollarsB
}
}
/// (CentsSigned, Cents) -> BasisPointsSigned16 ratio (a/b × 10000)
pub struct RatioCentsSignedCentsBps16;
impl BinaryTransform<CentsSigned, Cents, BasisPointsSigned16> for RatioCentsSignedCentsBps16 {
@@ -123,7 +111,6 @@ impl BinaryTransform<CentsSigned, Cents, BasisPointsSigned16> for RatioCentsSign
}
}
/// (CentsSigned, Dollars) -> BasisPointsSigned16 ratio (a/b × 10000)
pub struct RatioCentsSignedDollarsBps16;
impl BinaryTransform<CentsSigned, Dollars, BasisPointsSigned16> for RatioCentsSignedDollarsBps16 {
@@ -138,9 +125,6 @@ impl BinaryTransform<CentsSigned, Dollars, BasisPointsSigned16> for RatioCentsSi
}
}
// === BasisPoints32 (unsigned) ratios ===
/// (Dollars, Dollars) -> BasisPoints32 ratio (a / b × 10000)
pub struct RatioDollarsBp32;
impl BinaryTransform<Dollars, Dollars, BasisPoints32> for RatioDollarsBp32 {
@@ -150,9 +134,6 @@ impl BinaryTransform<Dollars, Dollars, BasisPoints32> for RatioDollarsBp32 {
}
}
// === BasisPointsSigned32 (signed) ratio diffs ===
/// (StoredF32, StoredF32) -> BasisPointsSigned32 ratio diff ((a/b - 1) × 10000)
pub struct RatioDiffF32Bps32;
impl BinaryTransform<StoredF32, StoredF32, BasisPointsSigned32> for RatioDiffF32Bps32 {
@@ -166,7 +147,6 @@ impl BinaryTransform<StoredF32, StoredF32, BasisPointsSigned32> for RatioDiffF32
}
}
/// (Dollars, Dollars) -> BasisPointsSigned32 ratio diff ((a/b - 1) × 10000)
pub struct RatioDiffDollarsBps32;
impl BinaryTransform<Dollars, Dollars, BasisPointsSigned32> for RatioDiffDollarsBps32 {
@@ -181,7 +161,6 @@ impl BinaryTransform<Dollars, Dollars, BasisPointsSigned32> for RatioDiffDollars
}
}
/// (Cents, Cents) -> BasisPointsSigned32 ratio diff ((a/b - 1) × 10000)
pub struct RatioDiffCentsBps32;
impl BinaryTransform<Cents, Cents, BasisPointsSigned32> for RatioDiffCentsBps32 {
@@ -1,17 +0,0 @@
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)
}
}
}
@@ -1,32 +0,0 @@
use brk_types::{StoredF32, StoredI8, StoredU16};
use vecdb::UnaryTransform;
/// Returns a constant f32 value from tenths (V=382 -> 38.2), ignoring the input.
pub struct ReturnF32Tenths<const V: u16>;
impl<S, const V: u16> UnaryTransform<S, StoredF32> for ReturnF32Tenths<V> {
#[inline(always)]
fn apply(_: S) -> StoredF32 {
StoredF32::from(V as f32 / 10.0)
}
}
/// Returns a constant u16 value, ignoring the input.
pub struct ReturnU16<const V: u16>;
impl<S, const V: u16> UnaryTransform<S, StoredU16> for ReturnU16<V> {
#[inline(always)]
fn apply(_: S) -> StoredU16 {
StoredU16::new(V)
}
}
/// Returns a constant i8 value, ignoring the input.
pub struct ReturnI8<const V: i8>;
impl<S, const V: i8> UnaryTransform<S, StoredI8> for ReturnI8<V> {
#[inline(always)]
fn apply(_: S) -> StoredI8 {
StoredI8::new(V)
}
}
@@ -1,17 +0,0 @@
use brk_types::{Sats, StoredU32};
use vecdb::BinaryTransform;
/// (StoredU32, Sats) -> Sats mask
/// Returns value if mask == 1, else 0. Used for pool fee/subsidy from chain data.
pub struct MaskSats;
impl BinaryTransform<StoredU32, Sats, Sats> for MaskSats {
#[inline(always)]
fn apply(mask: StoredU32, value: Sats) -> Sats {
if mask == StoredU32::ONE {
value
} else {
Sats::ZERO
}
}
}
@@ -1,22 +0,0 @@
use brk_types::{Bitcoin, Sats, SatsSigned};
use vecdb::UnaryTransform;
/// Sats -> Bitcoin (divide by 1e8)
pub struct SatsToBitcoin;
impl UnaryTransform<Sats, Bitcoin> for SatsToBitcoin {
#[inline(always)]
fn apply(sats: Sats) -> Bitcoin {
Bitcoin::from(sats)
}
}
/// SatsSigned -> Bitcoin (divide by 1e8, preserves sign)
pub struct SatsSignedToBitcoin;
impl UnaryTransform<SatsSigned, Bitcoin> for SatsSignedToBitcoin {
#[inline(always)]
fn apply(sats: SatsSigned) -> Bitcoin {
Bitcoin::from(sats)
}
}
@@ -1,13 +0,0 @@
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,16 +1,17 @@
use brk_types::{
Day1, Day3, DifficultyEpoch, HalvingEpoch, Height, Hour1, Hour12, Hour4,
Minute10, Minute30, Month1, Month3, Month6, StoredU64, Week1, Year1, Year10,
Close, Day1, Day3, DifficultyEpoch, HalvingEpoch, Height, High, Hour1, Hour4, Hour12, Low,
Minute10, Minute30, Month1, Month3, Month6, OHLCCents, OHLCDollars, OHLCSats, Open, StoredU64,
Week1, Year1, Year10,
};
use vecdb::UnaryTransform;
use super::CentsUnsignedToSats;
use crate::blocks::{
TARGET_BLOCKS_PER_DAY, TARGET_BLOCKS_PER_DAY3, TARGET_BLOCKS_PER_DECADE,
TARGET_BLOCKS_PER_HALVING, TARGET_BLOCKS_PER_HOUR1, TARGET_BLOCKS_PER_HOUR12,
TARGET_BLOCKS_PER_HOUR4, TARGET_BLOCKS_PER_MINUTE10,
TARGET_BLOCKS_PER_MINUTE30, TARGET_BLOCKS_PER_MONTH,
TARGET_BLOCKS_PER_QUARTER, TARGET_BLOCKS_PER_SEMESTER, TARGET_BLOCKS_PER_WEEK,
TARGET_BLOCKS_PER_YEAR,
TARGET_BLOCKS_PER_HALVING, TARGET_BLOCKS_PER_HOUR1, TARGET_BLOCKS_PER_HOUR4,
TARGET_BLOCKS_PER_HOUR12, TARGET_BLOCKS_PER_MINUTE10, TARGET_BLOCKS_PER_MINUTE30,
TARGET_BLOCKS_PER_MONTH, TARGET_BLOCKS_PER_QUARTER, TARGET_BLOCKS_PER_SEMESTER,
TARGET_BLOCKS_PER_WEEK, TARGET_BLOCKS_PER_YEAR,
};
pub struct BlockCountTarget;
@@ -126,3 +127,26 @@ impl UnaryTransform<DifficultyEpoch, StoredU64> for BlockCountTarget {
StoredU64::from(2016u64)
}
}
pub struct OhlcCentsToDollars;
impl UnaryTransform<OHLCCents, OHLCDollars> for OhlcCentsToDollars {
#[inline(always)]
fn apply(cents: OHLCCents) -> OHLCDollars {
OHLCDollars::from(cents)
}
}
pub struct OhlcCentsToSats;
impl UnaryTransform<OHLCCents, OHLCSats> for OhlcCentsToSats {
#[inline(always)]
fn apply(cents: OHLCCents) -> OHLCSats {
OHLCSats {
open: Open::new(CentsUnsignedToSats::apply(*cents.open)),
high: High::new(CentsUnsignedToSats::apply(*cents.low)),
low: Low::new(CentsUnsignedToSats::apply(*cents.high)),
close: Close::new(CentsUnsignedToSats::apply(*cents.close)),
}
}
}
@@ -1,33 +0,0 @@
use std::marker::PhantomData;
use brk_types::StoredF32;
use vecdb::UnaryTransform;
pub trait SqrtDays {
const FACTOR: f32;
}
pub struct Days7;
impl SqrtDays for Days7 {
const FACTOR: f32 = 2.6457513; // 7.0_f32.sqrt()
}
pub struct Days30;
impl SqrtDays for Days30 {
const FACTOR: f32 = 5.477226; // 30.0_f32.sqrt()
}
pub struct Days365;
impl SqrtDays for Days365 {
const FACTOR: f32 = 19.104973; // 365.0_f32.sqrt()
}
/// StoredF32 × sqrt(D) -> StoredF32 (annualize daily volatility to D-day period)
pub struct TimesSqrt<D: SqrtDays>(PhantomData<D>);
impl<D: SqrtDays> UnaryTransform<StoredF32, StoredF32> for TimesSqrt<D> {
#[inline(always)]
fn apply(v: StoredF32) -> StoredF32 {
(*v * D::FACTOR).into()
}
}
+11 -7
View File
@@ -11,7 +11,10 @@ use vecdb::{
StorageMode, VecIndex,
};
use crate::{internal::{CentsUnsignedToDollars, SatsToBitcoin, SatsToCents}, prices};
use crate::{
internal::{CentsUnsignedToDollars, SatsToBitcoin, SatsToCents},
prices,
};
const VERSION: Version = Version::TWO; // Match ValueFromHeight versioning
@@ -24,11 +27,7 @@ pub struct Value<I: VecIndex, M: StorageMode = Rw> {
}
impl Value<Height> {
pub(crate) fn forced_import(
db: &Database,
name: &str,
version: Version,
) -> Result<Self> {
pub(crate) fn forced_import(db: &Database, name: &str, version: Version) -> Result<Self> {
let v = version + VERSION;
let sats: EagerVec<PcoVec<Height, Sats>> = EagerVec::forced_import(db, name, v)?;
@@ -45,7 +44,12 @@ impl Value<Height> {
cents.read_only_boxed_clone(),
);
Ok(Self { sats, btc, cents, usd })
Ok(Self {
sats,
btc,
cents,
usd,
})
}
/// Eagerly compute cents height values: sats[h] * price_cents[h] / 1e8.