computer: snapshot

This commit is contained in:
nym21
2026-02-27 11:17:06 +01:00
parent c75421f46e
commit e7a5ab9450
45 changed files with 559 additions and 611 deletions
@@ -23,7 +23,7 @@ impl Vecs {
self.hodl_bank.compute_cumulative_transformed_binary(
starting_indexes.height,
&prices.usd.price,
&prices.price.usd,
&self.vocdd_365d_median,
|price, median| StoredF64::from(f64::from(price) - f64::from(median)),
exit,
@@ -31,7 +31,7 @@ impl Vecs {
self.reserve_risk.height.compute_divide(
starting_indexes.height,
&prices.usd.price,
&prices.price.usd,
&self.hodl_bank,
exit,
)?;
@@ -45,7 +45,7 @@ impl Vecs {
.compute(starting_indexes.height, &window_starts, exit, |vec| {
vec.compute_multiply(
starting_indexes.height,
&prices.usd.price,
&prices.price.usd,
&coinblocks_destroyed.height,
exit,
)?;
@@ -56,7 +56,7 @@ impl Vecs {
.compute(starting_indexes.height, &window_starts, exit, |vec| {
vec.compute_multiply(
starting_indexes.height,
&prices.usd.price,
&prices.price.usd,
&activity.coinblocks_created.height,
exit,
)?;
@@ -67,7 +67,7 @@ impl Vecs {
.compute(starting_indexes.height, &window_starts, exit, |vec| {
vec.compute_multiply(
starting_indexes.height,
&prices.usd.price,
&prices.price.usd,
&activity.coinblocks_stored.height,
exit,
)?;
@@ -81,7 +81,7 @@ impl Vecs {
.compute(starting_indexes.height, &window_starts, exit, |vec| {
vec.compute_transform3(
starting_indexes.height,
&prices.usd.price,
&prices.price.usd,
&coindays_destroyed.height,
circulating_supply,
|(i, price, cdd, supply, _): (_, Dollars, StoredF64, Bitcoin, _)| {
@@ -31,7 +31,7 @@ use super::{
},
BIP30_DUPLICATE_HEIGHT_1, BIP30_DUPLICATE_HEIGHT_2, BIP30_ORIGINAL_HEIGHT_1,
BIP30_ORIGINAL_HEIGHT_2, ComputeContext, FLUSH_INTERVAL, TxInReaders, TxOutReaders,
VecsReaders, build_txinindex_to_txindex, build_txoutindex_to_txindex,
IndexToTxIndexBuf, VecsReaders,
};
/// Process all blocks from starting_height to last_height.
@@ -78,7 +78,7 @@ pub(crate) fn process_blocks(
let txindex_to_input_count = &indexes.txindex.input_count;
// From price - use cents for computation:
let height_to_price = &prices.cents.price;
let height_to_price = &prices.price.cents;
// Access pre-computed vectors from context for thread-safe access
let height_to_price_vec = &ctx.height_to_price;
@@ -127,9 +127,11 @@ pub(crate) fn process_blocks(
};
debug!("txindex_to_height RangeMap built");
// Create reusable iterators for sequential txout/txin reads (16KB buffered)
// Create reusable iterators and buffers for per-block reads
let mut txout_iters = TxOutReaders::new(indexer);
let mut txin_iters = TxInReaders::new(indexer, inputs, &mut txindex_to_height);
let mut txout_to_txindex_buf = IndexToTxIndexBuf::new();
let mut txin_to_txindex_buf = IndexToTxIndexBuf::new();
// Pre-collect first address indexes per type for the block range
let first_p2a_vec = indexer
@@ -230,11 +232,11 @@ pub(crate) fn process_blocks(
debug_assert_eq!(ctx.timestamp_at(height), timestamp);
debug_assert_eq!(ctx.price_at(height), block_price);
// Build txindex mappings for this block (pass ReadableVec refs directly)
// Build txindex mappings for this block (reuses internal buffers)
let txoutindex_to_txindex =
build_txoutindex_to_txindex(first_txindex, tx_count, txindex_to_output_count);
txout_to_txindex_buf.build(first_txindex, tx_count, txindex_to_output_count);
let txinindex_to_txindex =
build_txinindex_to_txindex(first_txindex, tx_count, txindex_to_input_count);
txin_to_txindex_buf.build(first_txindex, tx_count, txindex_to_input_count);
// Get first address indexes for this height from pre-collected vecs
let first_addressindexes = ByAddressType {
@@ -125,7 +125,7 @@ impl ComputeContext {
blocks.time.timestamp_monotonic.collect();
let height_to_price: Vec<Cents> =
prices.cents.price.collect();
prices.price.cents.collect();
// Build sparse table for O(1) range max queries on prices
// Used for computing peak price during UTXO holding periods (peak regret)
@@ -7,10 +7,7 @@ mod write;
pub(crate) use block_loop::process_blocks;
pub(crate) use context::{ComputeContext, PriceRangeMax};
pub(crate) use readers::{
TxInReaders, TxOutData, TxOutReaders, VecsReaders, build_txinindex_to_txindex,
build_txoutindex_to_txindex,
};
pub(crate) use readers::{IndexToTxIndexBuf, TxInReaders, TxOutData, TxOutReaders, VecsReaders};
pub(crate) use recover::{StartMode, determine_start_mode, recover_state, reset_state};
/// Flush checkpoint interval (every N blocks).
@@ -153,42 +153,39 @@ impl VecsReaders {
}
}
/// Build txoutindex -> txindex mapping for a block.
pub(crate) fn build_txoutindex_to_txindex(
block_first_txindex: TxIndex,
block_tx_count: u64,
txindex_to_count: &impl ReadableVec<TxIndex, StoredU64>,
) -> Vec<TxIndex> {
build_index_to_txindex(block_first_txindex, block_tx_count, txindex_to_count)
/// Reusable buffers for per-block txindex mapping construction.
pub(crate) struct IndexToTxIndexBuf {
counts: Vec<StoredU64>,
result: Vec<TxIndex>,
}
/// Build txinindex -> txindex mapping for a block.
pub(crate) fn build_txinindex_to_txindex(
block_first_txindex: TxIndex,
block_tx_count: u64,
txindex_to_count: &impl ReadableVec<TxIndex, StoredU64>,
) -> Vec<TxIndex> {
build_index_to_txindex(block_first_txindex, block_tx_count, txindex_to_count)
}
/// Build index -> txindex mapping for a block (shared implementation).
fn build_index_to_txindex(
block_first_txindex: TxIndex,
block_tx_count: u64,
txindex_to_count: &impl ReadableVec<TxIndex, StoredU64>,
) -> Vec<TxIndex> {
let first = block_first_txindex.to_usize();
let counts: Vec<StoredU64> =
txindex_to_count.collect_range_at(first, first + block_tx_count as usize);
let total: u64 = counts.iter().map(|c| u64::from(*c)).sum();
let mut result = Vec::with_capacity(total as usize);
for (offset, count) in counts.iter().enumerate() {
let txindex = TxIndex::from(first + offset);
result.extend(std::iter::repeat_n(txindex, u64::from(*count) as usize));
impl IndexToTxIndexBuf {
pub(crate) fn new() -> Self {
Self {
counts: Vec::new(),
result: Vec::new(),
}
}
result
/// Build index -> txindex mapping for a block, reusing internal buffers.
pub(crate) fn build(
&mut self,
block_first_txindex: TxIndex,
block_tx_count: u64,
txindex_to_count: &impl ReadableVec<TxIndex, StoredU64>,
) -> &[TxIndex] {
let first = block_first_txindex.to_usize();
txindex_to_count.collect_range_into_at(first, first + block_tx_count as usize, &mut self.counts);
let total: u64 = self.counts.iter().map(|c| u64::from(*c)).sum();
self.result.clear();
self.result.reserve(total as usize);
for (offset, count) in self.counts.iter().enumerate() {
let txindex = TxIndex::from(first + offset);
self.result.extend(std::iter::repeat_n(txindex, u64::from(*count) as usize));
}
&self.result
}
}
@@ -697,15 +697,26 @@ impl RealizedBase {
.min(self.investor_price_cents.height.len());
let end = others.iter().map(|o| o.cap_raw.len()).min().unwrap_or(0);
// Pre-collect all cohort data to avoid per-element BytesVec reads in nested loop
let cap_ranges: Vec<Vec<CentsSats>> = others
.iter()
.map(|o| o.cap_raw.collect_range_at(start, end))
.collect();
let investor_cap_ranges: Vec<Vec<CentsSquaredSats>> = others
.iter()
.map(|o| o.investor_cap_raw.collect_range_at(start, end))
.collect();
for i in start..end {
let height = Height::from(i);
let local_i = i - start;
let mut sum_cap = CentsSats::ZERO;
let mut sum_investor_cap = CentsSquaredSats::ZERO;
for o in others.iter() {
sum_cap += o.cap_raw.collect_one_at(i).unwrap();
sum_investor_cap += o.investor_cap_raw.collect_one_at(i).unwrap();
for idx in 0..others.len() {
sum_cap += cap_ranges[idx][local_i];
sum_investor_cap += investor_cap_ranges[idx][local_i];
}
self.cap_raw.truncate_push(height, sum_cap)?;
@@ -842,14 +853,14 @@ impl RealizedBase {
self.realized_price_extra.compute_ratio(
starting_indexes,
&prices.usd.price,
&prices.price.usd,
&self.realized_price.usd.height,
exit,
)?;
self.investor_price_extra.compute_ratio(
starting_indexes,
&prices.usd.price,
&prices.price.usd,
&self.investor_price.usd.height,
exit,
)?;
@@ -331,31 +331,38 @@ impl UnrealizedBase {
.min()
.unwrap_or(0);
// Pre-collect all cohort data to avoid per-element BytesVec reads in nested loop
let invested_profit_ranges: Vec<Vec<CentsSats>> = others
.iter()
.map(|o| o.invested_capital_in_profit_raw.collect_range_at(start, end))
.collect();
let invested_loss_ranges: Vec<Vec<CentsSats>> = others
.iter()
.map(|o| o.invested_capital_in_loss_raw.collect_range_at(start, end))
.collect();
let investor_profit_ranges: Vec<Vec<CentsSquaredSats>> = others
.iter()
.map(|o| o.investor_cap_in_profit_raw.collect_range_at(start, end))
.collect();
let investor_loss_ranges: Vec<Vec<CentsSquaredSats>> = others
.iter()
.map(|o| o.investor_cap_in_loss_raw.collect_range_at(start, end))
.collect();
for i in start..end {
let height = Height::from(i);
let local_i = i - start;
let mut sum_invested_profit = CentsSats::ZERO;
let mut sum_invested_loss = CentsSats::ZERO;
let mut sum_investor_profit = CentsSquaredSats::ZERO;
let mut sum_investor_loss = CentsSquaredSats::ZERO;
for o in others.iter() {
sum_invested_profit += o
.invested_capital_in_profit_raw
.collect_one_at(i)
.unwrap();
sum_invested_loss += o
.invested_capital_in_loss_raw
.collect_one_at(i)
.unwrap();
sum_investor_profit += o
.investor_cap_in_profit_raw
.collect_one_at(i)
.unwrap();
sum_investor_loss += o
.investor_cap_in_loss_raw
.collect_one_at(i)
.unwrap();
for idx in 0..others.len() {
sum_invested_profit += invested_profit_ranges[idx][local_i];
sum_invested_loss += invested_loss_ranges[idx][local_i];
sum_investor_profit += investor_profit_ranges[idx][local_i];
sum_investor_loss += investor_loss_ranges[idx][local_i];
}
self.invested_capital_in_profit_raw
@@ -383,7 +390,7 @@ impl UnrealizedBase {
starting_indexes.height,
&self.investor_cap_in_loss_raw,
&self.invested_capital_in_loss_raw,
&prices.cents.price,
&prices.price.cents,
|(h, investor_cap, invested_cap, spot, ..)| {
if invested_cap.inner() == 0 {
return (h, Dollars::ZERO);
@@ -403,7 +410,7 @@ impl UnrealizedBase {
starting_indexes.height,
&self.investor_cap_in_profit_raw,
&self.invested_capital_in_profit_raw,
&prices.cents.price,
&prices.price.cents,
|(h, investor_cap, invested_cap, spot, ..)| {
if invested_cap.inner() == 0 {
return (h, Dollars::ZERO);
+1 -1
View File
@@ -249,7 +249,7 @@ impl Vecs {
// Recover chain_state from stored values
debug!("recovering chain_state from stored values");
let height_to_timestamp = &blocks.time.timestamp_monotonic;
let height_to_price = &prices.cents.price;
let height_to_price = &prices.price.cents;
let end = usize::from(recovered_height);
let timestamp_data: Vec<_> = height_to_timestamp.collect_range_at(0, end);
@@ -40,7 +40,7 @@ impl ComputedFromHeightRatioExtended {
exit: &Exit,
metric_price: &impl ReadableVec<Height, Dollars>,
) -> Result<()> {
let close_price = &prices.usd.price;
let close_price = &prices.price.usd;
self.base
.compute_ratio(starting_indexes, close_price, metric_price, exit)?;
self.extended
@@ -64,7 +64,7 @@ impl ValueFromHeightFull {
Ok(vec.compute_binary::<Sats, Dollars, SatsToDollars>(
max_from,
&self.sats.height,
&prices.usd.price,
&prices.price.usd,
exit,
)?)
})
@@ -59,7 +59,7 @@ impl ValueFromHeightLast {
self.usd.compute_binary::<Sats, Dollars, SatsToDollars>(
max_from,
&self.sats.height,
&prices.usd.price,
&prices.price.usd,
exit,
)?;
Ok(())
@@ -63,7 +63,7 @@ impl LazyComputedValueFromHeightCumulative {
self.usd.compute_binary::<Sats, Dollars, SatsToDollars>(
max_from,
&self.sats.height,
&prices.usd.price,
&prices.price.usd,
exit,
)?;
Ok(())
@@ -63,7 +63,7 @@ impl ValueFromHeightSumCumulative {
Ok(vec.compute_binary::<Sats, Dollars, SatsToDollars>(
max_from,
&self.sats.height,
&prices.usd.price,
&prices.price.usd,
exit,
)?)
})
@@ -51,7 +51,7 @@ impl ValueFromHeight {
self.usd.compute_binary::<Sats, Dollars, SatsToDollars>(
max_from,
&self.sats,
&prices.usd.price,
&prices.price.usd,
exit,
)?;
Ok(())
@@ -14,7 +14,7 @@ impl Vecs {
) -> Result<()> {
self.price_ath.usd.height.compute_all_time_high(
starting_indexes.height,
&prices.usd.price,
&prices.price.usd,
exit,
)?;
@@ -22,7 +22,7 @@ impl Vecs {
self.days_since_price_ath.height.compute_transform2(
starting_indexes.height,
&self.price_ath.usd.height,
&prices.usd.price,
&prices.price.usd,
|(i, ath, price, slf)| {
if prev.is_none() {
let i = i.to_usize();
+33 -16
View File
@@ -23,7 +23,7 @@ impl Vecs {
exit: &Exit,
) -> Result<()> {
let h2d = &indexes.height.day1;
let close = &prices.usd.split.close.day1;
let close = &prices.split.close.usd.day1;
let first_price_di = Day1::try_from(Date::new(2010, 7, 12))
.unwrap()
@@ -97,7 +97,7 @@ impl Vecs {
{
returns.compute_binary::<Dollars, Dollars, PercentageDiffDollars>(
starting_indexes.height,
&prices.usd.price,
&prices.price.usd,
&average_price.usd.height,
exit,
)?;
@@ -165,7 +165,7 @@ impl Vecs {
{
returns.compute_binary::<Dollars, Dollars, PercentageDiffDollars>(
starting_indexes.height,
&prices.usd.price,
&prices.price.usd,
&lookback_price.usd.height,
exit,
)?;
@@ -188,11 +188,16 @@ impl Vecs {
let start_days = super::ByDcaClass::<()>::start_days();
for (stack, day1) in self.class_stack.iter_mut().zip(start_days) {
let mut last_di: Option<Day1> = None;
let mut prev_value = if sh > 0 {
stack.sats.height.collect_one_at(sh - 1).unwrap_or_default()
} else {
Sats::ZERO
};
stack.sats.height.compute_transform(
starting_indexes.height,
h2d,
|(h, di, this)| {
|(h, di, _)| {
let hi = h.to_usize();
if last_di.is_none() && hi > 0 {
@@ -201,6 +206,7 @@ impl Vecs {
if di < day1 {
last_di = Some(di);
prev_value = Sats::ZERO;
return (h, Sats::ZERO);
}
@@ -208,17 +214,19 @@ impl Vecs {
last_di = Some(di);
let same_day = prev_di.is_some_and(|prev| prev == di);
if same_day {
(h, this.collect_one_at(hi - 1).unwrap_or_default())
let result = if same_day {
prev_value
} else {
let prev = if hi > 0 && prev_di.is_some_and(|pd| pd >= day1) {
this.collect_one_at(hi - 1).unwrap_or_default()
prev_value
} else {
Sats::ZERO
};
let s = close.collect_one_flat(di).map(sats_from_dca).unwrap_or(Sats::ZERO);
(h, prev + s)
}
prev + s
};
prev_value = result;
(h, result)
},
exit,
)?;
@@ -260,7 +268,7 @@ impl Vecs {
returns.compute_binary::<Dollars, Dollars, PercentageDiffDollars>(
starting_indexes.height,
&prices.usd.price,
&prices.price.usd,
&average_price.usd.height,
exit,
)?;
@@ -457,11 +465,17 @@ fn compute_cumulative<T: PcoVecValue + Default>(
mut accumulate: impl FnMut(T, StoredF32) -> T,
) -> Result<()> {
let mut last_di: Option<Day1> = None;
let sh = starting_height.to_usize();
let mut prev_value = if sh > 0 {
output.collect_one_at(sh - 1).unwrap_or_default()
} else {
T::default()
};
output.compute_transform(
starting_height,
h2d,
|(h, di, this)| {
|(h, di, _)| {
let hi = h.to_usize();
if last_di.is_none() && hi > 0 {
@@ -470,6 +484,7 @@ fn compute_cumulative<T: PcoVecValue + Default>(
if di < from_day1 {
last_di = Some(di);
prev_value = T::default();
return (h, T::default());
}
@@ -477,17 +492,19 @@ fn compute_cumulative<T: PcoVecValue + Default>(
last_di = Some(di);
let same_day = prev_di.is_some_and(|prev| prev == di);
if same_day {
(h, this.collect_one_at(hi - 1).unwrap_or_default())
let result = if same_day {
prev_value
} else {
let prev = if hi > 0 && prev_di.is_some_and(|pd| pd >= from_day1) {
this.collect_one_at(hi - 1).unwrap_or_default()
prev_value
} else {
initial
};
let ret = returns.collect_one_flat(di).unwrap_or_default();
(h, accumulate(prev, ret))
}
accumulate(prev, ret)
};
prev_value = result;
(h, result)
},
exit,
)?;
@@ -34,7 +34,7 @@ impl Vecs {
// Stochastic Oscillator: K = (close - low_2w) / (high_2w - low_2w) * 100
{
let price = &prices.usd.price;
let price = &prices.price.usd;
self.stoch_k.height.compute_transform3(
starting_indexes.height,
price,
@@ -15,7 +15,7 @@ pub(super) fn compute(
starting_indexes: &ComputeIndexes,
exit: &Exit,
) -> Result<()> {
let source_version = prices.usd.price.version();
let source_version = prices.price.usd.version();
chain
.line
@@ -16,10 +16,10 @@ pub(super) fn collect_returns(tf: &str, returns: &ReturnsVecs) -> Vec<f32> {
pub(super) fn collect_closes(tf: &str, prices: &prices::Vecs) -> Vec<Dollars> {
match tf {
"1d" => prices.usd.split.close.day1.collect_or_default(),
"1w" => prices.usd.split.close.week1.collect_or_default(),
"1m" => prices.usd.split.close.month1.collect_or_default(),
"1y" => prices.usd.split.close.year1.collect_or_default(),
"1d" => prices.split.close.usd.day1.collect_or_default(),
"1w" => prices.split.close.usd.week1.collect_or_default(),
"1m" => prices.split.close.usd.month1.collect_or_default(),
"1y" => prices.split.close.usd.year1.collect_or_default(),
_ => unreachable!(),
}
}
@@ -13,7 +13,7 @@ impl Vecs {
starting_indexes: &ComputeIndexes,
exit: &Exit,
) -> Result<()> {
let close_data: Vec<Dollars> = prices.usd.price.collect();
let close_data: Vec<Dollars> = prices.price.usd.collect();
for (price_ago, days) in self.price_ago.iter_mut_with_days() {
let window_starts = blocks.count.start_vec(days as usize);
@@ -14,7 +14,7 @@ impl Vecs {
starting_indexes: &ComputeIndexes,
exit: &Exit,
) -> Result<()> {
let close = &prices.usd.price;
let close = &prices.price.usd;
for (sma, period) in [
(&mut self.price_1w_sma, 7),
@@ -42,7 +42,7 @@ impl Vecs {
}
let h2d = &indexes.height.day1;
let closes: Vec<Dollars> = prices.usd.split.close.day1.collect_or_default();
let closes: Vec<Dollars> = prices.split.close.usd.day1.collect_or_default();
for (ema, period) in [
(&mut self.price_1w_ema, 7),
@@ -16,7 +16,7 @@ impl Vecs {
starting_indexes: &ComputeIndexes,
exit: &Exit,
) -> Result<()> {
let price = &prices.usd.price;
let price = &prices.price.usd;
self.price_1w_min.usd.height.compute_rolling_min_from_starts(
starting_indexes.height,
@@ -23,7 +23,7 @@ impl Vecs {
{
returns.compute_binary::<Dollars, Dollars, PercentageDiffDollars>(
starting_indexes.height,
&prices.usd.price,
&prices.price.usd,
&lookback_price.usd.height,
exit,
)?;
+49
View File
@@ -0,0 +1,49 @@
use brk_traversable::Traversable;
use brk_types::{Cents, Dollars, Height, OHLCCents, OHLCDollars, OHLCSats, Sats};
use vecdb::{LazyVecFrom1, PcoVec, Rw, StorageMode};
use crate::internal::{ComputedHeightDerivedLast, EagerIndexes, LazyEagerIndexes};
use super::ohlcs::{LazyOhlcVecs, OhlcVecs};
// ── SplitByUnit ─────────────────────────────────────────────────────
#[derive(Traversable)]
pub struct SplitByUnit<M: StorageMode = Rw> {
pub open: SplitIndexesByUnit<M>,
pub high: SplitIndexesByUnit<M>,
pub low: SplitIndexesByUnit<M>,
pub close: SplitCloseByUnit,
}
#[derive(Traversable)]
pub struct SplitIndexesByUnit<M: StorageMode = Rw> {
pub cents: EagerIndexes<Cents, M>,
pub usd: LazyEagerIndexes<Dollars, Cents>,
pub sats: LazyEagerIndexes<Sats, Cents>,
}
#[derive(Clone, Traversable)]
pub struct SplitCloseByUnit {
pub cents: ComputedHeightDerivedLast<Cents>,
pub usd: ComputedHeightDerivedLast<Dollars>,
pub sats: ComputedHeightDerivedLast<Sats>,
}
// ── OhlcByUnit ──────────────────────────────────────────────────────
#[derive(Traversable)]
pub struct OhlcByUnit<M: StorageMode = Rw> {
pub cents: OhlcVecs<OHLCCents, M>,
pub usd: LazyOhlcVecs<OHLCDollars, OHLCCents>,
pub sats: LazyOhlcVecs<OHLCSats, OHLCCents>,
}
// ── PriceByUnit ─────────────────────────────────────────────────────
#[derive(Traversable)]
pub struct PriceByUnit<M: StorageMode = Rw> {
pub cents: M::Stored<PcoVec<Height, Cents>>,
pub usd: LazyVecFrom1<Height, Dollars, Height, Cents>,
pub sats: LazyVecFrom1<Height, Sats, Height, Cents>,
}
@@ -1,207 +0,0 @@
use std::ops::Range;
use brk_error::Result;
use brk_indexer::Indexer;
use brk_oracle::{Config, NUM_BINS, Oracle, START_HEIGHT, bin_to_cents, cents_to_bin};
use brk_types::{Cents, OutputType, Sats, TxIndex, TxOutIndex};
use tracing::info;
use vecdb::{AnyStoredVec, AnyVec, Exit, ReadableVec, StorageMode, VecIndex, WritableVec};
use super::Vecs;
use crate::{ComputeIndexes, indexes};
impl Vecs {
pub(crate) fn compute(
&mut self,
indexer: &Indexer,
indexes: &indexes::Vecs,
starting_indexes: &ComputeIndexes,
exit: &Exit,
) -> Result<()> {
self.compute_prices(indexer, starting_indexes, exit)?;
self.split
.open
.compute_first(starting_indexes, &self.price, indexes, exit)?;
self.split
.high
.compute_max(starting_indexes, &self.price, indexes, exit)?;
self.split
.low
.compute_min(starting_indexes, &self.price, indexes, exit)?;
self.ohlc.compute_from_split(
starting_indexes,
&self.split.open,
&self.split.high,
&self.split.low,
&self.split.close,
indexes,
exit,
)?;
Ok(())
}
fn compute_prices(
&mut self,
indexer: &Indexer,
starting_indexes: &ComputeIndexes,
exit: &Exit,
) -> Result<()> {
let source_version =
indexer.vecs.outputs.value.version() + indexer.vecs.outputs.outputtype.version();
self.price
.validate_computed_version_or_reset(source_version)?;
let total_heights = indexer.vecs.blocks.timestamp.len();
if total_heights <= START_HEIGHT {
return Ok(());
}
// Reorg: truncate to starting_indexes
let truncate_to = self.price.len().min(starting_indexes.height.to_usize());
self.price.truncate_if_needed_at(truncate_to)?;
if self.price.len() < START_HEIGHT {
for line in brk_oracle::PRICES.lines().skip(self.price.len()) {
if self.price.len() >= START_HEIGHT {
break;
}
let dollars: f64 = line.parse().unwrap_or(0.0);
let cents = (dollars * 100.0).round() as u64;
self.price.push(Cents::new(cents));
}
}
if self.price.len() >= total_heights {
return Ok(());
}
let config = Config::default();
let committed = self.price.len();
let prev_cents = self.price.collect_one_at(committed - 1).unwrap();
let seed_bin = cents_to_bin(prev_cents.inner() as f64);
let warmup = config.window_size.min(committed - START_HEIGHT);
let mut oracle = Oracle::from_checkpoint(seed_bin, config, |o| {
Self::feed_blocks(o, indexer, (committed - warmup)..committed);
});
let num_new = total_heights - committed;
info!(
"Computing oracle prices: {} to {} ({warmup} warmup)",
committed, total_heights
);
let ref_bins = Self::feed_blocks(&mut oracle, indexer, committed..total_heights);
for (i, ref_bin) in ref_bins.into_iter().enumerate() {
self.price.push(Cents::new(bin_to_cents(ref_bin)));
let progress = ((i + 1) * 100 / num_new) as u8;
if i > 0 && progress > ((i * 100 / num_new) as u8) {
info!("Oracle price computation: {}%", progress);
}
}
{
let _lock = exit.lock();
self.price.write()?;
}
info!("Oracle prices complete: {} committed", self.price.len());
Ok(())
}
/// Feed a range of blocks from the indexer into an Oracle (skipping coinbase),
/// returning per-block ref_bin values.
fn feed_blocks<M: StorageMode>(
oracle: &mut Oracle,
indexer: &Indexer<M>,
range: Range<usize>,
) -> Vec<f64> {
let total_txs = indexer.vecs.transactions.height.len();
let total_outputs = indexer.vecs.outputs.value.len();
// Pre-collect height-indexed data for the range (plus one extra for next-block lookups)
let collect_end = (range.end + 1).min(indexer.vecs.transactions.first_txindex.len());
let first_txindexes: Vec<TxIndex> = indexer
.vecs
.transactions
.first_txindex
.collect_range_at(range.start, collect_end);
let out_firsts: Vec<TxOutIndex> = indexer
.vecs
.outputs
.first_txoutindex
.collect_range_at(range.start, collect_end);
let mut ref_bins = Vec::with_capacity(range.len());
// Cursor avoids per-block PcoVec page decompression for
// the tx-indexed first_txoutindex lookup. The accessed
// txindex values (first_txindex + 1) are strictly increasing
// across blocks, so the cursor only advances forward.
let mut txout_cursor = indexer.vecs.transactions.first_txoutindex.cursor();
// Reusable buffers — avoid per-block allocation
let mut values: Vec<Sats> = Vec::new();
let mut output_types: Vec<OutputType> = Vec::new();
for (idx, _h) in range.enumerate() {
let first_txindex = first_txindexes[idx];
let next_first_txindex = first_txindexes
.get(idx + 1)
.copied()
.unwrap_or(TxIndex::from(total_txs));
let out_start = if first_txindex.to_usize() + 1 < next_first_txindex.to_usize() {
let target = first_txindex.to_usize() + 1;
txout_cursor.advance(target - txout_cursor.position());
txout_cursor.next().unwrap().to_usize()
} else {
out_firsts
.get(idx + 1)
.copied()
.unwrap_or(TxOutIndex::from(total_outputs))
.to_usize()
};
let out_end = out_firsts
.get(idx + 1)
.copied()
.unwrap_or(TxOutIndex::from(total_outputs))
.to_usize();
indexer.vecs.outputs.value.collect_range_into_at(out_start, out_end, &mut values);
indexer.vecs.outputs.outputtype.collect_range_into_at(out_start, out_end, &mut output_types);
let mut hist = [0u32; NUM_BINS];
for i in 0..values.len() {
if let Some(bin) = oracle.output_to_bin(values[i], output_types[i]) {
hist[bin] += 1;
}
}
ref_bins.push(oracle.process_histogram(&hist));
}
ref_bins
}
}
impl<M: StorageMode> Vecs<M> {
/// Returns an Oracle seeded from the last committed price, with the last
/// window_size blocks already processed. Ready for additional blocks (e.g. mempool).
pub fn live_oracle<IM: StorageMode>(&self, indexer: &Indexer<IM>) -> Result<Oracle> {
let config = Config::default();
let height = indexer.vecs.blocks.timestamp.len();
let last_cents = self.price.collect_one_at(self.price.len() - 1).unwrap();
let seed_bin = cents_to_bin(last_cents.inner() as f64);
let window_size = config.window_size;
let oracle = Oracle::from_checkpoint(seed_bin, config, |o| {
Vecs::feed_blocks(o, indexer, height.saturating_sub(window_size)..height);
});
Ok(oracle)
}
}
@@ -1,42 +0,0 @@
use brk_error::Result;
use brk_types::Version;
use vecdb::{Database, ImportableVec, PcoVec, ReadableCloneableVec};
use super::Vecs;
use crate::indexes;
use crate::internal::{ComputedHeightDerivedLast, EagerIndexes};
use crate::prices::{ohlcs::OhlcVecs, split::SplitOhlc};
impl Vecs {
pub(crate) fn forced_import(
db: &Database,
parent_version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
let version = parent_version + Version::new(11);
let price = PcoVec::forced_import(db, "price_cents", version)?;
let open = EagerIndexes::forced_import(db, "price_open_cents", version)?;
let high = EagerIndexes::forced_import(db, "price_high_cents", version)?;
let low = EagerIndexes::forced_import(db, "price_low_cents", version)?;
let close = ComputedHeightDerivedLast::forced_import(
"price_close_cents",
price.read_only_boxed_clone(),
version,
indexes,
);
let split = SplitOhlc {
open,
high,
low,
close,
};
let ohlc = OhlcVecs::forced_import(db, "price_ohlc_cents", version)?;
Ok(Self { split, ohlc, price })
}
}
@@ -1,5 +0,0 @@
mod compute;
mod import;
mod vecs;
pub use vecs::Vecs;
@@ -1,19 +0,0 @@
use brk_traversable::Traversable;
use brk_types::{Cents, Height, OHLCCents};
use vecdb::{PcoVec, Rw, StorageMode};
use crate::internal::{ComputedHeightDerivedLast, EagerIndexes};
use crate::prices::{ohlcs::OhlcVecs, split::SplitOhlc};
#[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw> {
#[allow(clippy::type_complexity)]
pub split: SplitOhlc<
EagerIndexes<Cents, M>,
EagerIndexes<Cents, M>,
EagerIndexes<Cents, M>,
ComputedHeightDerivedLast<Cents>,
>,
pub ohlc: OhlcVecs<OHLCCents, M>,
pub price: M::Stored<PcoVec<Height, Cents>>,
}
+194 -3
View File
@@ -1,6 +1,11 @@
use std::ops::Range;
use brk_error::Result;
use brk_indexer::Indexer;
use vecdb::Exit;
use brk_oracle::{Config, NUM_BINS, Oracle, START_HEIGHT, bin_to_cents, cents_to_bin};
use brk_types::{Cents, OutputType, Sats, TxIndex, TxOutIndex};
use tracing::info;
use vecdb::{AnyStoredVec, AnyVec, Exit, ReadableVec, StorageMode, VecIndex, WritableVec};
use super::Vecs;
use crate::{ComputeIndexes, indexes};
@@ -13,11 +18,197 @@ impl Vecs {
starting_indexes: &ComputeIndexes,
exit: &Exit,
) -> Result<()> {
self.cents
.compute(indexer, indexes, starting_indexes, exit)?;
self.compute_prices(indexer, starting_indexes, exit)?;
self.split
.open
.cents
.compute_first(starting_indexes, &self.price.cents, indexes, exit)?;
self.split
.high
.cents
.compute_max(starting_indexes, &self.price.cents, indexes, exit)?;
self.split
.low
.cents
.compute_min(starting_indexes, &self.price.cents, indexes, exit)?;
self.ohlc.cents.compute_from_split(
starting_indexes,
&self.split.open.cents,
&self.split.high.cents,
&self.split.low.cents,
&self.split.close.cents,
indexes,
exit,
)?;
let _lock = exit.lock();
self.db().compact()?;
Ok(())
}
fn compute_prices(
&mut self,
indexer: &Indexer,
starting_indexes: &ComputeIndexes,
exit: &Exit,
) -> Result<()> {
let source_version =
indexer.vecs.outputs.value.version() + indexer.vecs.outputs.outputtype.version();
self.price
.cents
.validate_computed_version_or_reset(source_version)?;
let total_heights = indexer.vecs.blocks.timestamp.len();
if total_heights <= START_HEIGHT {
return Ok(());
}
// Reorg: truncate to starting_indexes
let truncate_to = self.price.cents.len().min(starting_indexes.height.to_usize());
self.price.cents.truncate_if_needed_at(truncate_to)?;
if self.price.cents.len() < START_HEIGHT {
for line in brk_oracle::PRICES.lines().skip(self.price.cents.len()) {
if self.price.cents.len() >= START_HEIGHT {
break;
}
let dollars: f64 = line.parse().unwrap_or(0.0);
let cents = (dollars * 100.0).round() as u64;
self.price.cents.push(Cents::new(cents));
}
}
if self.price.cents.len() >= total_heights {
return Ok(());
}
let config = Config::default();
let committed = self.price.cents.len();
let prev_cents = self.price.cents.collect_one_at(committed - 1).unwrap();
let seed_bin = cents_to_bin(prev_cents.inner() as f64);
let warmup = config.window_size.min(committed - START_HEIGHT);
let mut oracle = Oracle::from_checkpoint(seed_bin, config, |o| {
Self::feed_blocks(o, indexer, (committed - warmup)..committed);
});
let num_new = total_heights - committed;
info!(
"Computing oracle prices: {} to {} ({warmup} warmup)",
committed, total_heights
);
let ref_bins = Self::feed_blocks(&mut oracle, indexer, committed..total_heights);
for (i, ref_bin) in ref_bins.into_iter().enumerate() {
self.price.cents.push(Cents::new(bin_to_cents(ref_bin)));
let progress = ((i + 1) * 100 / num_new) as u8;
if i > 0 && progress > ((i * 100 / num_new) as u8) {
info!("Oracle price computation: {}%", progress);
}
}
{
let _lock = exit.lock();
self.price.cents.write()?;
}
info!("Oracle prices complete: {} committed", self.price.cents.len());
Ok(())
}
/// Feed a range of blocks from the indexer into an Oracle (skipping coinbase),
/// returning per-block ref_bin values.
fn feed_blocks<M: StorageMode>(
oracle: &mut Oracle,
indexer: &Indexer<M>,
range: Range<usize>,
) -> Vec<f64> {
let total_txs = indexer.vecs.transactions.height.len();
let total_outputs = indexer.vecs.outputs.value.len();
// Pre-collect height-indexed data for the range (plus one extra for next-block lookups)
let collect_end = (range.end + 1).min(indexer.vecs.transactions.first_txindex.len());
let first_txindexes: Vec<TxIndex> = indexer
.vecs
.transactions
.first_txindex
.collect_range_at(range.start, collect_end);
let out_firsts: Vec<TxOutIndex> = indexer
.vecs
.outputs
.first_txoutindex
.collect_range_at(range.start, collect_end);
let mut ref_bins = Vec::with_capacity(range.len());
// Cursor avoids per-block PcoVec page decompression for
// the tx-indexed first_txoutindex lookup. The accessed
// txindex values (first_txindex + 1) are strictly increasing
// across blocks, so the cursor only advances forward.
let mut txout_cursor = indexer.vecs.transactions.first_txoutindex.cursor();
// Reusable buffers — avoid per-block allocation
let mut values: Vec<Sats> = Vec::new();
let mut output_types: Vec<OutputType> = Vec::new();
for (idx, _h) in range.enumerate() {
let first_txindex = first_txindexes[idx];
let next_first_txindex = first_txindexes
.get(idx + 1)
.copied()
.unwrap_or(TxIndex::from(total_txs));
let out_start = if first_txindex.to_usize() + 1 < next_first_txindex.to_usize() {
let target = first_txindex.to_usize() + 1;
txout_cursor.advance(target - txout_cursor.position());
txout_cursor.next().unwrap().to_usize()
} else {
out_firsts
.get(idx + 1)
.copied()
.unwrap_or(TxOutIndex::from(total_outputs))
.to_usize()
};
let out_end = out_firsts
.get(idx + 1)
.copied()
.unwrap_or(TxOutIndex::from(total_outputs))
.to_usize();
indexer.vecs.outputs.value.collect_range_into_at(out_start, out_end, &mut values);
indexer.vecs.outputs.outputtype.collect_range_into_at(out_start, out_end, &mut output_types);
let mut hist = [0u32; NUM_BINS];
for i in 0..values.len() {
if let Some(bin) = oracle.output_to_bin(values[i], output_types[i]) {
hist[bin] += 1;
}
}
ref_bins.push(oracle.process_histogram(&hist));
}
ref_bins
}
}
impl<M: StorageMode> Vecs<M> {
/// Returns an Oracle seeded from the last committed price, with the last
/// window_size blocks already processed. Ready for additional blocks (e.g. mempool).
pub fn live_oracle<IM: StorageMode>(&self, indexer: &Indexer<IM>) -> Result<Oracle> {
let config = Config::default();
let height = indexer.vecs.blocks.timestamp.len();
let last_cents = self.price.cents.collect_one_at(self.price.cents.len() - 1).unwrap();
let seed_bin = cents_to_bin(last_cents.inner() as f64);
let window_size = config.window_size;
let oracle = Oracle::from_checkpoint(seed_bin, config, |o| {
Vecs::feed_blocks(o, indexer, height.saturating_sub(window_size)..height);
});
Ok(oracle)
}
}
+154 -20
View File
@@ -1,22 +1,28 @@
pub(crate) mod by_unit;
mod compute;
pub(crate) mod ohlcs;
pub(crate) mod split;
pub mod cents;
pub mod sats;
pub mod usd;
pub use cents::Vecs as CentsVecs;
pub use sats::Vecs as SatsVecs;
pub use usd::Vecs as UsdVecs;
use std::path::Path;
use brk_traversable::Traversable;
use brk_types::Version;
use vecdb::{Database, Rw, StorageMode, PAGE_SIZE};
use vecdb::{
Database, ImportableVec, LazyVecFrom1, PcoVec, ReadableCloneableVec, Rw, StorageMode,
PAGE_SIZE,
};
use crate::indexes;
use crate::{
indexes,
internal::{
CentsUnsignedToDollars, CentsUnsignedToSats, ComputedHeightDerivedLast, EagerIndexes,
LazyEagerIndexes, OhlcCentsToDollars, OhlcCentsToSats,
},
};
use by_unit::{
OhlcByUnit, PriceByUnit, SplitByUnit, SplitCloseByUnit, SplitIndexesByUnit,
};
use ohlcs::{LazyOhlcVecs, OhlcVecs};
pub const DB_NAME: &str = "prices";
@@ -25,9 +31,9 @@ pub struct Vecs<M: StorageMode = Rw> {
#[traversable(skip)]
pub(crate) db: Database,
pub cents: CentsVecs<M>,
pub usd: UsdVecs,
pub sats: SatsVecs,
pub split: SplitByUnit<M>,
pub ohlc: OhlcByUnit<M>,
pub price: PriceByUnit<M>,
}
impl Vecs {
@@ -56,15 +62,143 @@ impl Vecs {
version: Version,
indexes: &indexes::Vecs,
) -> brk_error::Result<Self> {
let cents = CentsVecs::forced_import(db, version, indexes)?;
let usd = UsdVecs::forced_import(version, indexes, &cents);
let sats = SatsVecs::forced_import(version, indexes, &cents);
let version = version + Version::new(11);
// ── Cents (eager, stored) ───────────────────────────────────
let price_cents = PcoVec::forced_import(db, "price_cents", version)?;
let open_cents = EagerIndexes::forced_import(db, "price_open_cents", version)?;
let high_cents = EagerIndexes::forced_import(db, "price_high_cents", version)?;
let low_cents = EagerIndexes::forced_import(db, "price_low_cents", version)?;
let close_cents = ComputedHeightDerivedLast::forced_import(
"price_close_cents",
price_cents.read_only_boxed_clone(),
version,
indexes,
);
let ohlc_cents = OhlcVecs::forced_import(db, "price_ohlc_cents", version)?;
// ── USD (lazy from cents) ───────────────────────────────────
let price_usd = LazyVecFrom1::transformed::<CentsUnsignedToDollars>(
"price",
version,
price_cents.read_only_boxed_clone(),
);
let open_usd = LazyEagerIndexes::from_eager_indexes::<CentsUnsignedToDollars>(
"price_open",
version,
&open_cents,
);
let high_usd = LazyEagerIndexes::from_eager_indexes::<CentsUnsignedToDollars>(
"price_high",
version,
&high_cents,
);
let low_usd = LazyEagerIndexes::from_eager_indexes::<CentsUnsignedToDollars>(
"price_low",
version,
&low_cents,
);
let close_usd = ComputedHeightDerivedLast::forced_import(
"price_close",
price_usd.read_only_boxed_clone(),
version,
indexes,
);
let ohlc_usd = LazyOhlcVecs::from_eager_ohlc_indexes::<OhlcCentsToDollars>(
"price_ohlc",
version,
&ohlc_cents,
);
// ── Sats (lazy from cents, high↔low swapped) ───────────────
let price_sats = LazyVecFrom1::transformed::<CentsUnsignedToSats>(
"price_sats",
version,
price_cents.read_only_boxed_clone(),
);
let open_sats = LazyEagerIndexes::from_eager_indexes::<CentsUnsignedToSats>(
"price_open_sats",
version,
&open_cents,
);
// Sats are inversely related to cents (sats = 10B/cents), so high↔low are swapped
let high_sats = LazyEagerIndexes::from_eager_indexes::<CentsUnsignedToSats>(
"price_high_sats",
version,
&low_cents,
);
let low_sats = LazyEagerIndexes::from_eager_indexes::<CentsUnsignedToSats>(
"price_low_sats",
version,
&high_cents,
);
let close_sats = ComputedHeightDerivedLast::forced_import(
"price_close_sats",
price_sats.read_only_boxed_clone(),
version,
indexes,
);
// OhlcCentsToSats handles the high↔low swap internally
let ohlc_sats = LazyOhlcVecs::from_eager_ohlc_indexes::<OhlcCentsToSats>(
"price_ohlc_sats",
version,
&ohlc_cents,
);
// ── Assemble pivoted structure ──────────────────────────────
let split = SplitByUnit {
open: SplitIndexesByUnit {
cents: open_cents,
usd: open_usd,
sats: open_sats,
},
high: SplitIndexesByUnit {
cents: high_cents,
usd: high_usd,
sats: high_sats,
},
low: SplitIndexesByUnit {
cents: low_cents,
usd: low_usd,
sats: low_sats,
},
close: SplitCloseByUnit {
cents: close_cents,
usd: close_usd,
sats: close_sats,
},
};
let ohlc = OhlcByUnit {
cents: ohlc_cents,
usd: ohlc_usd,
sats: ohlc_sats,
};
let price = PriceByUnit {
cents: price_cents,
usd: price_usd,
sats: price_sats,
};
Ok(Self {
db: db.clone(),
cents,
usd,
sats,
split,
ohlc,
price,
})
}
@@ -1,64 +0,0 @@
use brk_types::Version;
use vecdb::{LazyVecFrom1, ReadableCloneableVec};
use super::super::cents;
use super::Vecs;
use crate::prices::{ohlcs::LazyOhlcVecs, split::SplitOhlc};
use crate::{
indexes,
internal::{CentsUnsignedToSats, ComputedHeightDerivedLast, LazyEagerIndexes, OhlcCentsToSats},
};
impl Vecs {
pub(crate) fn forced_import(
version: Version,
indexes: &indexes::Vecs,
cents: &cents::Vecs,
) -> Self {
let price = LazyVecFrom1::transformed::<CentsUnsignedToSats>(
"price_sats",
version,
cents.price.read_only_boxed_clone(),
);
// Sats are inversely related to cents (sats = 10B/cents), so high↔low are swapped
let open = LazyEagerIndexes::from_eager_indexes::<CentsUnsignedToSats>(
"price_open_sats",
version,
&cents.split.open,
);
let high = LazyEagerIndexes::from_eager_indexes::<CentsUnsignedToSats>(
"price_high_sats",
version,
&cents.split.low,
);
let low = LazyEagerIndexes::from_eager_indexes::<CentsUnsignedToSats>(
"price_low_sats",
version,
&cents.split.high,
);
let close = ComputedHeightDerivedLast::forced_import(
"price_close_sats",
price.read_only_boxed_clone(),
version,
indexes,
);
let split = SplitOhlc {
open,
high,
low,
close,
};
// OhlcCentsToSats handles the high↔low swap internally
let ohlc = LazyOhlcVecs::from_eager_ohlc_indexes::<OhlcCentsToSats>(
"price_ohlc_sats",
version,
&cents.ohlc,
);
Self { split, ohlc, price }
}
}
@@ -1,4 +0,0 @@
mod import;
mod vecs;
pub use vecs::Vecs;
@@ -1,19 +0,0 @@
use brk_traversable::Traversable;
use brk_types::{Cents, Height, OHLCCents, OHLCSats, Sats};
use vecdb::LazyVecFrom1;
use crate::internal::{ComputedHeightDerivedLast, LazyEagerIndexes};
use crate::prices::{ohlcs::LazyOhlcVecs, split::SplitOhlc};
#[derive(Clone, Traversable)]
pub struct Vecs {
#[allow(clippy::type_complexity)]
pub split: SplitOhlc<
LazyEagerIndexes<Sats, Cents>,
LazyEagerIndexes<Sats, Cents>,
LazyEagerIndexes<Sats, Cents>,
ComputedHeightDerivedLast<Sats>,
>,
pub ohlc: LazyOhlcVecs<OHLCSats, OHLCCents>,
pub price: LazyVecFrom1<Height, Sats, Height, Cents>,
}
-9
View File
@@ -1,9 +0,0 @@
use brk_traversable::Traversable;
#[derive(Clone, Traversable)]
pub struct SplitOhlc<O, H, L, C> {
pub open: O,
pub high: H,
pub low: L,
pub close: C,
}
@@ -1,65 +0,0 @@
use brk_types::Version;
use vecdb::{LazyVecFrom1, ReadableCloneableVec};
use super::super::cents;
use super::Vecs;
use crate::prices::{ohlcs::LazyOhlcVecs, split::SplitOhlc};
use crate::{
indexes,
internal::{
CentsUnsignedToDollars, ComputedHeightDerivedLast, LazyEagerIndexes, OhlcCentsToDollars,
},
};
impl Vecs {
pub(crate) fn forced_import(
version: Version,
indexes: &indexes::Vecs,
cents: &cents::Vecs,
) -> Self {
let price = LazyVecFrom1::transformed::<CentsUnsignedToDollars>(
"price",
version,
cents.price.read_only_boxed_clone(),
);
// Dollars are monotonically increasing from cents, so open→open, high→high, low→low
let open = LazyEagerIndexes::from_eager_indexes::<CentsUnsignedToDollars>(
"price_open",
version,
&cents.split.open,
);
let high = LazyEagerIndexes::from_eager_indexes::<CentsUnsignedToDollars>(
"price_high",
version,
&cents.split.high,
);
let low = LazyEagerIndexes::from_eager_indexes::<CentsUnsignedToDollars>(
"price_low",
version,
&cents.split.low,
);
let close = ComputedHeightDerivedLast::forced_import(
"price_close",
price.read_only_boxed_clone(),
version,
indexes,
);
let split = SplitOhlc {
open,
high,
low,
close,
};
let ohlc = LazyOhlcVecs::from_eager_ohlc_indexes::<OhlcCentsToDollars>(
"price_ohlc",
version,
&cents.ohlc,
);
Self { split, ohlc, price }
}
}
@@ -1,4 +0,0 @@
mod import;
mod vecs;
pub use vecs::Vecs;
@@ -1,19 +0,0 @@
use brk_traversable::Traversable;
use brk_types::{Cents, Dollars, Height, OHLCCents, OHLCDollars};
use vecdb::LazyVecFrom1;
use crate::internal::{ComputedHeightDerivedLast, LazyEagerIndexes};
use crate::prices::{ohlcs::LazyOhlcVecs, split::SplitOhlc};
#[derive(Clone, Traversable)]
pub struct Vecs {
#[allow(clippy::type_complexity)]
pub split: SplitOhlc<
LazyEagerIndexes<Dollars, Cents>,
LazyEagerIndexes<Dollars, Cents>,
LazyEagerIndexes<Dollars, Cents>,
ComputedHeightDerivedLast<Dollars>,
>,
pub ohlc: LazyOhlcVecs<OHLCDollars, OHLCCents>,
pub price: LazyVecFrom1<Height, Dollars, Height, Cents>,
}
+1 -1
View File
@@ -84,9 +84,9 @@ impl Query {
let day1 = Day1::try_from(date).map_err(|e| Error::Parse(e.to_string()))?;
let price = &self.computer().prices;
let spot = price
.cents
.split
.close
.cents
.day1
.collect_one_flat(day1)
.ok_or_else(|| Error::NotFound(format!("No price data for {date}")))?;
+1 -1
View File
@@ -5,7 +5,7 @@ use crate::Query;
impl Query {
pub fn live_price(&self) -> Result<Dollars> {
let mut oracle = self.computer().prices.cents.live_oracle(self.indexer())?;
let mut oracle = self.computer().prices.live_oracle(self.indexer())?;
if let Some(mempool) = self.mempool() {
let txs = mempool.get_txs();
+9 -9
View File
@@ -6,40 +6,40 @@ console.log("Testing idiomatic API...\n");
// Test getter access (property)
console.log("1. Getter access (.by.dateindex):");
const all = await client.metrics.prices.usd.split.close.by.day1;
const all = await client.metrics.prices.split.close.usd.by.day1;
console.log(` Total: ${all.total}, Got: ${all.data.length} items\n`);
// Test dynamic access (bracket notation)
console.log("2. Dynamic access (.by['dateindex']):");
const allDynamic = await client.metrics.prices.usd.split.close.by.day1;
const allDynamic = await client.metrics.prices.split.close.usd.by.day1;
console.log(
` Total: ${allDynamic.total}, Got: ${allDynamic.data.length} items\n`,
);
// Test fetch all (explicit .fetch())
console.log("3. Explicit .fetch():");
const allExplicit = await client.metrics.prices.usd.split.close.by.day1.fetch();
const allExplicit = await client.metrics.prices.split.close.usd.by.day1.fetch();
console.log(
` Total: ${allExplicit.total}, Got: ${allExplicit.data.length} items\n`,
);
// Test first(n)
console.log("4. First 5 items (.first(5)):");
const first5 = await client.metrics.prices.usd.split.close.by.day1.first(5);
const first5 = await client.metrics.prices.split.close.usd.by.day1.first(5);
console.log(
` Total: ${first5.total}, Start: ${first5.start}, End: ${first5.end}, Got: ${first5.data.length} items\n`,
);
// Test last(n)
console.log("5. Last 5 items (.last(5)):");
const last5 = await client.metrics.prices.usd.split.close.by.day1.last(5);
const last5 = await client.metrics.prices.split.close.usd.by.day1.last(5);
console.log(
` Total: ${last5.total}, Start: ${last5.start}, End: ${last5.end}, Got: ${last5.data.length} items\n`,
);
// Test slice(start, end)
console.log("6. Slice 10-20 (.slice(10, 20)):");
const sliced = await client.metrics.prices.usd.split.close.by.day1.slice(
const sliced = await client.metrics.prices.split.close.usd.by.day1.slice(
10,
20,
);
@@ -49,14 +49,14 @@ console.log(
// Test get(index) - single item
console.log("7. Single item (.get(100)):");
const single = await client.metrics.prices.usd.split.close.by.day1.get(100);
const single = await client.metrics.prices.split.close.usd.by.day1.get(100);
console.log(
` Total: ${single.total}, Start: ${single.start}, End: ${single.end}, Got: ${single.data.length} item(s)\n`,
);
// Test skip(n).take(m) chaining
console.log("8. Skip and take (.skip(100).take(10)):");
const skipTake = await client.metrics.prices.usd.split.close.by.day1
const skipTake = await client.metrics.prices.split.close.usd.by.day1
.skip(100)
.take(10);
console.log(
@@ -65,7 +65,7 @@ console.log(
// Test fetchCsv
console.log("9. Fetch as CSV (.last(3).fetchCsv()):");
const csv = await client.metrics.prices.usd.split.close.by.day1
const csv = await client.metrics.prices.split.close.usd.by.day1
.last(3)
.fetchCsv();
console.log(` CSV preview: ${csv.substring(0, 100)}...\n`);
+4 -4
View File
@@ -11,7 +11,7 @@ console.log("Testing MetricData helpers...\n");
// Fetch a date-based metric
console.log("1. Fetching price data (day1):");
const price = await client.metrics.prices.usd.split.close.by.day1.first(5);
const price = await client.metrics.prices.split.close.usd.by.day1.first(5);
console.log(
` Total: ${price.total}, Start: ${price.start}, End: ${price.end}`,
);
@@ -95,7 +95,7 @@ if (count !== 5) throw new Error("Expected 5 iterations");
// Test with non-date-based index (height)
console.log("\n11. Testing height-based metric:");
const heightMetric = await client.metrics.prices.usd.price.by.height.last(3);
const heightMetric = await client.metrics.prices.price.usd.by.height.last(3);
console.log(
` Total: ${heightMetric.total}, Start: ${heightMetric.start}, End: ${heightMetric.end}`,
);
@@ -135,7 +135,7 @@ console.log(` Iterated ${heightCount} items`);
// Test different date indexes
console.log("\n13. Testing month1:");
const monthMetric =
await client.metrics.prices.usd.split.close.by.month1.first(3);
await client.metrics.prices.split.close.usd.by.month1.first(3);
const monthDates = monthMetric.dates();
console.log(` First month: ${monthDates[0].toISOString()}`);
// MonthIndex 0 = Jan 1, 2009
@@ -238,7 +238,7 @@ console.log(` Roundtrip day1 100: ${testDate.toISOString()} -> ${roundtrip}`);
// Test slice with Date
console.log("\n16. Testing slice with Date:");
const dateSlice = await client.metrics.prices.usd.split.close.by.day1
const dateSlice = await client.metrics.prices.split.close.usd.by.day1
.slice(new Date(Date.UTC(2020, 0, 1)), new Date(Date.UTC(2020, 0, 4)))
.fetch();
console.log(
+2 -2
View File
@@ -41,7 +41,7 @@ def test_fetch_typed_metric():
print(a)
b = client.metrics.outputs.count.utxo_count.by.height().tail(10).fetch()
print(b)
c = client.metrics.prices.usd.split.close.by.day1().tail(10).fetch()
c = client.metrics.prices.split.close.usd.by.day1().tail(10).fetch()
print(c)
d = (
client.metrics.market.dca.period_lump_sum_stack._10y.usd.by.day1()
@@ -61,5 +61,5 @@ def test_fetch_typed_metric():
.fetch()
)
print(f)
g = client.metrics.prices.usd.ohlc.by.day1().tail(10).fetch()
g = client.metrics.prices.ohlc.usd.by.day1().tail(10).fetch()
print(g)
+1 -1
View File
@@ -370,7 +370,7 @@ export function createMarketSection() {
title: "Sats per Dollar",
bottom: [
line({
metric: prices.sats.split.close,
metric: prices.split.close.sats,
name: "Sats/$",
unit: Unit.sats,
}),
+2 -2
View File
@@ -45,7 +45,7 @@ export function init() {
const usdPrice = {
type: "Candlestick",
title: "Price",
metric: brk.metrics.prices.usd.ohlc,
metric: brk.metrics.prices.ohlc.usd,
};
result.set(Unit.usd, [usdPrice, ...(optionTop.get(Unit.usd) ?? [])]);
@@ -54,7 +54,7 @@ export function init() {
const satsPrice = {
type: "Candlestick",
title: "Price",
metric: brk.metrics.prices.sats.ohlc,
metric: brk.metrics.prices.ohlc.sats,
colors: /** @type {const} */ ([colors.bi.p1[1], colors.bi.p1[0]]),
};
result.set(Unit.sats, [satsPrice, ...(optionTop.get(Unit.sats) ?? [])]);