mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-07-28 11:18:17 -07:00
global: snapshot
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>,
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user