mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-25 23:29:58 -07:00
computer: snapshot
This commit is contained in:
72
crates/brk_computer/src/internal/multi/from_height/full.rs
Normal file
72
crates/brk_computer/src/internal/multi/from_height/full.rs
Normal file
@@ -0,0 +1,72 @@
|
||||
//! ComputedFromHeightFull - Full (distribution + sum + cumulative) + RollingFull.
|
||||
//!
|
||||
//! For metrics aggregated per-block from finer-grained sources (e.g., per-tx data),
|
||||
//! where we want full per-block stats plus rolling window stats.
|
||||
|
||||
use std::ops::SubAssign;
|
||||
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Height, Version};
|
||||
use schemars::JsonSchema;
|
||||
use vecdb::{Database, Exit, Rw, StorageMode};
|
||||
|
||||
use crate::{
|
||||
indexes,
|
||||
internal::{Full, NumericValue, RollingFull, WindowStarts},
|
||||
};
|
||||
|
||||
#[derive(Traversable)]
|
||||
#[traversable(merge)]
|
||||
pub struct ComputedFromHeightFull<T, M: StorageMode = Rw>
|
||||
where
|
||||
T: NumericValue + JsonSchema,
|
||||
{
|
||||
#[traversable(flatten)]
|
||||
pub height: Full<Height, T, M>,
|
||||
#[traversable(flatten)]
|
||||
pub rolling: RollingFull<T, M>,
|
||||
}
|
||||
|
||||
const VERSION: Version = Version::ZERO;
|
||||
|
||||
impl<T> ComputedFromHeightFull<T>
|
||||
where
|
||||
T: NumericValue + JsonSchema,
|
||||
{
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
name: &str,
|
||||
version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Result<Self> {
|
||||
let v = version + VERSION;
|
||||
|
||||
let height = Full::forced_import(db, name, v)?;
|
||||
let rolling = RollingFull::forced_import(db, name, v, indexes)?;
|
||||
|
||||
Ok(Self { height, rolling })
|
||||
}
|
||||
|
||||
/// Compute Full stats via closure, then rolling windows from the per-block sum.
|
||||
pub(crate) fn compute(
|
||||
&mut self,
|
||||
max_from: Height,
|
||||
windows: &WindowStarts<'_>,
|
||||
exit: &Exit,
|
||||
compute_full: impl FnOnce(&mut Full<Height, T>) -> Result<()>,
|
||||
) -> Result<()>
|
||||
where
|
||||
T: From<f64> + Default + SubAssign + Copy + Ord,
|
||||
f64: From<T>,
|
||||
{
|
||||
compute_full(&mut self.height)?;
|
||||
self.rolling.compute(
|
||||
max_from,
|
||||
windows,
|
||||
self.height.sum_cumulative.sum.inner(),
|
||||
exit,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -70,11 +70,10 @@ where
|
||||
S2T: VecValue,
|
||||
F: BinaryTransform<S1T, S2T, T>,
|
||||
{
|
||||
self.height.compute_transform2(
|
||||
self.height.compute_binary::<S1T, S2T, F>(
|
||||
max_from,
|
||||
source1,
|
||||
source2,
|
||||
|(h, s1, s2, ..)| (h, F::apply(s1, s2)),
|
||||
exit,
|
||||
)?;
|
||||
Ok(())
|
||||
|
||||
@@ -4,13 +4,11 @@ use brk_traversable::Traversable;
|
||||
use brk_types::{Height, Version};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use schemars::JsonSchema;
|
||||
use vecdb::{ReadableBoxedVec, ReadableCloneableVec, LazyVecFrom1, UnaryTransform};
|
||||
use vecdb::{LazyVecFrom1, ReadableBoxedVec, ReadableCloneableVec, UnaryTransform};
|
||||
|
||||
use crate::{
|
||||
indexes,
|
||||
internal::{
|
||||
ComputedFromHeightLast, ComputedVecValue, LazyHeightDerivedLast, NumericValue,
|
||||
},
|
||||
internal::{ComputedFromHeightLast, ComputedVecValue, LazyHeightDerivedLast, NumericValue},
|
||||
};
|
||||
#[derive(Clone, Deref, DerefMut, Traversable)]
|
||||
#[traversable(merge)]
|
||||
@@ -61,7 +59,12 @@ where
|
||||
let v = version + VERSION;
|
||||
Self {
|
||||
height: LazyVecFrom1::transformed::<F>(name, v, height_source.clone()),
|
||||
rest: Box::new(LazyHeightDerivedLast::from_height_source::<F>(name, v, height_source, indexes)),
|
||||
rest: Box::new(LazyHeightDerivedLast::from_height_source::<F>(
|
||||
name,
|
||||
v,
|
||||
height_source,
|
||||
indexes,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,8 +81,11 @@ where
|
||||
let v = version + VERSION;
|
||||
Self {
|
||||
height: LazyVecFrom1::transformed::<F>(name, v, source.height.read_only_boxed_clone()),
|
||||
rest: Box::new(LazyHeightDerivedLast::from_lazy::<F, S2T>(name, v, &source.rest)),
|
||||
rest: Box::new(LazyHeightDerivedLast::from_lazy::<F, S2T>(
|
||||
name,
|
||||
v,
|
||||
&source.rest,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ mod cumulative;
|
||||
mod cumulative_rolling_full;
|
||||
mod cumulative_rolling_sum;
|
||||
mod distribution;
|
||||
mod full;
|
||||
mod last;
|
||||
mod lazy_computed_full;
|
||||
mod lazy_last;
|
||||
@@ -15,6 +16,7 @@ mod value_change;
|
||||
mod value_ema;
|
||||
mod value_full;
|
||||
mod value_last;
|
||||
mod value_last_rolling;
|
||||
mod value_lazy_computed_cumulative;
|
||||
mod value_lazy_last;
|
||||
mod value_sum_cumulative;
|
||||
@@ -24,6 +26,7 @@ pub use cumulative::*;
|
||||
pub use cumulative_rolling_full::*;
|
||||
pub use cumulative_rolling_sum::*;
|
||||
pub use distribution::*;
|
||||
pub use full::*;
|
||||
pub use last::*;
|
||||
pub use lazy_computed_full::*;
|
||||
pub use lazy_last::*;
|
||||
@@ -36,6 +39,7 @@ pub use value_change::*;
|
||||
pub use value_ema::*;
|
||||
pub use value_full::*;
|
||||
pub use value_last::*;
|
||||
pub use value_last_rolling::*;
|
||||
pub use value_lazy_computed_cumulative::*;
|
||||
pub use value_lazy_last::*;
|
||||
pub use value_sum_cumulative::*;
|
||||
|
||||
@@ -68,7 +68,7 @@ pub(crate) fn compute_spot_percentile_rank(
|
||||
}
|
||||
|
||||
pub struct PercentilesVecs<M: StorageMode = Rw> {
|
||||
pub vecs: [Option<Price<ComputedFromHeightLast<Dollars, M>>>; PERCENTILES_LEN],
|
||||
pub vecs: [Price<ComputedFromHeightLast<Dollars, M>>; PERCENTILES_LEN],
|
||||
}
|
||||
|
||||
const VERSION: Version = Version::ONE;
|
||||
@@ -79,15 +79,17 @@ impl PercentilesVecs {
|
||||
prefix: &str,
|
||||
version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
compute: bool,
|
||||
) -> Result<Self> {
|
||||
let vecs = PERCENTILES.map(|p| {
|
||||
compute.then(|| {
|
||||
let vecs = PERCENTILES
|
||||
.into_iter()
|
||||
.map(|p| {
|
||||
let metric_name = format!("{prefix}_pct{p:02}");
|
||||
Price::forced_import(db, &metric_name, version + VERSION, indexes)
|
||||
.unwrap()
|
||||
})
|
||||
});
|
||||
.collect::<Result<Vec<_>>>()?
|
||||
.try_into()
|
||||
.ok()
|
||||
.expect("PERCENTILES length mismatch");
|
||||
|
||||
Ok(Self { vecs })
|
||||
}
|
||||
@@ -98,17 +100,15 @@ impl PercentilesVecs {
|
||||
height: Height,
|
||||
percentile_prices: &[Dollars; PERCENTILES_LEN],
|
||||
) -> Result<()> {
|
||||
for (i, vec) in self.vecs.iter_mut().enumerate() {
|
||||
if let Some(v) = vec {
|
||||
v.usd.height.truncate_push(height, percentile_prices[i])?;
|
||||
}
|
||||
for (i, v) in self.vecs.iter_mut().enumerate() {
|
||||
v.usd.height.truncate_push(height, percentile_prices[i])?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 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().flatten() {
|
||||
for vec in self.vecs.iter_mut() {
|
||||
vec.usd.height.validate_computed_version_or_reset(version)?;
|
||||
}
|
||||
Ok(())
|
||||
@@ -123,7 +123,7 @@ impl ReadOnlyClone for PercentilesVecs {
|
||||
vecs: self
|
||||
.vecs
|
||||
.each_ref()
|
||||
.map(|v| v.as_ref().map(|p| p.read_only_clone())),
|
||||
.map(|v| v.read_only_clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -137,7 +137,7 @@ where
|
||||
PERCENTILES
|
||||
.iter()
|
||||
.zip(self.vecs.iter())
|
||||
.filter_map(|(p, v)| v.as_ref().map(|v| (format!("pct{p:02}"), v.to_tree_node())))
|
||||
.map(|(p, v)| (format!("pct{p:02}"), v.to_tree_node()))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
@@ -145,7 +145,6 @@ where
|
||||
fn iter_any_exportable(&self) -> impl Iterator<Item = &dyn AnyExportableVec> {
|
||||
self.vecs
|
||||
.iter()
|
||||
.flatten()
|
||||
.flat_map(|p| p.iter_any_exportable())
|
||||
}
|
||||
}
|
||||
@@ -1,454 +0,0 @@
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Dollars, Height, StoredF32, Version};
|
||||
use vecdb::{
|
||||
AnyStoredVec, AnyVec, Database, EagerVec, Exit, PcoVec, ReadableVec, Rw, StorageMode, VecIndex,
|
||||
WritableVec,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
ComputeIndexes, blocks, indexes,
|
||||
internal::{ComputedFromHeightStdDev, Price, StandardDeviationVecsOptions},
|
||||
prices,
|
||||
utils::get_percentile,
|
||||
};
|
||||
|
||||
use super::ComputedFromHeightLast;
|
||||
|
||||
#[derive(Traversable)]
|
||||
pub struct ComputedFromHeightRatio<M: StorageMode = Rw> {
|
||||
pub price: Option<Price<ComputedFromHeightLast<Dollars, M>>>,
|
||||
|
||||
pub ratio: ComputedFromHeightLast<StoredF32, M>,
|
||||
pub ratio_1w_sma: Option<ComputedFromHeightLast<StoredF32, M>>,
|
||||
pub ratio_1m_sma: Option<ComputedFromHeightLast<StoredF32, M>>,
|
||||
pub ratio_pct99: Option<ComputedFromHeightLast<StoredF32, M>>,
|
||||
pub ratio_pct98: Option<ComputedFromHeightLast<StoredF32, M>>,
|
||||
pub ratio_pct95: Option<ComputedFromHeightLast<StoredF32, M>>,
|
||||
pub ratio_pct5: Option<ComputedFromHeightLast<StoredF32, M>>,
|
||||
pub ratio_pct2: Option<ComputedFromHeightLast<StoredF32, M>>,
|
||||
pub ratio_pct1: Option<ComputedFromHeightLast<StoredF32, M>>,
|
||||
pub ratio_pct99_usd: Option<Price<ComputedFromHeightLast<Dollars, M>>>,
|
||||
pub ratio_pct98_usd: Option<Price<ComputedFromHeightLast<Dollars, M>>>,
|
||||
pub ratio_pct95_usd: Option<Price<ComputedFromHeightLast<Dollars, M>>>,
|
||||
pub ratio_pct5_usd: Option<Price<ComputedFromHeightLast<Dollars, M>>>,
|
||||
pub ratio_pct2_usd: Option<Price<ComputedFromHeightLast<Dollars, M>>>,
|
||||
pub ratio_pct1_usd: Option<Price<ComputedFromHeightLast<Dollars, M>>>,
|
||||
|
||||
pub ratio_sd: Option<ComputedFromHeightStdDev<M>>,
|
||||
pub ratio_4y_sd: Option<ComputedFromHeightStdDev<M>>,
|
||||
pub ratio_2y_sd: Option<ComputedFromHeightStdDev<M>>,
|
||||
pub ratio_1y_sd: Option<ComputedFromHeightStdDev<M>>,
|
||||
}
|
||||
|
||||
const VERSION: Version = Version::TWO;
|
||||
|
||||
impl ComputedFromHeightRatio {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
name: &str,
|
||||
metric_price: Option<&ComputedFromHeightLast<Dollars>>,
|
||||
version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
extended: bool,
|
||||
) -> Result<Self> {
|
||||
let v = version + VERSION;
|
||||
|
||||
macro_rules! import {
|
||||
($suffix:expr) => {
|
||||
ComputedFromHeightLast::forced_import(
|
||||
db,
|
||||
&format!("{name}_{}", $suffix),
|
||||
v,
|
||||
indexes,
|
||||
)
|
||||
.unwrap()
|
||||
};
|
||||
}
|
||||
|
||||
// Only compute price internally when metric_price is None
|
||||
let price = metric_price
|
||||
.is_none()
|
||||
.then(|| Price::forced_import(db, name, v, indexes).unwrap());
|
||||
|
||||
macro_rules! import_sd {
|
||||
($suffix:expr, $days:expr) => {
|
||||
ComputedFromHeightStdDev::forced_import(
|
||||
db,
|
||||
&format!("{name}_{}", $suffix),
|
||||
$days,
|
||||
v,
|
||||
indexes,
|
||||
StandardDeviationVecsOptions::default().add_all(),
|
||||
)
|
||||
.unwrap()
|
||||
};
|
||||
}
|
||||
|
||||
let ratio_pct99 = extended.then(|| import!("ratio_pct99"));
|
||||
let ratio_pct98 = extended.then(|| import!("ratio_pct98"));
|
||||
let ratio_pct95 = extended.then(|| import!("ratio_pct95"));
|
||||
let ratio_pct5 = extended.then(|| import!("ratio_pct5"));
|
||||
let ratio_pct2 = extended.then(|| import!("ratio_pct2"));
|
||||
let ratio_pct1 = extended.then(|| import!("ratio_pct1"));
|
||||
|
||||
macro_rules! lazy_usd {
|
||||
($ratio:expr, $suffix:expr) => {
|
||||
if !extended {
|
||||
None
|
||||
} else {
|
||||
$ratio.as_ref().map(|_| {
|
||||
Price::forced_import(
|
||||
db,
|
||||
&format!("{name}_{}", $suffix),
|
||||
v,
|
||||
indexes,
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
ratio: import!("ratio"),
|
||||
ratio_1w_sma: extended.then(|| import!("ratio_1w_sma")),
|
||||
ratio_1m_sma: extended.then(|| import!("ratio_1m_sma")),
|
||||
ratio_sd: extended.then(|| import_sd!("ratio", usize::MAX)),
|
||||
ratio_1y_sd: extended.then(|| import_sd!("ratio_1y", 365)),
|
||||
ratio_2y_sd: extended.then(|| import_sd!("ratio_2y", 2 * 365)),
|
||||
ratio_4y_sd: extended.then(|| import_sd!("ratio_4y", 4 * 365)),
|
||||
ratio_pct99_usd: lazy_usd!(&ratio_pct99, "ratio_pct99_usd"),
|
||||
ratio_pct98_usd: lazy_usd!(&ratio_pct98, "ratio_pct98_usd"),
|
||||
ratio_pct95_usd: lazy_usd!(&ratio_pct95, "ratio_pct95_usd"),
|
||||
ratio_pct5_usd: lazy_usd!(&ratio_pct5, "ratio_pct5_usd"),
|
||||
ratio_pct2_usd: lazy_usd!(&ratio_pct2, "ratio_pct2_usd"),
|
||||
ratio_pct1_usd: lazy_usd!(&ratio_pct1, "ratio_pct1_usd"),
|
||||
price,
|
||||
ratio_pct99,
|
||||
ratio_pct98,
|
||||
ratio_pct95,
|
||||
ratio_pct5,
|
||||
ratio_pct2,
|
||||
ratio_pct1,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn forced_import_from_lazy(
|
||||
db: &Database,
|
||||
name: &str,
|
||||
version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
extended: bool,
|
||||
) -> Result<Self> {
|
||||
let v = version + VERSION;
|
||||
|
||||
macro_rules! import {
|
||||
($suffix:expr) => {
|
||||
ComputedFromHeightLast::forced_import(
|
||||
db,
|
||||
&format!("{name}_{}", $suffix),
|
||||
v,
|
||||
indexes,
|
||||
)
|
||||
.unwrap()
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! import_sd {
|
||||
($suffix:expr, $days:expr) => {
|
||||
ComputedFromHeightStdDev::forced_import_from_lazy(
|
||||
db,
|
||||
&format!("{name}_{}", $suffix),
|
||||
$days,
|
||||
v,
|
||||
indexes,
|
||||
StandardDeviationVecsOptions::default().add_all(),
|
||||
)
|
||||
.unwrap()
|
||||
};
|
||||
}
|
||||
|
||||
let ratio_pct99 = extended.then(|| import!("ratio_pct99"));
|
||||
let ratio_pct98 = extended.then(|| import!("ratio_pct98"));
|
||||
let ratio_pct95 = extended.then(|| import!("ratio_pct95"));
|
||||
let ratio_pct5 = extended.then(|| import!("ratio_pct5"));
|
||||
let ratio_pct2 = extended.then(|| import!("ratio_pct2"));
|
||||
let ratio_pct1 = extended.then(|| import!("ratio_pct1"));
|
||||
|
||||
macro_rules! lazy_usd {
|
||||
($ratio:expr, $suffix:expr) => {
|
||||
$ratio.as_ref().map(|_| {
|
||||
Price::forced_import(db, &format!("{name}_{}", $suffix), v, indexes)
|
||||
.unwrap()
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
ratio: import!("ratio"),
|
||||
ratio_1w_sma: extended.then(|| import!("ratio_1w_sma")),
|
||||
ratio_1m_sma: extended.then(|| import!("ratio_1m_sma")),
|
||||
ratio_sd: extended.then(|| import_sd!("ratio", usize::MAX)),
|
||||
ratio_1y_sd: extended.then(|| import_sd!("ratio_1y", 365)),
|
||||
ratio_2y_sd: extended.then(|| import_sd!("ratio_2y", 2 * 365)),
|
||||
ratio_4y_sd: extended.then(|| import_sd!("ratio_4y", 4 * 365)),
|
||||
ratio_pct99_usd: lazy_usd!(&ratio_pct99, "ratio_pct99_usd"),
|
||||
ratio_pct98_usd: lazy_usd!(&ratio_pct98, "ratio_pct98_usd"),
|
||||
ratio_pct95_usd: lazy_usd!(&ratio_pct95, "ratio_pct95_usd"),
|
||||
ratio_pct5_usd: lazy_usd!(&ratio_pct5, "ratio_pct5_usd"),
|
||||
ratio_pct2_usd: lazy_usd!(&ratio_pct2, "ratio_pct2_usd"),
|
||||
ratio_pct1_usd: lazy_usd!(&ratio_pct1, "ratio_pct1_usd"),
|
||||
price: None,
|
||||
ratio_pct99,
|
||||
ratio_pct98,
|
||||
ratio_pct95,
|
||||
ratio_pct5,
|
||||
ratio_pct2,
|
||||
ratio_pct1,
|
||||
})
|
||||
}
|
||||
|
||||
/// Compute all: computes price at height level, then ratio + rest.
|
||||
pub(crate) fn compute_all<F>(
|
||||
&mut self,
|
||||
blocks: &blocks::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
exit: &Exit,
|
||||
mut compute: F,
|
||||
) -> Result<()>
|
||||
where
|
||||
F: FnMut(&mut EagerVec<PcoVec<Height, Dollars>>) -> Result<()>,
|
||||
{
|
||||
compute(&mut self.price.as_mut().unwrap().usd.height)?;
|
||||
|
||||
let price_opt: Option<&EagerVec<PcoVec<Height, Dollars>>> = None;
|
||||
self.compute_rest(blocks, prices, starting_indexes, exit, price_opt)
|
||||
}
|
||||
|
||||
/// Compute ratio and derived metrics from an externally-provided or internal price.
|
||||
pub(crate) fn compute_rest(
|
||||
&mut self,
|
||||
blocks: &blocks::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
exit: &Exit,
|
||||
price_opt: Option<&impl ReadableVec<Height, Dollars>>,
|
||||
) -> Result<()> {
|
||||
let close_price = &prices.usd.price;
|
||||
|
||||
let price = price_opt.unwrap_or_else(|| unsafe {
|
||||
std::mem::transmute(&self.price.as_ref().unwrap().usd.height)
|
||||
});
|
||||
|
||||
// Compute ratio = close_price / metric_price at height level
|
||||
self.ratio.height.compute_transform2(
|
||||
starting_indexes.height,
|
||||
close_price,
|
||||
price,
|
||||
|(i, close, price, ..)| {
|
||||
if price == Dollars::ZERO {
|
||||
(i, StoredF32::from(1.0))
|
||||
} else {
|
||||
(i, StoredF32::from(close / price))
|
||||
}
|
||||
},
|
||||
exit,
|
||||
)?;
|
||||
|
||||
if self.ratio_1w_sma.is_none() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// SMA using lookback vecs
|
||||
self.ratio_1w_sma
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.height
|
||||
.compute_rolling_average(
|
||||
starting_indexes.height,
|
||||
&blocks.count.height_1w_ago,
|
||||
&self.ratio.height,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
self.ratio_1m_sma
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.height
|
||||
.compute_rolling_average(
|
||||
starting_indexes.height,
|
||||
&blocks.count.height_1m_ago,
|
||||
&self.ratio.height,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
// Percentiles: insert into sorted array on day boundaries
|
||||
let ratio_version = self.ratio.height.version();
|
||||
self.mut_ratio_vecs()
|
||||
.iter_mut()
|
||||
.try_for_each(|v| -> Result<()> {
|
||||
v.validate_computed_version_or_reset(ratio_version)?;
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
let starting_height = self
|
||||
.mut_ratio_vecs()
|
||||
.iter()
|
||||
.map(|v| Height::from(v.len()))
|
||||
.min()
|
||||
.unwrap()
|
||||
.min(starting_indexes.height);
|
||||
|
||||
let start = starting_height.to_usize();
|
||||
let day_start = &blocks.count.height_24h_ago;
|
||||
|
||||
// Collect sorted history up to starting point (one per day boundary)
|
||||
let mut sorted = {
|
||||
let ratio_data = self.ratio.height.collect_range_at(0, start);
|
||||
let day_start_hist = day_start.collect_range_at(0, start);
|
||||
let mut sorted: Vec<StoredF32> = Vec::new();
|
||||
let mut last_day_start = Height::from(0_usize);
|
||||
for (h, ratio) in ratio_data.into_iter().enumerate() {
|
||||
let cur_day_start = day_start_hist[h];
|
||||
if h == 0 || cur_day_start != last_day_start {
|
||||
let pos = sorted.binary_search(&ratio).unwrap_or_else(|p| p);
|
||||
sorted.insert(pos, ratio);
|
||||
last_day_start = cur_day_start;
|
||||
}
|
||||
}
|
||||
sorted
|
||||
};
|
||||
|
||||
let pct1_vec = &mut self.ratio_pct1.as_mut().unwrap().height;
|
||||
let pct2_vec = &mut self.ratio_pct2.as_mut().unwrap().height;
|
||||
let pct5_vec = &mut self.ratio_pct5.as_mut().unwrap().height;
|
||||
let pct95_vec = &mut self.ratio_pct95.as_mut().unwrap().height;
|
||||
let pct98_vec = &mut self.ratio_pct98.as_mut().unwrap().height;
|
||||
let pct99_vec = &mut self.ratio_pct99.as_mut().unwrap().height;
|
||||
|
||||
let ratio_len = self.ratio.height.len();
|
||||
let ratio_data = self.ratio.height.collect_range_at(start, ratio_len);
|
||||
let mut last_day_start = if start > 0 {
|
||||
day_start
|
||||
.collect_one_at(start - 1)
|
||||
.unwrap_or(Height::from(0_usize))
|
||||
} else {
|
||||
Height::from(0_usize)
|
||||
};
|
||||
|
||||
let day_start_data = day_start.collect_range_at(start, ratio_len);
|
||||
|
||||
for (offset, ratio) in ratio_data.into_iter().enumerate() {
|
||||
let index = start + offset;
|
||||
|
||||
// Insert into sorted history on day boundaries
|
||||
let cur_day_start = day_start_data[offset];
|
||||
if index == 0 || cur_day_start != last_day_start {
|
||||
let pos = sorted.binary_search(&ratio).unwrap_or_else(|p| p);
|
||||
sorted.insert(pos, ratio);
|
||||
last_day_start = cur_day_start;
|
||||
}
|
||||
|
||||
if sorted.is_empty() {
|
||||
pct1_vec.truncate_push_at(index, StoredF32::NAN)?;
|
||||
pct2_vec.truncate_push_at(index, StoredF32::NAN)?;
|
||||
pct5_vec.truncate_push_at(index, StoredF32::NAN)?;
|
||||
pct95_vec.truncate_push_at(index, StoredF32::NAN)?;
|
||||
pct98_vec.truncate_push_at(index, StoredF32::NAN)?;
|
||||
pct99_vec.truncate_push_at(index, StoredF32::NAN)?;
|
||||
} else {
|
||||
pct1_vec.truncate_push_at(index, get_percentile(&sorted, 0.01))?;
|
||||
pct2_vec.truncate_push_at(index, get_percentile(&sorted, 0.02))?;
|
||||
pct5_vec.truncate_push_at(index, get_percentile(&sorted, 0.05))?;
|
||||
pct95_vec.truncate_push_at(index, get_percentile(&sorted, 0.95))?;
|
||||
pct98_vec.truncate_push_at(index, get_percentile(&sorted, 0.98))?;
|
||||
pct99_vec.truncate_push_at(index, get_percentile(&sorted, 0.99))?;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let _lock = exit.lock();
|
||||
self.mut_ratio_vecs()
|
||||
.into_iter()
|
||||
.try_for_each(|v| v.flush())?;
|
||||
}
|
||||
|
||||
// Compute stddev at height level
|
||||
macro_rules! compute_sd {
|
||||
($($field:ident),*) => {
|
||||
$(self.$field.as_mut().unwrap().compute_all(
|
||||
blocks, starting_indexes, exit, &self.ratio.height,
|
||||
)?;)*
|
||||
};
|
||||
}
|
||||
compute_sd!(ratio_sd, ratio_4y_sd, ratio_2y_sd, ratio_1y_sd);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Compute USD ratio bands: usd_band = metric_price * ratio_percentile
|
||||
pub(crate) fn compute_usd_bands(
|
||||
&mut self,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
metric_price: &impl ReadableVec<Height, Dollars>,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
use crate::internal::PriceTimesRatio;
|
||||
|
||||
macro_rules! compute_band {
|
||||
($usd_field:ident, $band_field:ident) => {
|
||||
if let Some(usd) = self.$usd_field.as_mut() {
|
||||
if let Some(band) = self.$band_field.as_ref() {
|
||||
usd.usd
|
||||
.compute_binary::<Dollars, StoredF32, PriceTimesRatio>(
|
||||
starting_indexes.height,
|
||||
metric_price,
|
||||
&band.height,
|
||||
exit,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
compute_band!(ratio_pct99_usd, ratio_pct99);
|
||||
compute_band!(ratio_pct98_usd, ratio_pct98);
|
||||
compute_band!(ratio_pct95_usd, ratio_pct95);
|
||||
compute_band!(ratio_pct5_usd, ratio_pct5);
|
||||
compute_band!(ratio_pct2_usd, ratio_pct2);
|
||||
compute_band!(ratio_pct1_usd, ratio_pct1);
|
||||
|
||||
// Stddev USD bands
|
||||
macro_rules! compute_sd_usd {
|
||||
($($field:ident),*) => {
|
||||
$(if let Some(sd) = self.$field.as_mut() {
|
||||
sd.compute_usd_bands(starting_indexes, metric_price, exit)?;
|
||||
})*
|
||||
};
|
||||
}
|
||||
compute_sd_usd!(ratio_sd, ratio_4y_sd, ratio_2y_sd, ratio_1y_sd);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn mut_ratio_vecs(&mut self) -> Vec<&mut EagerVec<PcoVec<Height, StoredF32>>> {
|
||||
macro_rules! collect_vecs {
|
||||
($($field:ident),*) => {{
|
||||
let mut vecs = Vec::with_capacity(6);
|
||||
$(if let Some(v) = self.$field.as_mut() { vecs.push(&mut v.height); })*
|
||||
vecs
|
||||
}};
|
||||
}
|
||||
collect_vecs!(
|
||||
ratio_pct1,
|
||||
ratio_pct2,
|
||||
ratio_pct5,
|
||||
ratio_pct95,
|
||||
ratio_pct98,
|
||||
ratio_pct99
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Dollars, Height, Version};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use vecdb::{Database, Exit, ReadableVec, Rw, StorageMode};
|
||||
|
||||
use crate::{ComputeIndexes, blocks, indexes, prices};
|
||||
|
||||
use super::{ComputedFromHeightRatio, ComputedFromHeightRatioExtension};
|
||||
|
||||
#[derive(Deref, DerefMut, Traversable)]
|
||||
#[traversable(merge)]
|
||||
pub struct ComputedFromHeightRatioExtended<M: StorageMode = Rw> {
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
#[traversable(flatten)]
|
||||
pub base: ComputedFromHeightRatio<M>,
|
||||
#[traversable(flatten)]
|
||||
pub extended: ComputedFromHeightRatioExtension<M>,
|
||||
}
|
||||
|
||||
impl ComputedFromHeightRatioExtended {
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
name: &str,
|
||||
version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Result<Self> {
|
||||
Ok(Self {
|
||||
base: ComputedFromHeightRatio::forced_import(db, name, version, indexes)?,
|
||||
extended: ComputedFromHeightRatioExtension::forced_import(db, name, version, indexes)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Compute ratio and all extended metrics from an externally-provided metric price.
|
||||
pub(crate) fn compute_rest(
|
||||
&mut self,
|
||||
blocks: &blocks::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
exit: &Exit,
|
||||
metric_price: &impl ReadableVec<Height, Dollars>,
|
||||
) -> Result<()> {
|
||||
let close_price = &prices.usd.price;
|
||||
self.base
|
||||
.compute_ratio(starting_indexes, close_price, metric_price, exit)?;
|
||||
self.extended.compute_rest(
|
||||
blocks,
|
||||
starting_indexes,
|
||||
exit,
|
||||
&self.base.ratio.height,
|
||||
)?;
|
||||
self.extended
|
||||
.compute_usd_bands(starting_indexes, metric_price, exit)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Dollars, Height, StoredF32, Version};
|
||||
use vecdb::{AnyStoredVec, AnyVec, Database, EagerVec, Exit, PcoVec, ReadableVec, Rw, StorageMode, VecIndex, WritableVec};
|
||||
|
||||
use crate::{
|
||||
ComputeIndexes, blocks, indexes,
|
||||
internal::{ComputedFromHeightStdDevExtended, Price},
|
||||
utils::get_percentile,
|
||||
};
|
||||
|
||||
use super::super::ComputedFromHeightLast;
|
||||
|
||||
#[derive(Traversable)]
|
||||
pub struct ComputedFromHeightRatioExtension<M: StorageMode = Rw> {
|
||||
pub ratio_1w_sma: ComputedFromHeightLast<StoredF32, M>,
|
||||
pub ratio_1m_sma: ComputedFromHeightLast<StoredF32, M>,
|
||||
pub ratio_pct99: ComputedFromHeightLast<StoredF32, M>,
|
||||
pub ratio_pct98: ComputedFromHeightLast<StoredF32, M>,
|
||||
pub ratio_pct95: ComputedFromHeightLast<StoredF32, M>,
|
||||
pub ratio_pct5: ComputedFromHeightLast<StoredF32, M>,
|
||||
pub ratio_pct2: ComputedFromHeightLast<StoredF32, M>,
|
||||
pub ratio_pct1: ComputedFromHeightLast<StoredF32, M>,
|
||||
pub ratio_pct99_usd: Price<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub ratio_pct98_usd: Price<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub ratio_pct95_usd: Price<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub ratio_pct5_usd: Price<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub ratio_pct2_usd: Price<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub ratio_pct1_usd: Price<ComputedFromHeightLast<Dollars, M>>,
|
||||
|
||||
pub ratio_sd: ComputedFromHeightStdDevExtended<M>,
|
||||
pub ratio_4y_sd: ComputedFromHeightStdDevExtended<M>,
|
||||
pub ratio_2y_sd: ComputedFromHeightStdDevExtended<M>,
|
||||
pub ratio_1y_sd: ComputedFromHeightStdDevExtended<M>,
|
||||
}
|
||||
|
||||
const VERSION: Version = Version::TWO;
|
||||
|
||||
impl ComputedFromHeightRatioExtension {
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
name: &str,
|
||||
version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Result<Self> {
|
||||
let v = version + VERSION;
|
||||
|
||||
macro_rules! import {
|
||||
($suffix:expr) => {
|
||||
ComputedFromHeightLast::forced_import(
|
||||
db,
|
||||
&format!("{name}_{}", $suffix),
|
||||
v,
|
||||
indexes,
|
||||
)?
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! import_sd {
|
||||
($suffix:expr, $days:expr) => {
|
||||
ComputedFromHeightStdDevExtended::forced_import(
|
||||
db,
|
||||
&format!("{name}_{}", $suffix),
|
||||
$days,
|
||||
v,
|
||||
indexes,
|
||||
)?
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! import_usd {
|
||||
($suffix:expr) => {
|
||||
Price::forced_import(db, &format!("{name}_{}", $suffix), v, indexes)?
|
||||
};
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
ratio_1w_sma: import!("ratio_1w_sma"),
|
||||
ratio_1m_sma: import!("ratio_1m_sma"),
|
||||
ratio_sd: import_sd!("ratio", usize::MAX),
|
||||
ratio_1y_sd: import_sd!("ratio_1y", 365),
|
||||
ratio_2y_sd: import_sd!("ratio_2y", 2 * 365),
|
||||
ratio_4y_sd: import_sd!("ratio_4y", 4 * 365),
|
||||
ratio_pct99: import!("ratio_pct99"),
|
||||
ratio_pct98: import!("ratio_pct98"),
|
||||
ratio_pct95: import!("ratio_pct95"),
|
||||
ratio_pct5: import!("ratio_pct5"),
|
||||
ratio_pct2: import!("ratio_pct2"),
|
||||
ratio_pct1: import!("ratio_pct1"),
|
||||
ratio_pct99_usd: import_usd!("ratio_pct99_usd"),
|
||||
ratio_pct98_usd: import_usd!("ratio_pct98_usd"),
|
||||
ratio_pct95_usd: import_usd!("ratio_pct95_usd"),
|
||||
ratio_pct5_usd: import_usd!("ratio_pct5_usd"),
|
||||
ratio_pct2_usd: import_usd!("ratio_pct2_usd"),
|
||||
ratio_pct1_usd: import_usd!("ratio_pct1_usd"),
|
||||
})
|
||||
}
|
||||
|
||||
/// Compute extended ratio metrics from an externally-provided ratio source.
|
||||
pub(crate) fn compute_rest(
|
||||
&mut self,
|
||||
blocks: &blocks::Vecs,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
exit: &Exit,
|
||||
ratio_source: &impl ReadableVec<Height, StoredF32>,
|
||||
) -> Result<()> {
|
||||
// SMA using lookback vecs
|
||||
self.ratio_1w_sma.height.compute_rolling_average(
|
||||
starting_indexes.height,
|
||||
&blocks.count.height_1w_ago,
|
||||
ratio_source,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
self.ratio_1m_sma.height.compute_rolling_average(
|
||||
starting_indexes.height,
|
||||
&blocks.count.height_1m_ago,
|
||||
ratio_source,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
// Percentiles: insert into sorted array on day boundaries
|
||||
let ratio_version = ratio_source.version();
|
||||
self.mut_ratio_vecs()
|
||||
.try_for_each(|v| -> Result<()> {
|
||||
v.validate_computed_version_or_reset(ratio_version)?;
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
let starting_height = self
|
||||
.mut_ratio_vecs()
|
||||
.map(|v| Height::from(v.len()))
|
||||
.min()
|
||||
.unwrap()
|
||||
.min(starting_indexes.height);
|
||||
|
||||
let start = starting_height.to_usize();
|
||||
let day_start = &blocks.count.height_24h_ago;
|
||||
|
||||
// Collect sorted history up to starting point (one per day boundary)
|
||||
let mut sorted = {
|
||||
let ratio_data = ratio_source.collect_range_at(0, start);
|
||||
let day_start_hist = day_start.collect_range_at(0, start);
|
||||
let mut sorted: Vec<StoredF32> = Vec::new();
|
||||
let mut last_day_start = Height::from(0_usize);
|
||||
for (h, ratio) in ratio_data.into_iter().enumerate() {
|
||||
let cur_day_start = day_start_hist[h];
|
||||
if h == 0 || cur_day_start != last_day_start {
|
||||
sorted.push(ratio);
|
||||
last_day_start = cur_day_start;
|
||||
}
|
||||
}
|
||||
sorted.sort_unstable();
|
||||
sorted
|
||||
};
|
||||
|
||||
let pct1_vec = &mut self.ratio_pct1.height;
|
||||
let pct2_vec = &mut self.ratio_pct2.height;
|
||||
let pct5_vec = &mut self.ratio_pct5.height;
|
||||
let pct95_vec = &mut self.ratio_pct95.height;
|
||||
let pct98_vec = &mut self.ratio_pct98.height;
|
||||
let pct99_vec = &mut self.ratio_pct99.height;
|
||||
|
||||
let ratio_len = ratio_source.len();
|
||||
let ratio_data = ratio_source.collect_range_at(start, ratio_len);
|
||||
let mut last_day_start = if start > 0 {
|
||||
day_start
|
||||
.collect_one_at(start - 1)
|
||||
.unwrap_or(Height::from(0_usize))
|
||||
} else {
|
||||
Height::from(0_usize)
|
||||
};
|
||||
|
||||
let day_start_data = day_start.collect_range_at(start, ratio_len);
|
||||
|
||||
for (offset, ratio) in ratio_data.into_iter().enumerate() {
|
||||
let index = start + offset;
|
||||
|
||||
let cur_day_start = day_start_data[offset];
|
||||
if index == 0 || cur_day_start != last_day_start {
|
||||
let pos = sorted.binary_search(&ratio).unwrap_or_else(|p| p);
|
||||
sorted.insert(pos, ratio);
|
||||
last_day_start = cur_day_start;
|
||||
}
|
||||
|
||||
if sorted.is_empty() {
|
||||
pct1_vec.truncate_push_at(index, StoredF32::NAN)?;
|
||||
pct2_vec.truncate_push_at(index, StoredF32::NAN)?;
|
||||
pct5_vec.truncate_push_at(index, StoredF32::NAN)?;
|
||||
pct95_vec.truncate_push_at(index, StoredF32::NAN)?;
|
||||
pct98_vec.truncate_push_at(index, StoredF32::NAN)?;
|
||||
pct99_vec.truncate_push_at(index, StoredF32::NAN)?;
|
||||
} else {
|
||||
pct1_vec.truncate_push_at(index, get_percentile(&sorted, 0.01))?;
|
||||
pct2_vec.truncate_push_at(index, get_percentile(&sorted, 0.02))?;
|
||||
pct5_vec.truncate_push_at(index, get_percentile(&sorted, 0.05))?;
|
||||
pct95_vec.truncate_push_at(index, get_percentile(&sorted, 0.95))?;
|
||||
pct98_vec.truncate_push_at(index, get_percentile(&sorted, 0.98))?;
|
||||
pct99_vec.truncate_push_at(index, get_percentile(&sorted, 0.99))?;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let _lock = exit.lock();
|
||||
self.mut_ratio_vecs()
|
||||
.try_for_each(|v| v.flush())?;
|
||||
}
|
||||
|
||||
// Compute stddev at height level
|
||||
self.ratio_sd
|
||||
.compute_all(blocks, starting_indexes, exit, ratio_source)?;
|
||||
self.ratio_4y_sd
|
||||
.compute_all(blocks, starting_indexes, exit, ratio_source)?;
|
||||
self.ratio_2y_sd
|
||||
.compute_all(blocks, starting_indexes, exit, ratio_source)?;
|
||||
self.ratio_1y_sd
|
||||
.compute_all(blocks, starting_indexes, exit, ratio_source)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Compute USD ratio bands: usd_band = metric_price * ratio_percentile
|
||||
pub(crate) fn compute_usd_bands(
|
||||
&mut self,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
metric_price: &impl ReadableVec<Height, Dollars>,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
use crate::internal::PriceTimesRatio;
|
||||
|
||||
macro_rules! compute_band {
|
||||
($usd_field:ident, $band_source:expr) => {
|
||||
self.$usd_field
|
||||
.usd
|
||||
.compute_binary::<Dollars, StoredF32, PriceTimesRatio>(
|
||||
starting_indexes.height,
|
||||
metric_price,
|
||||
$band_source,
|
||||
exit,
|
||||
)?;
|
||||
};
|
||||
}
|
||||
|
||||
compute_band!(ratio_pct99_usd, &self.ratio_pct99.height);
|
||||
compute_band!(ratio_pct98_usd, &self.ratio_pct98.height);
|
||||
compute_band!(ratio_pct95_usd, &self.ratio_pct95.height);
|
||||
compute_band!(ratio_pct5_usd, &self.ratio_pct5.height);
|
||||
compute_band!(ratio_pct2_usd, &self.ratio_pct2.height);
|
||||
compute_band!(ratio_pct1_usd, &self.ratio_pct1.height);
|
||||
|
||||
// Stddev USD bands
|
||||
self.ratio_sd
|
||||
.compute_usd_bands(starting_indexes, metric_price, exit)?;
|
||||
self.ratio_4y_sd
|
||||
.compute_usd_bands(starting_indexes, metric_price, exit)?;
|
||||
self.ratio_2y_sd
|
||||
.compute_usd_bands(starting_indexes, metric_price, exit)?;
|
||||
self.ratio_1y_sd
|
||||
.compute_usd_bands(starting_indexes, metric_price, exit)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn mut_ratio_vecs(
|
||||
&mut self,
|
||||
) -> impl Iterator<Item = &mut EagerVec<PcoVec<Height, StoredF32>>> {
|
||||
[
|
||||
&mut self.ratio_pct1.height,
|
||||
&mut self.ratio_pct2.height,
|
||||
&mut self.ratio_pct5.height,
|
||||
&mut self.ratio_pct95.height,
|
||||
&mut self.ratio_pct98.height,
|
||||
&mut self.ratio_pct99.height,
|
||||
]
|
||||
.into_iter()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
mod extended;
|
||||
mod extension;
|
||||
mod price_extended;
|
||||
|
||||
pub use extended::*;
|
||||
pub use extension::*;
|
||||
pub use price_extended::*;
|
||||
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Dollars, Height, StoredF32, Version};
|
||||
use vecdb::{Database, Exit, ReadableVec, Rw, StorageMode};
|
||||
|
||||
use crate::{ComputeIndexes, indexes};
|
||||
|
||||
use super::ComputedFromHeightLast;
|
||||
|
||||
#[derive(Traversable)]
|
||||
pub struct ComputedFromHeightRatio<M: StorageMode = Rw> {
|
||||
pub ratio: ComputedFromHeightLast<StoredF32, M>,
|
||||
}
|
||||
|
||||
const VERSION: Version = Version::TWO;
|
||||
|
||||
impl ComputedFromHeightRatio {
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
name: &str,
|
||||
version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Result<Self> {
|
||||
let v = version + VERSION;
|
||||
|
||||
Ok(Self {
|
||||
ratio: ComputedFromHeightLast::forced_import(db, &format!("{name}_ratio"), v, indexes)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Compute ratio = close_price / metric_price at height level
|
||||
pub(crate) fn compute_ratio(
|
||||
&mut self,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
close_price: &impl ReadableVec<Height, Dollars>,
|
||||
metric_price: &impl ReadableVec<Height, Dollars>,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.ratio.height.compute_transform2(
|
||||
starting_indexes.height,
|
||||
close_price,
|
||||
metric_price,
|
||||
|(i, close, price, ..)| {
|
||||
if price == Dollars::ZERO {
|
||||
(i, StoredF32::from(1.0))
|
||||
} else {
|
||||
(i, StoredF32::from(close / price))
|
||||
}
|
||||
},
|
||||
exit,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Dollars, Height, Version};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use vecdb::{Database, EagerVec, Exit, PcoVec, Rw, StorageMode};
|
||||
|
||||
use crate::internal::{ComputedFromHeightLast, Price};
|
||||
use crate::{ComputeIndexes, blocks, indexes, prices};
|
||||
|
||||
use super::ComputedFromHeightRatioExtended;
|
||||
|
||||
#[derive(Deref, DerefMut, Traversable)]
|
||||
#[traversable(merge)]
|
||||
pub struct ComputedFromHeightPriceWithRatioExtended<M: StorageMode = Rw> {
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
#[traversable(flatten)]
|
||||
pub inner: ComputedFromHeightRatioExtended<M>,
|
||||
pub price: Price<ComputedFromHeightLast<Dollars, M>>,
|
||||
}
|
||||
|
||||
impl ComputedFromHeightPriceWithRatioExtended {
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
name: &str,
|
||||
version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Result<Self> {
|
||||
let v = version + Version::TWO;
|
||||
Ok(Self {
|
||||
inner: ComputedFromHeightRatioExtended::forced_import(db, name, version, indexes)?,
|
||||
price: Price::forced_import(db, name, v, indexes)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Compute price via closure, then compute ratio + extended metrics.
|
||||
pub(crate) fn compute_all<F>(
|
||||
&mut self,
|
||||
blocks: &blocks::Vecs,
|
||||
prices: &prices::Vecs,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
exit: &Exit,
|
||||
mut compute_price: F,
|
||||
) -> Result<()>
|
||||
where
|
||||
F: FnMut(&mut EagerVec<PcoVec<Height, Dollars>>) -> Result<()>,
|
||||
{
|
||||
compute_price(&mut self.price.usd.height)?;
|
||||
self.inner.compute_rest(
|
||||
blocks,
|
||||
prices,
|
||||
starting_indexes,
|
||||
exit,
|
||||
&self.price.usd.height,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,527 +0,0 @@
|
||||
use std::mem;
|
||||
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Dollars, Height, StoredF32, Version};
|
||||
use vecdb::{
|
||||
AnyStoredVec, AnyVec, Database, EagerVec, Exit, PcoVec, ReadableVec, Rw, StorageMode, VecIndex,
|
||||
WritableVec,
|
||||
};
|
||||
|
||||
use crate::{ComputeIndexes, blocks, indexes};
|
||||
|
||||
use crate::internal::{ComputedFromHeightLast, Price};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct StandardDeviationVecsOptions {
|
||||
zscore: bool,
|
||||
bands: bool,
|
||||
price_bands: bool,
|
||||
}
|
||||
|
||||
impl StandardDeviationVecsOptions {
|
||||
pub(crate) fn add_all(mut self) -> Self {
|
||||
self.zscore = true;
|
||||
self.bands = true;
|
||||
self.price_bands = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn zscore(&self) -> bool {
|
||||
self.zscore
|
||||
}
|
||||
|
||||
pub(crate) fn bands(&self) -> bool {
|
||||
self.bands
|
||||
}
|
||||
|
||||
pub(crate) fn price_bands(&self) -> bool {
|
||||
self.price_bands
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Traversable)]
|
||||
pub struct ComputedFromHeightStdDev<M: StorageMode = Rw> {
|
||||
days: usize,
|
||||
|
||||
pub sma: Option<ComputedFromHeightLast<StoredF32, M>>,
|
||||
|
||||
pub sd: ComputedFromHeightLast<StoredF32, M>,
|
||||
|
||||
pub zscore: Option<ComputedFromHeightLast<StoredF32, M>>,
|
||||
|
||||
pub p0_5sd: Option<ComputedFromHeightLast<StoredF32, M>>,
|
||||
pub p1sd: Option<ComputedFromHeightLast<StoredF32, M>>,
|
||||
pub p1_5sd: Option<ComputedFromHeightLast<StoredF32, M>>,
|
||||
pub p2sd: Option<ComputedFromHeightLast<StoredF32, M>>,
|
||||
pub p2_5sd: Option<ComputedFromHeightLast<StoredF32, M>>,
|
||||
pub p3sd: Option<ComputedFromHeightLast<StoredF32, M>>,
|
||||
pub m0_5sd: Option<ComputedFromHeightLast<StoredF32, M>>,
|
||||
pub m1sd: Option<ComputedFromHeightLast<StoredF32, M>>,
|
||||
pub m1_5sd: Option<ComputedFromHeightLast<StoredF32, M>>,
|
||||
pub m2sd: Option<ComputedFromHeightLast<StoredF32, M>>,
|
||||
pub m2_5sd: Option<ComputedFromHeightLast<StoredF32, M>>,
|
||||
pub m3sd: Option<ComputedFromHeightLast<StoredF32, M>>,
|
||||
|
||||
pub _0sd_usd: Option<Price<ComputedFromHeightLast<Dollars, M>>>,
|
||||
pub p0_5sd_usd: Option<Price<ComputedFromHeightLast<Dollars, M>>>,
|
||||
pub p1sd_usd: Option<Price<ComputedFromHeightLast<Dollars, M>>>,
|
||||
pub p1_5sd_usd: Option<Price<ComputedFromHeightLast<Dollars, M>>>,
|
||||
pub p2sd_usd: Option<Price<ComputedFromHeightLast<Dollars, M>>>,
|
||||
pub p2_5sd_usd: Option<Price<ComputedFromHeightLast<Dollars, M>>>,
|
||||
pub p3sd_usd: Option<Price<ComputedFromHeightLast<Dollars, M>>>,
|
||||
pub m0_5sd_usd: Option<Price<ComputedFromHeightLast<Dollars, M>>>,
|
||||
pub m1sd_usd: Option<Price<ComputedFromHeightLast<Dollars, M>>>,
|
||||
pub m1_5sd_usd: Option<Price<ComputedFromHeightLast<Dollars, M>>>,
|
||||
pub m2sd_usd: Option<Price<ComputedFromHeightLast<Dollars, M>>>,
|
||||
pub m2_5sd_usd: Option<Price<ComputedFromHeightLast<Dollars, M>>>,
|
||||
pub m3sd_usd: Option<Price<ComputedFromHeightLast<Dollars, M>>>,
|
||||
}
|
||||
|
||||
impl ComputedFromHeightStdDev {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
name: &str,
|
||||
days: usize,
|
||||
parent_version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
options: StandardDeviationVecsOptions,
|
||||
) -> Result<Self> {
|
||||
let version = parent_version + Version::TWO;
|
||||
|
||||
macro_rules! import {
|
||||
($suffix:expr) => {
|
||||
ComputedFromHeightLast::forced_import(
|
||||
db,
|
||||
&format!("{name}_{}", $suffix),
|
||||
version,
|
||||
indexes,
|
||||
)
|
||||
.unwrap()
|
||||
};
|
||||
}
|
||||
|
||||
let sma_vec = Some(import!("sma"));
|
||||
let p0_5sd = options.bands().then(|| import!("p0_5sd"));
|
||||
let p1sd = options.bands().then(|| import!("p1sd"));
|
||||
let p1_5sd = options.bands().then(|| import!("p1_5sd"));
|
||||
let p2sd = options.bands().then(|| import!("p2sd"));
|
||||
let p2_5sd = options.bands().then(|| import!("p2_5sd"));
|
||||
let p3sd = options.bands().then(|| import!("p3sd"));
|
||||
let m0_5sd = options.bands().then(|| import!("m0_5sd"));
|
||||
let m1sd = options.bands().then(|| import!("m1sd"));
|
||||
let m1_5sd = options.bands().then(|| import!("m1_5sd"));
|
||||
let m2sd = options.bands().then(|| import!("m2sd"));
|
||||
let m2_5sd = options.bands().then(|| import!("m2_5sd"));
|
||||
let m3sd = options.bands().then(|| import!("m3sd"));
|
||||
|
||||
// Import USD price band vecs (computed eagerly at compute time)
|
||||
macro_rules! lazy_usd {
|
||||
($band:expr, $suffix:expr) => {
|
||||
if !options.price_bands() {
|
||||
None
|
||||
} else {
|
||||
$band.as_ref().map(|_| {
|
||||
Price::forced_import(
|
||||
db,
|
||||
&format!("{name}_{}", $suffix),
|
||||
version,
|
||||
indexes,
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
days,
|
||||
sd: import!("sd"),
|
||||
zscore: options.zscore().then(|| import!("zscore")),
|
||||
// Lazy USD vecs
|
||||
_0sd_usd: lazy_usd!(&sma_vec, "0sd_usd"),
|
||||
p0_5sd_usd: lazy_usd!(&p0_5sd, "p0_5sd_usd"),
|
||||
p1sd_usd: lazy_usd!(&p1sd, "p1sd_usd"),
|
||||
p1_5sd_usd: lazy_usd!(&p1_5sd, "p1_5sd_usd"),
|
||||
p2sd_usd: lazy_usd!(&p2sd, "p2sd_usd"),
|
||||
p2_5sd_usd: lazy_usd!(&p2_5sd, "p2_5sd_usd"),
|
||||
p3sd_usd: lazy_usd!(&p3sd, "p3sd_usd"),
|
||||
m0_5sd_usd: lazy_usd!(&m0_5sd, "m0_5sd_usd"),
|
||||
m1sd_usd: lazy_usd!(&m1sd, "m1sd_usd"),
|
||||
m1_5sd_usd: lazy_usd!(&m1_5sd, "m1_5sd_usd"),
|
||||
m2sd_usd: lazy_usd!(&m2sd, "m2sd_usd"),
|
||||
m2_5sd_usd: lazy_usd!(&m2_5sd, "m2_5sd_usd"),
|
||||
m3sd_usd: lazy_usd!(&m3sd, "m3sd_usd"),
|
||||
// Stored band sources
|
||||
sma: sma_vec,
|
||||
p0_5sd,
|
||||
p1sd,
|
||||
p1_5sd,
|
||||
p2sd,
|
||||
p2_5sd,
|
||||
p3sd,
|
||||
m0_5sd,
|
||||
m1sd,
|
||||
m1_5sd,
|
||||
m2sd,
|
||||
m2_5sd,
|
||||
m3sd,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn forced_import_from_lazy(
|
||||
db: &Database,
|
||||
name: &str,
|
||||
days: usize,
|
||||
parent_version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
options: StandardDeviationVecsOptions,
|
||||
) -> Result<Self> {
|
||||
let version = parent_version + Version::TWO;
|
||||
|
||||
macro_rules! import {
|
||||
($suffix:expr) => {
|
||||
ComputedFromHeightLast::forced_import(
|
||||
db,
|
||||
&format!("{name}_{}", $suffix),
|
||||
version,
|
||||
indexes,
|
||||
)
|
||||
.unwrap()
|
||||
};
|
||||
}
|
||||
|
||||
let sma_vec = Some(import!("sma"));
|
||||
let p0_5sd = options.bands().then(|| import!("p0_5sd"));
|
||||
let p1sd = options.bands().then(|| import!("p1sd"));
|
||||
let p1_5sd = options.bands().then(|| import!("p1_5sd"));
|
||||
let p2sd = options.bands().then(|| import!("p2sd"));
|
||||
let p2_5sd = options.bands().then(|| import!("p2_5sd"));
|
||||
let p3sd = options.bands().then(|| import!("p3sd"));
|
||||
let m0_5sd = options.bands().then(|| import!("m0_5sd"));
|
||||
let m1sd = options.bands().then(|| import!("m1sd"));
|
||||
let m1_5sd = options.bands().then(|| import!("m1_5sd"));
|
||||
let m2sd = options.bands().then(|| import!("m2sd"));
|
||||
let m2_5sd = options.bands().then(|| import!("m2_5sd"));
|
||||
let m3sd = options.bands().then(|| import!("m3sd"));
|
||||
|
||||
// For lazy metric price, use from_lazy_block_last_and_block_last.
|
||||
macro_rules! lazy_usd {
|
||||
($band:expr, $suffix:expr) => {
|
||||
if !options.price_bands() {
|
||||
None
|
||||
} else {
|
||||
$band.as_ref().map(|_| {
|
||||
Price::forced_import(
|
||||
db,
|
||||
&format!("{name}_{}", $suffix),
|
||||
version,
|
||||
indexes,
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
days,
|
||||
sd: import!("sd"),
|
||||
zscore: options.zscore().then(|| import!("zscore")),
|
||||
_0sd_usd: lazy_usd!(&sma_vec, "0sd_usd"),
|
||||
p0_5sd_usd: lazy_usd!(&p0_5sd, "p0_5sd_usd"),
|
||||
p1sd_usd: lazy_usd!(&p1sd, "p1sd_usd"),
|
||||
p1_5sd_usd: lazy_usd!(&p1_5sd, "p1_5sd_usd"),
|
||||
p2sd_usd: lazy_usd!(&p2sd, "p2sd_usd"),
|
||||
p2_5sd_usd: lazy_usd!(&p2_5sd, "p2_5sd_usd"),
|
||||
p3sd_usd: lazy_usd!(&p3sd, "p3sd_usd"),
|
||||
m0_5sd_usd: lazy_usd!(&m0_5sd, "m0_5sd_usd"),
|
||||
m1sd_usd: lazy_usd!(&m1sd, "m1sd_usd"),
|
||||
m1_5sd_usd: lazy_usd!(&m1_5sd, "m1_5sd_usd"),
|
||||
m2sd_usd: lazy_usd!(&m2sd, "m2sd_usd"),
|
||||
m2_5sd_usd: lazy_usd!(&m2_5sd, "m2_5sd_usd"),
|
||||
m3sd_usd: lazy_usd!(&m3sd, "m3sd_usd"),
|
||||
sma: sma_vec,
|
||||
p0_5sd,
|
||||
p1sd,
|
||||
p1_5sd,
|
||||
p2sd,
|
||||
p2_5sd,
|
||||
p3sd,
|
||||
m0_5sd,
|
||||
m1sd,
|
||||
m1_5sd,
|
||||
m2sd,
|
||||
m2_5sd,
|
||||
m3sd,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn compute_all(
|
||||
&mut self,
|
||||
blocks: &blocks::Vecs,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
exit: &Exit,
|
||||
source: &impl ReadableVec<Height, StoredF32>,
|
||||
) -> Result<()> {
|
||||
// 1. Compute SMA using the appropriate lookback vec (or full-history SMA)
|
||||
if self.days != usize::MAX {
|
||||
let window_starts = blocks.count.start_vec(self.days);
|
||||
self.sma.as_mut().unwrap().height.compute_rolling_average(
|
||||
starting_indexes.height,
|
||||
window_starts,
|
||||
source,
|
||||
exit,
|
||||
)?;
|
||||
} else {
|
||||
// Full history SMA (days == usize::MAX)
|
||||
self.sma.as_mut().unwrap().height.compute_sma_(
|
||||
starting_indexes.height,
|
||||
source,
|
||||
self.days,
|
||||
exit,
|
||||
None,
|
||||
)?;
|
||||
}
|
||||
|
||||
let sma_opt: Option<&EagerVec<PcoVec<Height, StoredF32>>> = None;
|
||||
self.compute_rest(blocks, starting_indexes, exit, sma_opt, source)
|
||||
}
|
||||
|
||||
pub(crate) fn compute_rest(
|
||||
&mut self,
|
||||
blocks: &blocks::Vecs,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
exit: &Exit,
|
||||
sma_opt: Option<&impl ReadableVec<Height, StoredF32>>,
|
||||
source: &impl ReadableVec<Height, StoredF32>,
|
||||
) -> Result<()> {
|
||||
let sma = sma_opt
|
||||
.unwrap_or_else(|| unsafe { mem::transmute(&self.sma.as_ref().unwrap().height) });
|
||||
|
||||
let source_version = source.version();
|
||||
|
||||
self.mut_stateful_height_vecs()
|
||||
.try_for_each(|v| -> Result<()> {
|
||||
v.validate_computed_version_or_reset(source_version)?;
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
let starting_height = self
|
||||
.mut_stateful_height_vecs()
|
||||
.map(|v| Height::from(v.len()))
|
||||
.min()
|
||||
.unwrap()
|
||||
.min(starting_indexes.height);
|
||||
|
||||
// Reconstruct running statistics up to starting point.
|
||||
// We accumulate one data point per day boundary, tracking sum and sum_sq
|
||||
// for O(1) per-height SD computation (instead of O(n) sorted-array scan).
|
||||
let day_start = &blocks.count.height_24h_ago;
|
||||
let start = starting_height.to_usize();
|
||||
|
||||
let mut n: usize = 0;
|
||||
let mut welford_sum: f64 = 0.0;
|
||||
let mut welford_sum_sq: f64 = 0.0;
|
||||
if start > 0 {
|
||||
let day_start_hist = day_start.collect_range_at(0, start);
|
||||
let source_hist = source.collect_range_at(0, start);
|
||||
let mut last_ds = Height::from(0_usize);
|
||||
for h in 0..start {
|
||||
let cur_ds = day_start_hist[h];
|
||||
if h == 0 || cur_ds != last_ds {
|
||||
let val = *source_hist[h] as f64;
|
||||
n += 1;
|
||||
welford_sum += val;
|
||||
welford_sum_sq += val * val;
|
||||
last_ds = cur_ds;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! band_ref {
|
||||
($field:ident) => {
|
||||
self.$field.as_mut().map(|c| &mut c.height)
|
||||
};
|
||||
}
|
||||
let mut p0_5sd = band_ref!(p0_5sd);
|
||||
let mut p1sd = band_ref!(p1sd);
|
||||
let mut p1_5sd = band_ref!(p1_5sd);
|
||||
let mut p2sd = band_ref!(p2sd);
|
||||
let mut p2_5sd = band_ref!(p2_5sd);
|
||||
let mut p3sd = band_ref!(p3sd);
|
||||
let mut m0_5sd = band_ref!(m0_5sd);
|
||||
let mut m1sd = band_ref!(m1sd);
|
||||
let mut m1_5sd = band_ref!(m1_5sd);
|
||||
let mut m2sd = band_ref!(m2sd);
|
||||
let mut m2_5sd = band_ref!(m2_5sd);
|
||||
let mut m3sd = band_ref!(m3sd);
|
||||
|
||||
let source_len = source.len();
|
||||
let source_data = source.collect_range_at(start, source_len);
|
||||
let sma_data = sma.collect_range_at(start, sma.len());
|
||||
let mut last_day_start = if start > 0 {
|
||||
day_start
|
||||
.collect_one_at(start - 1)
|
||||
.unwrap_or(Height::from(0_usize))
|
||||
} else {
|
||||
Height::from(0_usize)
|
||||
};
|
||||
|
||||
let day_start_data = day_start.collect_range_at(start, source_len);
|
||||
|
||||
for (offset, ratio) in source_data.into_iter().enumerate() {
|
||||
let index = start + offset;
|
||||
// Update running statistics on day boundaries
|
||||
let cur_day_start = day_start_data[offset];
|
||||
if index == 0 || cur_day_start != last_day_start {
|
||||
let val = *ratio as f64;
|
||||
n += 1;
|
||||
welford_sum += val;
|
||||
welford_sum_sq += val * val;
|
||||
last_day_start = cur_day_start;
|
||||
}
|
||||
|
||||
let average = sma_data[offset];
|
||||
let avg_f64 = *average as f64;
|
||||
|
||||
// SD = sqrt((sum_sq/n - 2*avg*sum/n + avg^2))
|
||||
// This is the population SD of all daily values relative to the current SMA
|
||||
let sd = if n > 0 {
|
||||
let nf = n as f64;
|
||||
let variance =
|
||||
welford_sum_sq / nf - 2.0 * avg_f64 * welford_sum / nf + avg_f64 * avg_f64;
|
||||
StoredF32::from(variance.max(0.0).sqrt() as f32)
|
||||
} else {
|
||||
StoredF32::from(0.0_f32)
|
||||
};
|
||||
|
||||
self.sd.height.truncate_push_at(index, sd)?;
|
||||
if let Some(v) = p0_5sd.as_mut() {
|
||||
v.truncate_push_at(index, average + StoredF32::from(0.5 * *sd))?
|
||||
}
|
||||
if let Some(v) = p1sd.as_mut() {
|
||||
v.truncate_push_at(index, average + sd)?
|
||||
}
|
||||
if let Some(v) = p1_5sd.as_mut() {
|
||||
v.truncate_push_at(index, average + StoredF32::from(1.5 * *sd))?
|
||||
}
|
||||
if let Some(v) = p2sd.as_mut() {
|
||||
v.truncate_push_at(index, average + 2 * sd)?
|
||||
}
|
||||
if let Some(v) = p2_5sd.as_mut() {
|
||||
v.truncate_push_at(index, average + StoredF32::from(2.5 * *sd))?
|
||||
}
|
||||
if let Some(v) = p3sd.as_mut() {
|
||||
v.truncate_push_at(index, average + 3 * sd)?
|
||||
}
|
||||
if let Some(v) = m0_5sd.as_mut() {
|
||||
v.truncate_push_at(index, average - StoredF32::from(0.5 * *sd))?
|
||||
}
|
||||
if let Some(v) = m1sd.as_mut() {
|
||||
v.truncate_push_at(index, average - sd)?
|
||||
}
|
||||
if let Some(v) = m1_5sd.as_mut() {
|
||||
v.truncate_push_at(index, average - StoredF32::from(1.5 * *sd))?
|
||||
}
|
||||
if let Some(v) = m2sd.as_mut() {
|
||||
v.truncate_push_at(index, average - 2 * sd)?
|
||||
}
|
||||
if let Some(v) = m2_5sd.as_mut() {
|
||||
v.truncate_push_at(index, average - StoredF32::from(2.5 * *sd))?
|
||||
}
|
||||
if let Some(v) = m3sd.as_mut() {
|
||||
v.truncate_push_at(index, average - 3 * sd)?
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let _lock = exit.lock();
|
||||
self.mut_stateful_height_vecs()
|
||||
.try_for_each(|v| v.flush())?;
|
||||
}
|
||||
|
||||
if let Some(zscore) = self.zscore.as_mut() {
|
||||
zscore.height.compute_zscore(
|
||||
starting_indexes.height,
|
||||
source,
|
||||
sma,
|
||||
&self.sd.height,
|
||||
exit,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Compute USD price bands: usd_band = metric_price * band_ratio
|
||||
pub(crate) fn compute_usd_bands(
|
||||
&mut self,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
metric_price: &impl ReadableVec<Height, Dollars>,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
use crate::internal::PriceTimesRatio;
|
||||
|
||||
macro_rules! compute_band {
|
||||
($usd_field:ident, $band_field:ident) => {
|
||||
if let Some(usd) = self.$usd_field.as_mut() {
|
||||
if let Some(band) = self.$band_field.as_ref() {
|
||||
usd.usd
|
||||
.compute_binary::<Dollars, StoredF32, PriceTimesRatio>(
|
||||
starting_indexes.height,
|
||||
metric_price,
|
||||
&band.height,
|
||||
exit,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
compute_band!(_0sd_usd, sma);
|
||||
compute_band!(p0_5sd_usd, p0_5sd);
|
||||
compute_band!(p1sd_usd, p1sd);
|
||||
compute_band!(p1_5sd_usd, p1_5sd);
|
||||
compute_band!(p2sd_usd, p2sd);
|
||||
compute_band!(p2_5sd_usd, p2_5sd);
|
||||
compute_band!(p3sd_usd, p3sd);
|
||||
compute_band!(m0_5sd_usd, m0_5sd);
|
||||
compute_band!(m1sd_usd, m1sd);
|
||||
compute_band!(m1_5sd_usd, m1_5sd);
|
||||
compute_band!(m2sd_usd, m2sd);
|
||||
compute_band!(m2_5sd_usd, m2_5sd);
|
||||
compute_band!(m3sd_usd, m3sd);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn mut_stateful_computed(
|
||||
&mut self,
|
||||
) -> impl Iterator<Item = &mut ComputedFromHeightLast<StoredF32>> {
|
||||
[
|
||||
Some(&mut self.sd),
|
||||
self.p0_5sd.as_mut(),
|
||||
self.p1sd.as_mut(),
|
||||
self.p1_5sd.as_mut(),
|
||||
self.p2sd.as_mut(),
|
||||
self.p2_5sd.as_mut(),
|
||||
self.p3sd.as_mut(),
|
||||
self.m0_5sd.as_mut(),
|
||||
self.m1sd.as_mut(),
|
||||
self.m1_5sd.as_mut(),
|
||||
self.m2sd.as_mut(),
|
||||
self.m2_5sd.as_mut(),
|
||||
self.m3sd.as_mut(),
|
||||
]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
}
|
||||
|
||||
fn mut_stateful_height_vecs(
|
||||
&mut self,
|
||||
) -> impl Iterator<Item = &mut EagerVec<PcoVec<Height, StoredF32>>> {
|
||||
self.mut_stateful_computed().map(|c| &mut c.height)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Dollars, Height, StoredF32, Version};
|
||||
use vecdb::{AnyStoredVec, AnyVec, Database, EagerVec, Exit, PcoVec, ReadableVec, Rw, StorageMode, VecIndex, WritableVec};
|
||||
|
||||
use crate::{ComputeIndexes, blocks, indexes};
|
||||
|
||||
use crate::internal::{ComputedFromHeightLast, Price};
|
||||
|
||||
use super::ComputedFromHeightStdDev;
|
||||
|
||||
#[derive(Traversable)]
|
||||
#[traversable(merge)]
|
||||
pub struct ComputedFromHeightStdDevExtended<M: StorageMode = Rw> {
|
||||
#[traversable(flatten)]
|
||||
pub base: ComputedFromHeightStdDev<M>,
|
||||
|
||||
pub zscore: ComputedFromHeightLast<StoredF32, M>,
|
||||
|
||||
pub p0_5sd: ComputedFromHeightLast<StoredF32, M>,
|
||||
pub p1sd: ComputedFromHeightLast<StoredF32, M>,
|
||||
pub p1_5sd: ComputedFromHeightLast<StoredF32, M>,
|
||||
pub p2sd: ComputedFromHeightLast<StoredF32, M>,
|
||||
pub p2_5sd: ComputedFromHeightLast<StoredF32, M>,
|
||||
pub p3sd: ComputedFromHeightLast<StoredF32, M>,
|
||||
pub m0_5sd: ComputedFromHeightLast<StoredF32, M>,
|
||||
pub m1sd: ComputedFromHeightLast<StoredF32, M>,
|
||||
pub m1_5sd: ComputedFromHeightLast<StoredF32, M>,
|
||||
pub m2sd: ComputedFromHeightLast<StoredF32, M>,
|
||||
pub m2_5sd: ComputedFromHeightLast<StoredF32, M>,
|
||||
pub m3sd: ComputedFromHeightLast<StoredF32, M>,
|
||||
|
||||
pub _0sd_usd: Price<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub p0_5sd_usd: Price<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub p1sd_usd: Price<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub p1_5sd_usd: Price<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub p2sd_usd: Price<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub p2_5sd_usd: Price<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub p3sd_usd: Price<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub m0_5sd_usd: Price<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub m1sd_usd: Price<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub m1_5sd_usd: Price<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub m2sd_usd: Price<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub m2_5sd_usd: Price<ComputedFromHeightLast<Dollars, M>>,
|
||||
pub m3sd_usd: Price<ComputedFromHeightLast<Dollars, M>>,
|
||||
}
|
||||
|
||||
impl ComputedFromHeightStdDevExtended {
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
name: &str,
|
||||
days: usize,
|
||||
parent_version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Result<Self> {
|
||||
let version = parent_version + Version::TWO;
|
||||
|
||||
macro_rules! import {
|
||||
($suffix:expr) => {
|
||||
ComputedFromHeightLast::forced_import(
|
||||
db,
|
||||
&format!("{name}_{}", $suffix),
|
||||
version,
|
||||
indexes,
|
||||
)?
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! import_usd {
|
||||
($suffix:expr) => {
|
||||
Price::forced_import(
|
||||
db,
|
||||
&format!("{name}_{}", $suffix),
|
||||
version,
|
||||
indexes,
|
||||
)?
|
||||
};
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
base: ComputedFromHeightStdDev::forced_import(db, name, days, parent_version, indexes)?,
|
||||
zscore: import!("zscore"),
|
||||
p0_5sd: import!("p0_5sd"),
|
||||
p1sd: import!("p1sd"),
|
||||
p1_5sd: import!("p1_5sd"),
|
||||
p2sd: import!("p2sd"),
|
||||
p2_5sd: import!("p2_5sd"),
|
||||
p3sd: import!("p3sd"),
|
||||
m0_5sd: import!("m0_5sd"),
|
||||
m1sd: import!("m1sd"),
|
||||
m1_5sd: import!("m1_5sd"),
|
||||
m2sd: import!("m2sd"),
|
||||
m2_5sd: import!("m2_5sd"),
|
||||
m3sd: import!("m3sd"),
|
||||
_0sd_usd: import_usd!("0sd_usd"),
|
||||
p0_5sd_usd: import_usd!("p0_5sd_usd"),
|
||||
p1sd_usd: import_usd!("p1sd_usd"),
|
||||
p1_5sd_usd: import_usd!("p1_5sd_usd"),
|
||||
p2sd_usd: import_usd!("p2sd_usd"),
|
||||
p2_5sd_usd: import_usd!("p2_5sd_usd"),
|
||||
p3sd_usd: import_usd!("p3sd_usd"),
|
||||
m0_5sd_usd: import_usd!("m0_5sd_usd"),
|
||||
m1sd_usd: import_usd!("m1sd_usd"),
|
||||
m1_5sd_usd: import_usd!("m1_5sd_usd"),
|
||||
m2sd_usd: import_usd!("m2sd_usd"),
|
||||
m2_5sd_usd: import_usd!("m2_5sd_usd"),
|
||||
m3sd_usd: import_usd!("m3sd_usd"),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn compute_all(
|
||||
&mut self,
|
||||
blocks: &blocks::Vecs,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
exit: &Exit,
|
||||
source: &impl ReadableVec<Height, StoredF32>,
|
||||
) -> Result<()> {
|
||||
self.base.compute_all(blocks, starting_indexes, exit, source)?;
|
||||
|
||||
let sma_opt: Option<&EagerVec<PcoVec<Height, StoredF32>>> = None;
|
||||
self.compute_bands(starting_indexes, exit, sma_opt, source)
|
||||
}
|
||||
|
||||
pub(crate) fn compute_bands(
|
||||
&mut self,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
exit: &Exit,
|
||||
sma_opt: Option<&impl ReadableVec<Height, StoredF32>>,
|
||||
source: &impl ReadableVec<Height, StoredF32>,
|
||||
) -> Result<()> {
|
||||
let source_version = source.version();
|
||||
|
||||
self.mut_band_height_vecs()
|
||||
.try_for_each(|v| -> Result<()> {
|
||||
v.validate_computed_version_or_reset(source_version)?;
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
let starting_height = self
|
||||
.mut_band_height_vecs()
|
||||
.map(|v| Height::from(v.len()))
|
||||
.min()
|
||||
.unwrap()
|
||||
.min(starting_indexes.height);
|
||||
|
||||
let start = starting_height.to_usize();
|
||||
|
||||
let source_len = source.len();
|
||||
let source_data = source.collect_range_at(start, source_len);
|
||||
|
||||
let sma_len = sma_opt.map(|s| s.len()).unwrap_or(self.base.sma.height.len());
|
||||
let sma_data: Vec<StoredF32> = if let Some(sma) = sma_opt {
|
||||
sma.collect_range_at(start, sma_len)
|
||||
} else {
|
||||
self.base.sma.height.collect_range_at(start, sma_len)
|
||||
};
|
||||
let sd_data = self.base.sd.height.collect_range_at(start, self.base.sd.height.len());
|
||||
|
||||
for (offset, _ratio) in source_data.into_iter().enumerate() {
|
||||
let index = start + offset;
|
||||
let average = sma_data[offset];
|
||||
let sd = sd_data[offset];
|
||||
|
||||
self.p0_5sd.height.truncate_push_at(index, average + StoredF32::from(0.5 * *sd))?;
|
||||
self.p1sd.height.truncate_push_at(index, average + sd)?;
|
||||
self.p1_5sd.height.truncate_push_at(index, average + StoredF32::from(1.5 * *sd))?;
|
||||
self.p2sd.height.truncate_push_at(index, average + 2 * sd)?;
|
||||
self.p2_5sd.height.truncate_push_at(index, average + StoredF32::from(2.5 * *sd))?;
|
||||
self.p3sd.height.truncate_push_at(index, average + 3 * sd)?;
|
||||
self.m0_5sd.height.truncate_push_at(index, average - StoredF32::from(0.5 * *sd))?;
|
||||
self.m1sd.height.truncate_push_at(index, average - sd)?;
|
||||
self.m1_5sd.height.truncate_push_at(index, average - StoredF32::from(1.5 * *sd))?;
|
||||
self.m2sd.height.truncate_push_at(index, average - 2 * sd)?;
|
||||
self.m2_5sd.height.truncate_push_at(index, average - StoredF32::from(2.5 * *sd))?;
|
||||
self.m3sd.height.truncate_push_at(index, average - 3 * sd)?;
|
||||
}
|
||||
|
||||
{
|
||||
let _lock = exit.lock();
|
||||
self.mut_band_height_vecs()
|
||||
.try_for_each(|v| v.flush())?;
|
||||
}
|
||||
|
||||
if let Some(sma) = sma_opt {
|
||||
self.zscore.height.compute_zscore(
|
||||
starting_indexes.height,
|
||||
source,
|
||||
sma,
|
||||
&self.base.sd.height,
|
||||
exit,
|
||||
)?;
|
||||
} else {
|
||||
self.zscore.height.compute_zscore(
|
||||
starting_indexes.height,
|
||||
source,
|
||||
&self.base.sma.height,
|
||||
&self.base.sd.height,
|
||||
exit,
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Compute USD price bands: usd_band = metric_price * band_ratio
|
||||
pub(crate) fn compute_usd_bands(
|
||||
&mut self,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
metric_price: &impl ReadableVec<Height, Dollars>,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
use crate::internal::PriceTimesRatio;
|
||||
|
||||
macro_rules! compute_band {
|
||||
($usd_field:ident, $band_source:expr) => {
|
||||
self.$usd_field.usd
|
||||
.compute_binary::<Dollars, StoredF32, PriceTimesRatio>(
|
||||
starting_indexes.height,
|
||||
metric_price,
|
||||
$band_source,
|
||||
exit,
|
||||
)?;
|
||||
};
|
||||
}
|
||||
|
||||
compute_band!(_0sd_usd, &self.base.sma.height);
|
||||
compute_band!(p0_5sd_usd, &self.p0_5sd.height);
|
||||
compute_band!(p1sd_usd, &self.p1sd.height);
|
||||
compute_band!(p1_5sd_usd, &self.p1_5sd.height);
|
||||
compute_band!(p2sd_usd, &self.p2sd.height);
|
||||
compute_band!(p2_5sd_usd, &self.p2_5sd.height);
|
||||
compute_band!(p3sd_usd, &self.p3sd.height);
|
||||
compute_band!(m0_5sd_usd, &self.m0_5sd.height);
|
||||
compute_band!(m1sd_usd, &self.m1sd.height);
|
||||
compute_band!(m1_5sd_usd, &self.m1_5sd.height);
|
||||
compute_band!(m2sd_usd, &self.m2sd.height);
|
||||
compute_band!(m2_5sd_usd, &self.m2_5sd.height);
|
||||
compute_band!(m3sd_usd, &self.m3sd.height);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn mut_band_height_vecs(
|
||||
&mut self,
|
||||
) -> impl Iterator<Item = &mut EagerVec<PcoVec<Height, StoredF32>>> {
|
||||
[
|
||||
&mut self.p0_5sd.height,
|
||||
&mut self.p1sd.height,
|
||||
&mut self.p1_5sd.height,
|
||||
&mut self.p2sd.height,
|
||||
&mut self.p2_5sd.height,
|
||||
&mut self.p3sd.height,
|
||||
&mut self.m0_5sd.height,
|
||||
&mut self.m1sd.height,
|
||||
&mut self.m1_5sd.height,
|
||||
&mut self.m2sd.height,
|
||||
&mut self.m2_5sd.height,
|
||||
&mut self.m3sd.height,
|
||||
]
|
||||
.into_iter()
|
||||
}
|
||||
}
|
||||
168
crates/brk_computer/src/internal/multi/from_height/stddev/mod.rs
Normal file
168
crates/brk_computer/src/internal/multi/from_height/stddev/mod.rs
Normal file
@@ -0,0 +1,168 @@
|
||||
mod extended;
|
||||
|
||||
pub use extended::*;
|
||||
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Height, StoredF32, Version};
|
||||
use vecdb::{AnyStoredVec, AnyVec, Database, Exit, ReadableVec, Rw, StorageMode, VecIndex, WritableVec};
|
||||
|
||||
use crate::{ComputeIndexes, blocks, indexes};
|
||||
|
||||
use crate::internal::ComputedFromHeightLast;
|
||||
|
||||
#[derive(Traversable)]
|
||||
pub struct ComputedFromHeightStdDev<M: StorageMode = Rw> {
|
||||
days: usize,
|
||||
pub sma: ComputedFromHeightLast<StoredF32, M>,
|
||||
pub sd: ComputedFromHeightLast<StoredF32, M>,
|
||||
}
|
||||
|
||||
impl ComputedFromHeightStdDev {
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
name: &str,
|
||||
days: usize,
|
||||
parent_version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Result<Self> {
|
||||
let version = parent_version + Version::TWO;
|
||||
|
||||
let sma = ComputedFromHeightLast::forced_import(
|
||||
db,
|
||||
&format!("{name}_sma"),
|
||||
version,
|
||||
indexes,
|
||||
)?;
|
||||
let sd = ComputedFromHeightLast::forced_import(
|
||||
db,
|
||||
&format!("{name}_sd"),
|
||||
version,
|
||||
indexes,
|
||||
)?;
|
||||
|
||||
Ok(Self { days, sma, sd })
|
||||
}
|
||||
|
||||
pub(crate) fn compute_all(
|
||||
&mut self,
|
||||
blocks: &blocks::Vecs,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
exit: &Exit,
|
||||
source: &impl ReadableVec<Height, StoredF32>,
|
||||
) -> Result<()> {
|
||||
// 1. Compute SMA using the appropriate lookback vec (or full-history SMA)
|
||||
if self.days != usize::MAX {
|
||||
let window_starts = blocks.count.start_vec(self.days);
|
||||
self.sma.height.compute_rolling_average(
|
||||
starting_indexes.height,
|
||||
window_starts,
|
||||
source,
|
||||
exit,
|
||||
)?;
|
||||
} else {
|
||||
// Full history SMA (days == usize::MAX)
|
||||
self.sma.height.compute_sma_(
|
||||
starting_indexes.height,
|
||||
source,
|
||||
self.days,
|
||||
exit,
|
||||
None,
|
||||
)?;
|
||||
}
|
||||
|
||||
// Split borrows: sd is mutated, sma is read
|
||||
compute_sd(
|
||||
&mut self.sd,
|
||||
blocks,
|
||||
starting_indexes,
|
||||
exit,
|
||||
&self.sma.height,
|
||||
source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_sd(
|
||||
sd: &mut ComputedFromHeightLast<StoredF32>,
|
||||
blocks: &blocks::Vecs,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
exit: &Exit,
|
||||
sma: &impl ReadableVec<Height, StoredF32>,
|
||||
source: &impl ReadableVec<Height, StoredF32>,
|
||||
) -> Result<()> {
|
||||
let source_version = source.version();
|
||||
|
||||
sd.height
|
||||
.validate_computed_version_or_reset(source_version)?;
|
||||
|
||||
let starting_height = Height::from(sd.height.len()).min(starting_indexes.height);
|
||||
|
||||
let day_start = &blocks.count.height_24h_ago;
|
||||
let start = starting_height.to_usize();
|
||||
|
||||
let mut n: usize = 0;
|
||||
let mut welford_sum: f64 = 0.0;
|
||||
let mut welford_sum_sq: f64 = 0.0;
|
||||
if start > 0 {
|
||||
let day_start_hist = day_start.collect_range_at(0, start);
|
||||
let source_hist = source.collect_range_at(0, start);
|
||||
let mut last_ds = Height::from(0_usize);
|
||||
for h in 0..start {
|
||||
let cur_ds = day_start_hist[h];
|
||||
if h == 0 || cur_ds != last_ds {
|
||||
let val = *source_hist[h] as f64;
|
||||
n += 1;
|
||||
welford_sum += val;
|
||||
welford_sum_sq += val * val;
|
||||
last_ds = cur_ds;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let source_len = source.len();
|
||||
let source_data = source.collect_range_at(start, source_len);
|
||||
let sma_data = sma.collect_range_at(start, sma.len());
|
||||
let mut last_day_start = if start > 0 {
|
||||
day_start
|
||||
.collect_one_at(start - 1)
|
||||
.unwrap_or(Height::from(0_usize))
|
||||
} else {
|
||||
Height::from(0_usize)
|
||||
};
|
||||
|
||||
let day_start_data = day_start.collect_range_at(start, source_len);
|
||||
|
||||
for (offset, ratio) in source_data.into_iter().enumerate() {
|
||||
let index = start + offset;
|
||||
let cur_day_start = day_start_data[offset];
|
||||
if index == 0 || cur_day_start != last_day_start {
|
||||
let val = *ratio as f64;
|
||||
n += 1;
|
||||
welford_sum += val;
|
||||
welford_sum_sq += val * val;
|
||||
last_day_start = cur_day_start;
|
||||
}
|
||||
|
||||
let average = sma_data[offset];
|
||||
let avg_f64 = *average as f64;
|
||||
|
||||
let sd_val = if n > 0 {
|
||||
let nf = n as f64;
|
||||
let variance =
|
||||
welford_sum_sq / nf - 2.0 * avg_f64 * welford_sum / nf + avg_f64 * avg_f64;
|
||||
StoredF32::from(variance.max(0.0).sqrt() as f32)
|
||||
} else {
|
||||
StoredF32::from(0.0_f32)
|
||||
};
|
||||
|
||||
sd.height.truncate_push_at(index, sd_val)?;
|
||||
}
|
||||
|
||||
{
|
||||
let _lock = exit.lock();
|
||||
sd.height.flush()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -10,7 +10,10 @@ use vecdb::{Database, EagerVec, Exit, PcoVec, ReadableCloneableVec, Rw, StorageM
|
||||
|
||||
use crate::{
|
||||
indexes,
|
||||
internal::{ComputedFromHeightCumulativeFull, LazyFromHeightLast, SatsToBitcoin, WindowStarts},
|
||||
internal::{
|
||||
ComputedFromHeightCumulativeFull, LazyFromHeightLast, SatsToBitcoin, SatsToDollars,
|
||||
WindowStarts,
|
||||
},
|
||||
prices,
|
||||
};
|
||||
|
||||
@@ -58,14 +61,10 @@ impl ValueFromHeightFull {
|
||||
self.sats.compute(max_from, windows, exit, compute_sats)?;
|
||||
|
||||
self.usd.compute(max_from, windows, exit, |vec| {
|
||||
Ok(vec.compute_transform2(
|
||||
Ok(vec.compute_binary::<Sats, Dollars, SatsToDollars>(
|
||||
max_from,
|
||||
&self.sats.height,
|
||||
&prices.usd.price,
|
||||
|(h, sats, price, ..)| {
|
||||
let btc = *sats as f64 / 100_000_000.0;
|
||||
(h, Dollars::from(*price * btc))
|
||||
},
|
||||
exit,
|
||||
)?)
|
||||
})
|
||||
|
||||
@@ -10,7 +10,7 @@ use vecdb::{Database, Exit, ReadableCloneableVec, Rw, StorageMode};
|
||||
|
||||
use crate::{
|
||||
indexes, prices,
|
||||
internal::{ComputedFromHeightLast, LazyFromHeightLast, SatsToBitcoin},
|
||||
internal::{ComputedFromHeightLast, LazyFromHeightLast, SatsToBitcoin, SatsToDollars},
|
||||
};
|
||||
|
||||
#[derive(Traversable)]
|
||||
@@ -56,14 +56,10 @@ impl ValueFromHeightLast {
|
||||
max_from: Height,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.usd.height.compute_transform2(
|
||||
self.usd.compute_binary::<Sats, Dollars, SatsToDollars>(
|
||||
max_from,
|
||||
&self.sats.height,
|
||||
&prices.usd.price,
|
||||
|(h, sats, price, ..)| {
|
||||
let btc = *sats as f64 / 100_000_000.0;
|
||||
(h, Dollars::from(*price * btc))
|
||||
},
|
||||
exit,
|
||||
)?;
|
||||
Ok(())
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
//! Value type for Height + Rolling pattern.
|
||||
//!
|
||||
//! Combines ValueFromHeight (sats/btc/usd per height, no period views) with
|
||||
//! StoredValueRollingWindows (rolling sums across 4 windows).
|
||||
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Height, Sats, Version};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use vecdb::{Database, EagerVec, Exit, PcoVec, Rw, StorageMode};
|
||||
|
||||
use crate::{
|
||||
indexes,
|
||||
internal::{StoredValueRollingWindows, ValueFromHeight, WindowStarts},
|
||||
prices,
|
||||
};
|
||||
|
||||
#[derive(Deref, DerefMut, Traversable)]
|
||||
#[traversable(merge)]
|
||||
pub struct ValueFromHeightLastRolling<M: StorageMode = Rw> {
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
#[traversable(flatten)]
|
||||
pub value: ValueFromHeight<M>,
|
||||
pub rolling: StoredValueRollingWindows<M>,
|
||||
}
|
||||
|
||||
const VERSION: Version = Version::ZERO;
|
||||
|
||||
impl ValueFromHeightLastRolling {
|
||||
pub(crate) fn forced_import(
|
||||
db: &Database,
|
||||
name: &str,
|
||||
version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
) -> Result<Self> {
|
||||
let v = version + VERSION;
|
||||
Ok(Self {
|
||||
value: ValueFromHeight::forced_import(db, name, v)?,
|
||||
rolling: StoredValueRollingWindows::forced_import(db, name, v, indexes)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Compute sats height via closure, then USD from price, then rolling windows.
|
||||
pub(crate) fn compute(
|
||||
&mut self,
|
||||
max_from: Height,
|
||||
windows: &WindowStarts<'_>,
|
||||
prices: &prices::Vecs,
|
||||
exit: &Exit,
|
||||
compute_sats: impl FnOnce(&mut EagerVec<PcoVec<Height, Sats>>) -> Result<()>,
|
||||
) -> Result<()> {
|
||||
compute_sats(&mut self.value.sats)?;
|
||||
self.value.compute_usd(prices, max_from, exit)?;
|
||||
self.rolling.compute_rolling_sum(
|
||||
max_from,
|
||||
windows,
|
||||
&self.value.sats,
|
||||
&self.value.usd,
|
||||
exit,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,10 @@ use vecdb::{Database, Exit, ReadableCloneableVec, Rw, StorageMode};
|
||||
|
||||
use crate::{
|
||||
indexes,
|
||||
internal::{ComputedFromHeightCumulative, ComputedFromHeightLast, LazyFromHeightLast, SatsToBitcoin},
|
||||
internal::{
|
||||
ComputedFromHeightCumulative, ComputedFromHeightLast, LazyFromHeightLast, SatsToBitcoin,
|
||||
SatsToDollars,
|
||||
},
|
||||
prices,
|
||||
};
|
||||
|
||||
@@ -57,14 +60,10 @@ impl LazyComputedValueFromHeightCumulative {
|
||||
) -> Result<()> {
|
||||
self.sats.compute_rest(max_from, exit)?;
|
||||
|
||||
self.usd.height.compute_transform2(
|
||||
self.usd.compute_binary::<Sats, Dollars, SatsToDollars>(
|
||||
max_from,
|
||||
&prices.usd.price,
|
||||
&self.sats.height,
|
||||
|(h, price, sats, ..)| {
|
||||
let btc = *sats as f64 / 100_000_000.0;
|
||||
(h, Dollars::from(*price * btc))
|
||||
},
|
||||
&prices.usd.price,
|
||||
exit,
|
||||
)?;
|
||||
Ok(())
|
||||
|
||||
@@ -10,7 +10,10 @@ use vecdb::{Database, EagerVec, Exit, PcoVec, ReadableCloneableVec, Rw, StorageM
|
||||
|
||||
use crate::{
|
||||
indexes, prices,
|
||||
internal::{ComputedFromHeightCumulativeSum, LazyFromHeightLast, SatsToBitcoin, WindowStarts},
|
||||
internal::{
|
||||
ComputedFromHeightCumulativeSum, LazyFromHeightLast, SatsToBitcoin, SatsToDollars,
|
||||
WindowStarts,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Traversable)]
|
||||
@@ -57,14 +60,10 @@ impl ValueFromHeightSumCumulative {
|
||||
self.sats.compute(max_from, windows, exit, compute_sats)?;
|
||||
|
||||
self.usd.compute(max_from, windows, exit, |vec| {
|
||||
Ok(vec.compute_transform2(
|
||||
Ok(vec.compute_binary::<Sats, Dollars, SatsToDollars>(
|
||||
max_from,
|
||||
&self.sats.height,
|
||||
&prices.usd.price,
|
||||
|(h, sats, price, ..)| {
|
||||
let btc = *sats as f64 / 100_000_000.0;
|
||||
(h, Dollars::from(*price * btc))
|
||||
},
|
||||
exit,
|
||||
)?)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user