mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-06-08 14:11:56 -07:00
global: snapshot
This commit is contained in:
@@ -193,31 +193,13 @@ impl Vecs {
|
||||
where
|
||||
F: FnOnce(&mut Self) -> &mut EagerVec<PcoVec<Height, Height>>,
|
||||
{
|
||||
let field = get_field(self);
|
||||
let resume_from = field.len().min(starting_indexes.height.to_usize());
|
||||
let mut prev = if resume_from > 0 {
|
||||
field.collect_one_at(resume_from - 1).unwrap()
|
||||
} else {
|
||||
Height::ZERO
|
||||
};
|
||||
let mut cursor = Cursor::new(&time.timestamp_monotonic);
|
||||
cursor.advance(prev.to_usize());
|
||||
let mut prev_ts = cursor.next().unwrap();
|
||||
Ok(field.compute_transform(
|
||||
starting_indexes.height,
|
||||
&time.timestamp_monotonic,
|
||||
|(h, t, ..)| {
|
||||
while t.difference_in_days_between(prev_ts) >= days {
|
||||
prev.increment();
|
||||
prev_ts = cursor.next().unwrap();
|
||||
if prev > h {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
(h, prev)
|
||||
},
|
||||
self.compute_rolling_start_inner(
|
||||
time,
|
||||
starting_indexes,
|
||||
exit,
|
||||
)?)
|
||||
get_field,
|
||||
|t, prev_ts| t.difference_in_days_between(prev_ts) >= days,
|
||||
)
|
||||
}
|
||||
|
||||
fn compute_rolling_start_hours<F>(
|
||||
@@ -230,6 +212,27 @@ impl Vecs {
|
||||
) -> Result<()>
|
||||
where
|
||||
F: FnOnce(&mut Self) -> &mut EagerVec<PcoVec<Height, Height>>,
|
||||
{
|
||||
self.compute_rolling_start_inner(
|
||||
time,
|
||||
starting_indexes,
|
||||
exit,
|
||||
get_field,
|
||||
|t, prev_ts| t.difference_in_hours_between(prev_ts) >= hours,
|
||||
)
|
||||
}
|
||||
|
||||
fn compute_rolling_start_inner<F, D>(
|
||||
&mut self,
|
||||
time: &time::Vecs,
|
||||
starting_indexes: &ComputeIndexes,
|
||||
exit: &Exit,
|
||||
get_field: F,
|
||||
expired: D,
|
||||
) -> Result<()>
|
||||
where
|
||||
F: FnOnce(&mut Self) -> &mut EagerVec<PcoVec<Height, Height>>,
|
||||
D: Fn(Timestamp, Timestamp) -> bool,
|
||||
{
|
||||
let field = get_field(self);
|
||||
let resume_from = field.len().min(starting_indexes.height.to_usize());
|
||||
@@ -245,7 +248,7 @@ impl Vecs {
|
||||
starting_indexes.height,
|
||||
&time.timestamp_monotonic,
|
||||
|(h, t, ..)| {
|
||||
while t.difference_in_hours_between(prev_ts) >= hours {
|
||||
while expired(t, prev_ts) {
|
||||
prev.increment();
|
||||
prev_ts = cursor.next().unwrap();
|
||||
if prev > h {
|
||||
|
||||
@@ -15,7 +15,7 @@ use crate::{
|
||||
CentsPlus, CentsUnsignedToDollars, ComputedFromHeightCumulative, ComputedFromHeight,
|
||||
ComputedFromHeightRatio, NegCentsUnsignedToDollars, ValueFromHeightCumulative, LazyFromHeight,
|
||||
PercentageCentsF32, PercentageCentsSignedCentsF32, PercentageCentsSignedDollarsF32, Price, RatioCents64,
|
||||
StoredF32Identity, ValueFromHeight,
|
||||
Identity, ValueFromHeight,
|
||||
},
|
||||
prices,
|
||||
};
|
||||
@@ -344,7 +344,7 @@ impl RealizedBase {
|
||||
cfg.indexes,
|
||||
)?;
|
||||
|
||||
let mvrv = LazyFromHeight::from_computed::<StoredF32Identity>(
|
||||
let mvrv = LazyFromHeight::from_computed::<Identity<StoredF32>>(
|
||||
&cfg.name("mvrv"),
|
||||
cfg.version,
|
||||
realized_price_extra.ratio.height.read_only_boxed_clone(),
|
||||
|
||||
@@ -4,9 +4,8 @@ use brk_types::{Dollars, Height, Sats, StoredF32, StoredF64, Version};
|
||||
use vecdb::{Exit, ReadableCloneableVec, ReadableVec, Rw, StorageMode};
|
||||
|
||||
use crate::internal::{
|
||||
ComputedFromHeight, LazyFromHeight,
|
||||
ComputedFromHeight, Identity, LazyFromHeight,
|
||||
NegPercentageDollarsF32, PercentageDollarsF32, PercentageSatsF64,
|
||||
StoredF32Identity,
|
||||
};
|
||||
|
||||
use crate::distribution::metrics::{ImportConfig, RealizedBase, UnrealizedBase};
|
||||
@@ -41,7 +40,7 @@ impl RelativeBase {
|
||||
cfg.db, &cfg.name("net_unrealized_pnl_rel_to_market_cap"), cfg.version + v2, cfg.indexes,
|
||||
)?;
|
||||
|
||||
let nupl = LazyFromHeight::from_computed::<StoredF32Identity>(
|
||||
let nupl = LazyFromHeight::from_computed::<Identity<StoredF32>>(
|
||||
&cfg.name("nupl"),
|
||||
cfg.version + v2,
|
||||
net_unrealized_pnl_rel_to_market_cap.height.read_only_boxed_clone(),
|
||||
|
||||
@@ -160,35 +160,15 @@ impl ComputedFromHeightStdDevExtended {
|
||||
.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)?;
|
||||
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() {
|
||||
let index = start + offset;
|
||||
let average = sma_data[offset];
|
||||
let sd = sd_data[offset];
|
||||
vec.truncate_push_at(index, average + StoredF32::from(mult * *sd))?;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -56,26 +56,28 @@ impl SortedBlocks {
|
||||
|
||||
/// Remove one occurrence of value. O(sqrt(n)).
|
||||
fn remove(&mut self, value: f64) -> bool {
|
||||
for (bi, block) in self.blocks.iter_mut().enumerate() {
|
||||
if block.is_empty() {
|
||||
continue;
|
||||
}
|
||||
// If value > block max, it's not in this block
|
||||
if *block.last().unwrap() < value {
|
||||
continue;
|
||||
}
|
||||
let pos = block.partition_point(|a| *a < value);
|
||||
if pos < block.len() && block[pos] == value {
|
||||
block.remove(pos);
|
||||
self.total_len -= 1;
|
||||
if block.is_empty() {
|
||||
self.blocks.remove(bi);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// Value not found (would be in this block range but isn't)
|
||||
if self.blocks.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Binary search for first block whose max >= value
|
||||
let bi = self
|
||||
.blocks
|
||||
.partition_point(|b| b.last().is_some_and(|&last| last < value));
|
||||
if bi >= self.blocks.len() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let block = &mut self.blocks[bi];
|
||||
let pos = block.partition_point(|a| *a < value);
|
||||
if pos < block.len() && block[pos] == value {
|
||||
block.remove(pos);
|
||||
self.total_len -= 1;
|
||||
if block.is_empty() {
|
||||
self.blocks.remove(bi);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
|
||||
@@ -66,20 +66,25 @@ impl TDigest {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find nearest centroid by mean
|
||||
let pos = self
|
||||
// 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))
|
||||
.unwrap_or_else(|i| i.min(self.centroids.len() - 1));
|
||||
.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,
|
||||
};
|
||||
|
||||
// Check neighbors for the actual nearest
|
||||
let nearest = if pos > 0
|
||||
&& (value - self.centroids[pos - 1].mean).abs()
|
||||
< (value - self.centroids[pos].mean).abs()
|
||||
// Find nearest centroid from insert_pos
|
||||
let nearest = if insert_pos >= self.centroids.len() {
|
||||
self.centroids.len() - 1
|
||||
} else if insert_pos == 0 {
|
||||
0
|
||||
} else if (value - self.centroids[insert_pos - 1].mean).abs()
|
||||
< (value - self.centroids[insert_pos].mean).abs()
|
||||
{
|
||||
pos - 1
|
||||
insert_pos - 1
|
||||
} else {
|
||||
pos
|
||||
insert_pos
|
||||
};
|
||||
|
||||
// Compute quantile of nearest centroid
|
||||
@@ -97,15 +102,7 @@ impl TDigest {
|
||||
c.mean = (c.mean * c.weight + value) / (c.weight + 1.0);
|
||||
c.weight += 1.0;
|
||||
} else {
|
||||
// Insert new centroid at sorted position
|
||||
let insert_pos = self
|
||||
.centroids
|
||||
.binary_search_by(|c| {
|
||||
c.mean
|
||||
.partial_cmp(&value)
|
||||
.unwrap_or(std::cmp::Ordering::Equal)
|
||||
})
|
||||
.unwrap_or_else(|i| i);
|
||||
// Insert new centroid at sorted position (reuse insert_pos)
|
||||
self.centroids.insert(
|
||||
insert_pos,
|
||||
Centroid {
|
||||
@@ -148,65 +145,84 @@ impl TDigest {
|
||||
self.centroids = merged;
|
||||
}
|
||||
|
||||
pub fn quantile(&self, q: f64) -> f64 {
|
||||
/// Batch quantile query in a single pass. `qs` must be sorted ascending.
|
||||
pub fn quantiles(&self, qs: &[f64], out: &mut [f64]) {
|
||||
if self.centroids.is_empty() {
|
||||
return 0.0;
|
||||
}
|
||||
if q <= 0.0 {
|
||||
return self.min;
|
||||
}
|
||||
if q >= 1.0 {
|
||||
return self.max;
|
||||
out.iter_mut().for_each(|o| *o = 0.0);
|
||||
return;
|
||||
}
|
||||
if self.centroids.len() == 1 {
|
||||
return self.centroids[0].mean;
|
||||
let mean = self.centroids[0].mean;
|
||||
for (i, &q) in qs.iter().enumerate() {
|
||||
out[i] = if q <= 0.0 {
|
||||
self.min
|
||||
} else if q >= 1.0 {
|
||||
self.max
|
||||
} else {
|
||||
mean
|
||||
};
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let total: f64 = self.centroids.iter().map(|c| c.weight).sum();
|
||||
let target = q * total;
|
||||
let mut cum = 0.0;
|
||||
let mut ci = 0;
|
||||
|
||||
for i in 0..self.centroids.len() {
|
||||
let c = &self.centroids[i];
|
||||
let mid = cum + c.weight / 2.0;
|
||||
for (qi, &q) in qs.iter().enumerate() {
|
||||
if q <= 0.0 {
|
||||
out[qi] = self.min;
|
||||
continue;
|
||||
}
|
||||
if q >= 1.0 {
|
||||
out[qi] = self.max;
|
||||
continue;
|
||||
}
|
||||
|
||||
if target < mid {
|
||||
// Interpolate between previous centroid (or min) and this one
|
||||
if i == 0 {
|
||||
// Between min and first centroid center
|
||||
let first_mid = c.weight / 2.0;
|
||||
if first_mid == 0.0 {
|
||||
return self.min;
|
||||
}
|
||||
return self.min + (c.mean - self.min) * (target / first_mid);
|
||||
let target = q * total;
|
||||
|
||||
// Advance centroids until the current centroid's midpoint exceeds target
|
||||
while ci < self.centroids.len() {
|
||||
let mid = cum + self.centroids[ci].weight / 2.0;
|
||||
if target < mid {
|
||||
break;
|
||||
}
|
||||
let prev = &self.centroids[i - 1];
|
||||
cum += self.centroids[ci].weight;
|
||||
ci += 1;
|
||||
}
|
||||
|
||||
if ci >= self.centroids.len() {
|
||||
// Past all centroids — interpolate between last centroid and max
|
||||
let last = self.centroids.last().unwrap();
|
||||
let last_mid = total - last.weight / 2.0;
|
||||
let remaining = total - last_mid;
|
||||
out[qi] = if remaining == 0.0 {
|
||||
self.max
|
||||
} else {
|
||||
last.mean + (self.max - last.mean) * ((target - last_mid) / remaining)
|
||||
};
|
||||
} else if ci == 0 {
|
||||
// Before first centroid — interpolate between min and first centroid
|
||||
let c = &self.centroids[0];
|
||||
let first_mid = c.weight / 2.0;
|
||||
out[qi] = if first_mid == 0.0 {
|
||||
self.min
|
||||
} else {
|
||||
self.min + (c.mean - self.min) * (target / first_mid)
|
||||
};
|
||||
} else {
|
||||
// Between centroid ci-1 and ci
|
||||
let c = &self.centroids[ci];
|
||||
let prev = &self.centroids[ci - 1];
|
||||
let mid = cum + c.weight / 2.0;
|
||||
let prev_center = cum - prev.weight / 2.0;
|
||||
let frac = if mid == prev_center {
|
||||
0.5
|
||||
} else {
|
||||
(target - prev_center) / (mid - prev_center)
|
||||
};
|
||||
return prev.mean + (c.mean - prev.mean) * frac;
|
||||
out[qi] = prev.mean + (c.mean - prev.mean) * frac;
|
||||
}
|
||||
|
||||
cum += c.weight;
|
||||
}
|
||||
|
||||
// Between last centroid center and max
|
||||
let last = self.centroids.last().unwrap();
|
||||
let last_mid = total - last.weight / 2.0;
|
||||
let remaining = total - last_mid;
|
||||
if remaining == 0.0 {
|
||||
return self.max;
|
||||
}
|
||||
last.mean + (self.max - last.mean) * ((target - last_mid) / remaining)
|
||||
}
|
||||
|
||||
/// Batch quantile query. `qs` must be sorted ascending.
|
||||
pub fn quantiles(&self, qs: &[f64], out: &mut [f64]) {
|
||||
for (i, &q) in qs.iter().enumerate() {
|
||||
out[i] = self.quantile(q);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -215,6 +231,12 @@ impl TDigest {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn quantile(td: &TDigest, q: f64) -> f64 {
|
||||
let mut out = [0.0];
|
||||
td.quantiles(&[q], &mut out);
|
||||
out[0]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_quantiles() {
|
||||
let mut td = TDigest::default();
|
||||
@@ -223,13 +245,13 @@ mod tests {
|
||||
}
|
||||
assert_eq!(td.count(), 1000);
|
||||
|
||||
let median = td.quantile(0.5);
|
||||
let median = quantile(&td, 0.5);
|
||||
assert!((median - 500.0).abs() < 10.0, "median was {median}");
|
||||
|
||||
let p99 = td.quantile(0.99);
|
||||
let p99 = quantile(&td, 0.99);
|
||||
assert!((p99 - 990.0).abs() < 15.0, "p99 was {p99}");
|
||||
|
||||
let p01 = td.quantile(0.01);
|
||||
let p01 = quantile(&td, 0.01);
|
||||
assert!((p01 - 10.0).abs() < 15.0, "p01 was {p01}");
|
||||
}
|
||||
|
||||
@@ -237,16 +259,16 @@ mod tests {
|
||||
fn empty_digest() {
|
||||
let td = TDigest::default();
|
||||
assert_eq!(td.count(), 0);
|
||||
assert_eq!(td.quantile(0.5), 0.0);
|
||||
assert_eq!(quantile(&td, 0.5), 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_value() {
|
||||
let mut td = TDigest::default();
|
||||
td.add(42.0);
|
||||
assert_eq!(td.quantile(0.0), 42.0);
|
||||
assert_eq!(td.quantile(0.5), 42.0);
|
||||
assert_eq!(td.quantile(1.0), 42.0);
|
||||
assert_eq!(quantile(&td, 0.0), 42.0);
|
||||
assert_eq!(quantile(&td, 0.5), 42.0);
|
||||
assert_eq!(quantile(&td, 1.0), 42.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -258,6 +280,6 @@ mod tests {
|
||||
assert_eq!(td.count(), 100);
|
||||
td.reset();
|
||||
assert_eq!(td.count(), 0);
|
||||
assert_eq!(td.quantile(0.5), 0.0);
|
||||
assert_eq!(quantile(&td, 0.5), 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
use brk_types::Cents;
|
||||
use vecdb::UnaryTransform;
|
||||
|
||||
/// Cents -> Cents (identity transform for lazy references)
|
||||
pub struct CentsIdentity;
|
||||
|
||||
impl UnaryTransform<Cents, Cents> for CentsIdentity {
|
||||
#[inline(always)]
|
||||
fn apply(cents: Cents) -> Cents {
|
||||
cents
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
use brk_types::Dollars;
|
||||
use vecdb::UnaryTransform;
|
||||
|
||||
/// Dollars -> Dollars (identity transform for lazy references)
|
||||
pub struct DollarsIdentity;
|
||||
|
||||
impl UnaryTransform<Dollars, Dollars> for DollarsIdentity {
|
||||
#[inline(always)]
|
||||
fn apply(dollars: Dollars) -> Dollars {
|
||||
dollars
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
use brk_types::StoredF32;
|
||||
use vecdb::UnaryTransform;
|
||||
|
||||
/// StoredF32 -> StoredF32 (identity transform for lazy references/proxies)
|
||||
pub struct StoredF32Identity;
|
||||
|
||||
impl UnaryTransform<StoredF32, StoredF32> for StoredF32Identity {
|
||||
#[inline(always)]
|
||||
fn apply(v: StoredF32) -> StoredF32 {
|
||||
v
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ mod bps16_to_float;
|
||||
mod bps32_to_float;
|
||||
mod block_count_target;
|
||||
mod cents_halve;
|
||||
mod cents_identity;
|
||||
mod identity;
|
||||
mod cents_plus;
|
||||
mod cents_signed_to_dollars;
|
||||
mod cents_subtract_to_cents_signed;
|
||||
@@ -12,9 +12,7 @@ mod cents_times_tenths;
|
||||
mod cents_to_dollars;
|
||||
mod cents_to_sats;
|
||||
mod dollar_halve;
|
||||
mod dollar_identity;
|
||||
mod dollars_to_sats_fract;
|
||||
mod f32_identity;
|
||||
mod neg_cents_to_dollars;
|
||||
mod ohlc_cents_to_dollars;
|
||||
mod ohlc_cents_to_sats;
|
||||
@@ -30,6 +28,7 @@ mod percentage_u32_f32;
|
||||
mod price_times_ratio_cents;
|
||||
mod ratio32;
|
||||
mod ratio_cents64;
|
||||
mod per_sec;
|
||||
mod ratio_u64_f32;
|
||||
mod return_f32_tenths;
|
||||
mod return_i8;
|
||||
@@ -38,13 +37,10 @@ mod sats_to_cents;
|
||||
|
||||
mod sat_halve;
|
||||
mod sat_halve_to_bitcoin;
|
||||
mod sat_identity;
|
||||
mod sat_mask;
|
||||
mod sat_to_bitcoin;
|
||||
mod days_to_years;
|
||||
mod volatility_sqrt30;
|
||||
mod volatility_sqrt365;
|
||||
mod volatility_sqrt7;
|
||||
mod volatility;
|
||||
|
||||
pub use bp16_to_float::*;
|
||||
pub use bp32_to_float::*;
|
||||
@@ -52,7 +48,7 @@ pub use bps16_to_float::*;
|
||||
pub use bps32_to_float::*;
|
||||
pub use block_count_target::*;
|
||||
pub use cents_halve::*;
|
||||
pub use cents_identity::*;
|
||||
pub use identity::*;
|
||||
pub use cents_plus::*;
|
||||
pub use cents_signed_to_dollars::*;
|
||||
pub use cents_subtract_to_cents_signed::*;
|
||||
@@ -67,9 +63,7 @@ pub use percentage_cents_signed_dollars_f32::*;
|
||||
pub use percentage_cents_signed_f32::*;
|
||||
|
||||
pub use dollar_halve::*;
|
||||
pub use dollar_identity::*;
|
||||
pub use dollars_to_sats_fract::*;
|
||||
pub use f32_identity::*;
|
||||
pub use percentage_diff_close_cents::*;
|
||||
pub use percentage_diff_close_dollars::*;
|
||||
pub use percentage_dollars_f32::*;
|
||||
@@ -79,6 +73,7 @@ pub use percentage_u32_f32::*;
|
||||
pub use price_times_ratio_cents::*;
|
||||
pub use ratio32::*;
|
||||
pub use ratio_cents64::*;
|
||||
pub use per_sec::*;
|
||||
pub use ratio_u64_f32::*;
|
||||
pub use return_f32_tenths::*;
|
||||
pub use return_i8::*;
|
||||
@@ -86,10 +81,7 @@ pub use return_u16::*;
|
||||
pub use sats_to_cents::*;
|
||||
pub use sat_halve::*;
|
||||
pub use sat_halve_to_bitcoin::*;
|
||||
pub use sat_identity::*;
|
||||
pub use sat_mask::*;
|
||||
pub use sat_to_bitcoin::*;
|
||||
pub use days_to_years::*;
|
||||
pub use volatility_sqrt7::*;
|
||||
pub use volatility_sqrt30::*;
|
||||
pub use volatility_sqrt365::*;
|
||||
pub use volatility::*;
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
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,12 +0,0 @@
|
||||
use brk_types::Sats;
|
||||
use vecdb::UnaryTransform;
|
||||
|
||||
/// Sats -> Sats (identity transform for lazy references)
|
||||
pub struct SatsIdentity;
|
||||
|
||||
impl UnaryTransform<Sats, Sats> for SatsIdentity {
|
||||
#[inline(always)]
|
||||
fn apply(sats: Sats) -> Sats {
|
||||
sats
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
use brk_types::StoredF32;
|
||||
use vecdb::UnaryTransform;
|
||||
|
||||
/// StoredF32 × sqrt(30) -> StoredF32 (1-month volatility from daily SD)
|
||||
pub struct StoredF32TimesSqrt30;
|
||||
|
||||
impl UnaryTransform<StoredF32, StoredF32> for StoredF32TimesSqrt30 {
|
||||
#[inline(always)]
|
||||
fn apply(v: StoredF32) -> StoredF32 {
|
||||
// 30.0_f32.sqrt() = 5.477226
|
||||
(*v * 5.477226_f32).into()
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
use brk_types::StoredF32;
|
||||
use vecdb::UnaryTransform;
|
||||
|
||||
/// StoredF32 × sqrt(365) -> StoredF32 (1-year volatility from daily SD)
|
||||
pub struct StoredF32TimesSqrt365;
|
||||
|
||||
impl UnaryTransform<StoredF32, StoredF32> for StoredF32TimesSqrt365 {
|
||||
#[inline(always)]
|
||||
fn apply(v: StoredF32) -> StoredF32 {
|
||||
// 365.0_f32.sqrt() = 19.104973
|
||||
(*v * 19.104973_f32).into()
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
use brk_types::StoredF32;
|
||||
use vecdb::UnaryTransform;
|
||||
|
||||
/// StoredF32 × sqrt(7) -> StoredF32 (1-week volatility from daily SD)
|
||||
pub struct StoredF32TimesSqrt7;
|
||||
|
||||
impl UnaryTransform<StoredF32, StoredF32> for StoredF32TimesSqrt7 {
|
||||
#[inline(always)]
|
||||
fn apply(v: StoredF32) -> StoredF32 {
|
||||
// 7.0_f32.sqrt() = 2.6457513
|
||||
(*v * 2.6457513_f32).into()
|
||||
}
|
||||
}
|
||||
@@ -15,61 +15,15 @@ impl Vecs {
|
||||
) -> Result<()> {
|
||||
let price = &prices.price.cents.height;
|
||||
|
||||
self.price_1w_min.cents.height.compute_rolling_min_from_starts(
|
||||
starting_indexes.height,
|
||||
&blocks.count.height_1w_ago,
|
||||
price,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
self.price_1w_max.cents.height.compute_rolling_max_from_starts(
|
||||
starting_indexes.height,
|
||||
&blocks.count.height_1w_ago,
|
||||
price,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
self.price_2w_min.cents.height.compute_rolling_min_from_starts(
|
||||
starting_indexes.height,
|
||||
&blocks.count.height_2w_ago,
|
||||
price,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
self.price_2w_max.cents.height.compute_rolling_max_from_starts(
|
||||
starting_indexes.height,
|
||||
&blocks.count.height_2w_ago,
|
||||
price,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
self.price_1m_min.cents.height.compute_rolling_min_from_starts(
|
||||
starting_indexes.height,
|
||||
&blocks.count.height_1m_ago,
|
||||
price,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
self.price_1m_max.cents.height.compute_rolling_max_from_starts(
|
||||
starting_indexes.height,
|
||||
&blocks.count.height_1m_ago,
|
||||
price,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
self.price_1y_min.cents.height.compute_rolling_min_from_starts(
|
||||
starting_indexes.height,
|
||||
&blocks.count.height_1y_ago,
|
||||
price,
|
||||
exit,
|
||||
)?;
|
||||
|
||||
self.price_1y_max.cents.height.compute_rolling_max_from_starts(
|
||||
starting_indexes.height,
|
||||
&blocks.count.height_1y_ago,
|
||||
price,
|
||||
exit,
|
||||
)?;
|
||||
for (min_vec, max_vec, starts) in [
|
||||
(&mut self.price_1w_min.cents.height, &mut self.price_1w_max.cents.height, &blocks.count.height_1w_ago),
|
||||
(&mut self.price_2w_min.cents.height, &mut self.price_2w_max.cents.height, &blocks.count.height_2w_ago),
|
||||
(&mut self.price_1m_min.cents.height, &mut self.price_1m_max.cents.height, &blocks.count.height_1m_ago),
|
||||
(&mut self.price_1y_min.cents.height, &mut self.price_1y_max.cents.height, &blocks.count.height_1y_ago),
|
||||
] {
|
||||
min_vec.compute_rolling_min_from_starts(starting_indexes.height, starts, price, exit)?;
|
||||
max_vec.compute_rolling_max_from_starts(starting_indexes.height, starts, price, exit)?;
|
||||
}
|
||||
|
||||
// True range at block level: |price[h] - price[h-1]|
|
||||
let mut prev_price = None;
|
||||
|
||||
@@ -6,8 +6,7 @@ use super::super::returns;
|
||||
use super::Vecs;
|
||||
use crate::indexes;
|
||||
use crate::internal::{
|
||||
ComputedFromHeight, LazyFromHeight, StoredF32TimesSqrt7, StoredF32TimesSqrt30,
|
||||
StoredF32TimesSqrt365,
|
||||
ComputedFromHeight, Days30, Days365, Days7, LazyFromHeight, TimesSqrt,
|
||||
};
|
||||
|
||||
impl Vecs {
|
||||
@@ -19,21 +18,21 @@ impl Vecs {
|
||||
) -> Result<Self> {
|
||||
let v2 = Version::TWO;
|
||||
|
||||
let price_1w_volatility = LazyFromHeight::from_computed::<StoredF32TimesSqrt7>(
|
||||
let price_1w_volatility = LazyFromHeight::from_computed::<TimesSqrt<Days7>>(
|
||||
"price_1w_volatility",
|
||||
version + v2,
|
||||
returns._1d_returns_1w_sd.sd.height.read_only_boxed_clone(),
|
||||
&returns._1d_returns_1w_sd.sd,
|
||||
);
|
||||
|
||||
let price_1m_volatility = LazyFromHeight::from_computed::<StoredF32TimesSqrt30>(
|
||||
let price_1m_volatility = LazyFromHeight::from_computed::<TimesSqrt<Days30>>(
|
||||
"price_1m_volatility",
|
||||
version + v2,
|
||||
returns._1d_returns_1m_sd.sd.height.read_only_boxed_clone(),
|
||||
&returns._1d_returns_1m_sd.sd,
|
||||
);
|
||||
|
||||
let price_1y_volatility = LazyFromHeight::from_computed::<StoredF32TimesSqrt365>(
|
||||
let price_1y_volatility = LazyFromHeight::from_computed::<TimesSqrt<Days365>>(
|
||||
"price_1y_volatility",
|
||||
version + v2,
|
||||
returns._1d_returns_1y_sd.sd.height.read_only_boxed_clone(),
|
||||
|
||||
@@ -2,15 +2,14 @@ use std::path::Path;
|
||||
|
||||
use brk_error::Result;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Cents, Version};
|
||||
use brk_types::{Cents, Dollars, Sats, Version};
|
||||
use vecdb::{Database, PAGE_SIZE};
|
||||
|
||||
use super::Vecs;
|
||||
use crate::{
|
||||
distribution, indexes,
|
||||
internal::{
|
||||
CentsIdentity, ComputedFromHeight, DollarsIdentity, LazyFromHeight, LazyValueFromHeight,
|
||||
SatsIdentity, SatsToBitcoin,
|
||||
ComputedFromHeight, Identity, LazyFromHeight, LazyValueFromHeight, SatsToBitcoin,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -31,10 +30,10 @@ impl Vecs {
|
||||
|
||||
// Circulating supply - lazy refs to distribution
|
||||
let circulating = LazyValueFromHeight::from_block_source::<
|
||||
SatsIdentity,
|
||||
Identity<Sats>,
|
||||
SatsToBitcoin,
|
||||
CentsIdentity,
|
||||
DollarsIdentity,
|
||||
Identity<Cents>,
|
||||
Identity<Dollars>,
|
||||
>("circulating_supply", &supply_metrics.total, version);
|
||||
|
||||
// Burned/unspendable supply - computed from scripts
|
||||
@@ -48,7 +47,7 @@ impl Vecs {
|
||||
let velocity = super::velocity::Vecs::forced_import(&db, version, indexes)?;
|
||||
|
||||
// Market cap - lazy identity from distribution supply in USD
|
||||
let market_cap = LazyFromHeight::from_lazy::<DollarsIdentity, Cents>(
|
||||
let market_cap = LazyFromHeight::from_lazy::<Identity<Dollars>, Cents>(
|
||||
"market_cap",
|
||||
version,
|
||||
&supply_metrics.total.usd,
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use brk_error::Result;
|
||||
use brk_indexer::Indexer;
|
||||
use brk_types::StoredF32;
|
||||
use brk_types::Timestamp;
|
||||
use vecdb::Exit;
|
||||
|
||||
use super::Vecs;
|
||||
use crate::transactions::{count, fees};
|
||||
use crate::{ComputeIndexes, blocks, indexes, inputs, outputs, prices};
|
||||
use crate::{ComputeIndexes, blocks, indexes, inputs, internal::PerSec, outputs, prices};
|
||||
|
||||
impl Vecs {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@@ -67,54 +67,22 @@ impl Vecs {
|
||||
self.annualized_volume
|
||||
.compute(prices, starting_indexes.height, exit)?;
|
||||
|
||||
// tx_per_sec: per-block tx count / block interval
|
||||
self.tx_per_sec.height.compute_transform2(
|
||||
self.tx_per_sec.height.compute_binary::<_, Timestamp, PerSec>(
|
||||
starting_indexes.height,
|
||||
&count_vecs.tx_count.height,
|
||||
&blocks.interval.height,
|
||||
|(h, tx_count, interval, ..)| {
|
||||
let interval_f64 = f64::from(*interval);
|
||||
let per_sec = if interval_f64 > 0.0 {
|
||||
StoredF32::from(*tx_count as f64 / interval_f64)
|
||||
} else {
|
||||
StoredF32::NAN
|
||||
};
|
||||
(h, per_sec)
|
||||
},
|
||||
exit,
|
||||
)?;
|
||||
|
||||
// inputs_per_sec: per-block input count / block interval
|
||||
self.inputs_per_sec.height.compute_transform2(
|
||||
self.inputs_per_sec.height.compute_binary::<_, Timestamp, PerSec>(
|
||||
starting_indexes.height,
|
||||
&inputs_count.full.sum_cumulative.sum.0,
|
||||
&blocks.interval.height,
|
||||
|(h, input_count, interval, ..)| {
|
||||
let interval_f64 = f64::from(*interval);
|
||||
let per_sec = if interval_f64 > 0.0 {
|
||||
StoredF32::from(*input_count as f64 / interval_f64)
|
||||
} else {
|
||||
StoredF32::NAN
|
||||
};
|
||||
(h, per_sec)
|
||||
},
|
||||
exit,
|
||||
)?;
|
||||
|
||||
// outputs_per_sec: per-block output count / block interval
|
||||
self.outputs_per_sec.height.compute_transform2(
|
||||
self.outputs_per_sec.height.compute_binary::<_, Timestamp, PerSec>(
|
||||
starting_indexes.height,
|
||||
&outputs_count.total_count.full.sum_cumulative.sum.0,
|
||||
&blocks.interval.height,
|
||||
|(h, output_count, interval, ..)| {
|
||||
let interval_f64 = f64::from(*interval);
|
||||
let per_sec = if interval_f64 > 0.0 {
|
||||
StoredF32::from(*output_count as f64 / interval_f64)
|
||||
} else {
|
||||
StoredF32::NAN
|
||||
};
|
||||
(h, per_sec)
|
||||
},
|
||||
exit,
|
||||
)?;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user