global: snapshot

This commit is contained in:
nym21
2026-03-02 15:28:13 +01:00
parent 4d97cec869
commit 4e7cd9ab6f
21 changed files with 595 additions and 373 deletions
+16 -16
View File
@@ -1,15 +1,15 @@
use brk_error::Result;
use brk_types::{Day1, StoredU16};
use brk_types::{StoredF32, Timestamp};
use vecdb::{Exit, ReadableVec, VecIndex};
use super::Vecs;
use crate::{ComputeIndexes, indexes, prices, traits::ComputeDrawdown};
use crate::{blocks, ComputeIndexes, prices, traits::ComputeDrawdown};
impl Vecs {
pub(crate) fn compute(
&mut self,
prices: &prices::Vecs,
indexes: &indexes::Vecs,
blocks: &blocks::Vecs,
starting_indexes: &ComputeIndexes,
exit: &Exit,
) -> Result<()> {
@@ -19,28 +19,28 @@ impl Vecs {
exit,
)?;
let mut ath_day: Option<Day1> = None;
let mut ath_ts: Option<Timestamp> = None;
self.days_since_price_ath.height.compute_transform3(
starting_indexes.height,
&self.price_ath.cents.height,
&prices.price.cents.height,
&indexes.height.day1,
|(i, ath, price, day, slf)| {
if ath_day.is_none() {
&blocks.time.timestamp_monotonic,
|(i, ath, price, ts, slf)| {
if ath_ts.is_none() {
let idx = i.to_usize();
ath_day = Some(if idx > 0 {
let prev_days_since = slf.collect_one_at(idx - 1).unwrap();
Day1::from(day.to_usize().saturating_sub(usize::from(prev_days_since)))
ath_ts = Some(if idx > 0 {
let prev_days: StoredF32 = slf.collect_one_at(idx - 1).unwrap();
Timestamp::from((*ts as f64 - *prev_days as f64 * 86400.0) as u32)
} else {
day
ts
});
}
if price == ath {
ath_day = Some(day);
(i, StoredU16::default())
ath_ts = Some(ts);
(i, StoredF32::default())
} else {
let days_since = (day.to_usize() - ath_day.unwrap().to_usize()) as u16;
(i, StoredU16::from(days_since))
let days = ts.difference_in_days_between_float(ath_ts.unwrap());
(i, StoredF32::from(days as f32))
}
},
exit,
@@ -56,7 +56,7 @@ impl Vecs {
prev.replace(if i > 0 {
slf.collect_one_at(i - 1).unwrap()
} else {
StoredU16::ZERO
StoredF32::default()
});
}
let max = prev.unwrap().max(days);
+14 -17
View File
@@ -5,45 +5,42 @@ use vecdb::Database;
use super::Vecs;
use crate::{
indexes,
internal::{
ComputedFromHeight, LazyHeightDerived,
Price, StoredU16ToYears,
},
internal::{ComputedFromHeight, DaysToYears, LazyHeightDerived, Price},
};
const VERSION: Version = Version::ONE;
impl Vecs {
pub(crate) fn forced_import(
db: &Database,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
let price_ath = Price::forced_import(db, "price_ath", version, indexes)?;
let v = version + VERSION;
let max_days_between_price_aths = ComputedFromHeight::forced_import(
db,
"max_days_between_price_aths",
version,
indexes,
)?;
let price_ath = Price::forced_import(db, "price_ath", v, indexes)?;
let max_days_between_price_aths =
ComputedFromHeight::forced_import(db, "max_days_between_price_aths", v, indexes)?;
let max_years_between_price_aths =
LazyHeightDerived::from_computed::<StoredU16ToYears>(
LazyHeightDerived::from_computed::<DaysToYears>(
"max_years_between_price_aths",
version,
v,
&max_days_between_price_aths,
);
let days_since_price_ath =
ComputedFromHeight::forced_import(db, "days_since_price_ath", version, indexes)?;
ComputedFromHeight::forced_import(db, "days_since_price_ath", v, indexes)?;
let years_since_price_ath = LazyHeightDerived::from_computed::<StoredU16ToYears>(
let years_since_price_ath = LazyHeightDerived::from_computed::<DaysToYears>(
"years_since_price_ath",
version,
v,
&days_since_price_ath,
);
let price_drawdown =
ComputedFromHeight::forced_import(db, "price_drawdown", version, indexes)?;
ComputedFromHeight::forced_import(db, "price_drawdown", v, indexes)?;
Ok(Self {
price_ath,
+5 -6
View File
@@ -1,16 +1,15 @@
use brk_traversable::Traversable;
use brk_types::{Cents, StoredF32, StoredU16};
use brk_types::{Cents, StoredF32};
use vecdb::{Rw, StorageMode};
use crate::internal::{ComputedFromHeight, LazyHeightDerived, Price};
/// All-time high related metrics
#[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw> {
pub price_ath: Price<ComputedFromHeight<Cents, M>>,
pub price_drawdown: ComputedFromHeight<StoredF32, M>,
pub days_since_price_ath: ComputedFromHeight<StoredU16, M>,
pub years_since_price_ath: LazyHeightDerived<StoredF32, StoredU16>,
pub max_days_between_price_aths: ComputedFromHeight<StoredU16, M>,
pub max_years_between_price_aths: LazyHeightDerived<StoredF32, StoredU16>,
pub days_since_price_ath: ComputedFromHeight<StoredF32, M>,
pub years_since_price_ath: LazyHeightDerived<StoredF32, StoredF32>,
pub max_days_between_price_aths: ComputedFromHeight<StoredF32, M>,
pub max_years_between_price_aths: LazyHeightDerived<StoredF32, StoredF32>,
}
+1 -3
View File
@@ -18,8 +18,7 @@ impl Vecs {
starting_indexes: &ComputeIndexes,
exit: &Exit,
) -> Result<()> {
// ATH metrics (independent)
self.ath.compute(prices, indexes, starting_indexes, exit)?;
self.ath.compute(prices, blocks, starting_indexes, exit)?;
// Lookback metrics (independent)
self.lookback
@@ -46,7 +45,6 @@ impl Vecs {
.compute(indexes, prices, blocks, &self.lookback, starting_indexes, exit)?;
self.indicators.compute(
indexes,
&mining.rewards,
&self.returns,
&self.range,
+23 -15
View File
@@ -4,10 +4,7 @@ use vecdb::{AnyVec, Exit, ReadableOptionVec, ReadableVec, VecIndex};
use super::Vecs;
use crate::{
ComputeIndexes, blocks, indexes,
internal::{ComputedFromHeight, PercentageDiffCents},
market::lookback,
prices,
ComputeIndexes, blocks, indexes, internal::PercentageDiffCents, market::lookback, prices,
};
const DCA_AMOUNT: Dollars = Dollars::mint(100.0);
@@ -25,9 +22,7 @@ impl Vecs {
let h2d = &indexes.height.day1;
let close = &prices.split.close.usd.day1;
let first_price_di = Day1::try_from(Date::new(2010, 7, 12))
.unwrap()
.to_usize();
let first_price_di = Day1::try_from(Date::new(2010, 7, 12)).unwrap().to_usize();
// Compute per-height DCA sats contribution once (reused by all periods).
// Value = sats_from_dca(close_price) on day-boundary blocks, Sats::ZERO otherwise.
@@ -42,7 +37,10 @@ impl Vecs {
if same_day {
(h, Sats::ZERO)
} else {
let s = close.collect_one_flat(di).map(sats_from_dca).unwrap_or(Sats::ZERO);
let s = close
.collect_one_flat(di)
.map(sats_from_dca)
.unwrap_or(Sats::ZERO);
(h, s)
}
},
@@ -68,7 +66,10 @@ impl Vecs {
.zip_mut_with_days(&self.period_stack)
{
let days = days as usize;
let stack_data = stack.sats.height.collect_range_at(sh, stack.sats.height.len());
let stack_data = stack
.sats
.height
.collect_range_at(sh, stack.sats.height.len());
average_price.cents.height.compute_transform(
starting_indexes.height,
h2d,
@@ -76,9 +77,7 @@ impl Vecs {
let di_usize = di.to_usize();
let stack_sats = stack_data[h.to_usize() - sh];
let avg = if di_usize > first_price_di {
let num_days = days
.min(di_usize + 1)
.min(di_usize + 1 - first_price_di);
let num_days = days.min(di_usize + 1).min(di_usize + 1 - first_price_di);
Cents::from(DCA_AMOUNT * num_days / Bitcoin::from(stack_sats))
} else {
Cents::ZERO
@@ -123,7 +122,10 @@ impl Vecs {
self.period_lump_sum_stack.zip_mut_with_days(&lookback_dca)
{
let total_invested = DCA_AMOUNT * days as usize;
let lookback_data = lookback_price.cents.height.collect_range_at(sh, lookback_price.cents.height.len());
let lookback_data = lookback_price
.cents
.height
.collect_range_at(sh, lookback_price.cents.height.len());
stack.sats.height.compute_transform(
starting_indexes.height,
h2d,
@@ -193,7 +195,10 @@ impl Vecs {
} else {
Sats::ZERO
};
let s = close.collect_one_flat(di).map(sats_from_dca).unwrap_or(Sats::ZERO);
let s = close
.collect_one_flat(di)
.map(sats_from_dca)
.unwrap_or(Sats::ZERO);
prev + s
};
prev_value = result;
@@ -212,7 +217,10 @@ impl Vecs {
.zip(start_days)
{
let from_usize = from.to_usize();
let stack_data = stack.sats.height.collect_range_at(sh, stack.sats.height.len());
let stack_data = stack
.sats
.height
.collect_range_at(sh, stack.sats.height.len());
average_price.cents.height.compute_transform(
starting_indexes.height,
h2d,
@@ -1,10 +1,10 @@
use brk_error::Result;
use brk_types::{Day1, Dollars, StoredF32};
use vecdb::{Exit, ReadableVec};
use brk_types::{Dollars, StoredF32};
use vecdb::Exit;
use super::{super::range, Vecs};
use crate::{
ComputeIndexes, blocks, distribution, indexes,
ComputeIndexes, blocks, distribution,
internal::Ratio32,
mining, prices, transactions,
};
@@ -23,7 +23,6 @@ impl Vecs {
#[allow(clippy::too_many_arguments)]
pub(crate) fn compute(
&mut self,
indexes: &indexes::Vecs,
rewards: &mining::RewardsVecs,
returns: &super::super::returns::Vecs,
range: &range::Vecs,
@@ -106,14 +105,10 @@ impl Vecs {
)?;
}
// Gini (daily, expanded to Height)
let h2d: Vec<Day1> = indexes.height.day1.collect();
let total_heights = h2d.len();
// Gini (per height)
super::gini::compute(
&mut self.gini,
distribution,
&h2d,
total_heights,
starting_indexes,
exit,
)?;
@@ -1,14 +1,12 @@
use brk_error::Result;
use brk_types::{Day1, StoredF32, Version};
use vecdb::{AnyStoredVec, AnyVec, Exit, ReadableOptionVec, VecIndex, WritableVec};
use brk_types::{Sats, StoredF32, StoredU64, Version};
use vecdb::{AnyStoredVec, AnyVec, Exit, ReadableVec, VecIndex, WritableVec};
use crate::{ComputeIndexes, distribution, internal::ComputedFromHeight};
pub(super) fn compute(
gini: &mut ComputedFromHeight<StoredF32>,
distribution: &distribution::Vecs,
h2d: &[Day1],
total_heights: usize,
starting_indexes: &ComputeIndexes,
exit: &Exit,
) -> Result<()> {
@@ -16,11 +14,11 @@ pub(super) fn compute(
let supply_vecs: Vec<&_> = amount_range
.iter()
.map(|c| &c.metrics.supply.total.sats.day1)
.map(|c| &c.metrics.supply.total.sats.height)
.collect();
let count_vecs: Vec<&_> = amount_range
.iter()
.map(|c| &c.metrics.outputs.utxo_count.day1)
.map(|c| &c.metrics.outputs.utxo_count.height)
.collect();
if supply_vecs.is_empty() || supply_vecs.len() != count_vecs.len() {
@@ -39,49 +37,40 @@ pub(super) fn compute(
gini.height
.truncate_if_needed_at(gini.height.len().min(starting_indexes.height.to_usize()))?;
let start_height = gini.height.len();
if start_height >= total_heights {
return Ok(());
}
let num_days = supply_vecs
let total_heights = supply_vecs
.iter()
.map(|v| v.len())
.min()
.unwrap_or(0)
.min(count_vecs.iter().map(|v| v.len()).min().unwrap_or(0));
// Only compute gini for new days (each day is independent)
let start_day = if start_height > 0 {
h2d[start_height].to_usize()
} else {
0
};
let mut gini_new: Vec<f32> = Vec::with_capacity(num_days.saturating_sub(start_day));
let mut buckets: Vec<(u64, u64)> = Vec::with_capacity(supply_vecs.len());
for di in start_day..num_days {
buckets.clear();
let day = Day1::from(di);
for (sv, cv) in supply_vecs.iter().zip(count_vecs.iter()) {
let supply: u64 = sv.collect_one_flat(day).unwrap_or_default().into();
let count: u64 = cv.collect_one_flat(day).unwrap_or_default().into();
buckets.push((count, supply));
}
gini_new.push(gini_from_lorenz(&buckets));
let start_height = gini.height.len();
if start_height >= total_heights {
return Ok(());
}
// Expand to Height
(start_height..total_heights).for_each(|h| {
let di = h2d[h].to_usize();
let offset = di.saturating_sub(start_day);
let val = if offset < gini_new.len() {
StoredF32::from(gini_new[offset])
} else {
StoredF32::NAN
};
gini.height.push(val);
});
// Batch-collect all cohort data for the range [start_height, total_heights)
let n_cohorts = supply_vecs.len();
let supply_data: Vec<Vec<Sats>> = supply_vecs
.iter()
.map(|v| v.collect_range_at(start_height, total_heights))
.collect();
let count_data: Vec<Vec<StoredU64>> = count_vecs
.iter()
.map(|v| v.collect_range_at(start_height, total_heights))
.collect();
let mut buckets: Vec<(u64, u64)> = Vec::with_capacity(n_cohorts);
for offset in 0..total_heights - start_height {
buckets.clear();
for c in 0..n_cohorts {
let supply: u64 = supply_data[c][offset].into();
let count: u64 = count_data[c][offset].into();
buckets.push((count, supply));
}
gini.height
.push(StoredF32::from(gini_from_lorenz(&buckets)));
}
{
let _lock = exit.lock();
@@ -4,6 +4,7 @@ use vecdb::Exit;
use super::MacdChain;
use crate::{ComputeIndexes, blocks, prices};
#[allow(clippy::too_many_arguments)]
pub(super) fn compute(
chain: &mut MacdChain,
blocks: &blocks::Vecs,
@@ -19,19 +20,15 @@ pub(super) fn compute(
let ws_slow = blocks.count.start_vec(slow_days);
let ws_signal = blocks.count.start_vec(signal_days);
chain.ema_fast.height.compute_rolling_ema(
starting_indexes.height,
ws_fast,
close,
exit,
)?;
chain
.ema_fast
.height
.compute_rolling_ema(starting_indexes.height, ws_fast, close, exit)?;
chain.ema_slow.height.compute_rolling_ema(
starting_indexes.height,
ws_slow,
close,
exit,
)?;
chain
.ema_slow
.height
.compute_rolling_ema(starting_indexes.height, ws_slow, close, exit)?;
// MACD line = ema_fast - ema_slow
chain.line.height.compute_subtract(