mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-07-15 04:58:12 -07:00
global: snapshot
This commit is contained in:
@@ -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 {
|
||||
¢s,
|
||||
);
|
||||
|
||||
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),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
+30
-1
@@ -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)
|
||||
}
|
||||
}
|
||||
+31
-7
@@ -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 +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.
|
||||
|
||||
Reference in New Issue
Block a user