mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-06-10 06:53:33 -07:00
computer: snapshot
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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(())
|
||||
|
||||
+1
-1
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
)?;
|
||||
|
||||
@@ -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>>,
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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, ¢s);
|
||||
let sats = SatsVecs::forced_import(version, indexes, ¢s);
|
||||
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: ¢s::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,
|
||||
¢s.split.open,
|
||||
);
|
||||
let high = LazyEagerIndexes::from_eager_indexes::<CentsUnsignedToSats>(
|
||||
"price_high_sats",
|
||||
version,
|
||||
¢s.split.low,
|
||||
);
|
||||
let low = LazyEagerIndexes::from_eager_indexes::<CentsUnsignedToSats>(
|
||||
"price_low_sats",
|
||||
version,
|
||||
¢s.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,
|
||||
¢s.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>,
|
||||
}
|
||||
@@ -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: ¢s::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,
|
||||
¢s.split.open,
|
||||
);
|
||||
let high = LazyEagerIndexes::from_eager_indexes::<CentsUnsignedToDollars>(
|
||||
"price_high",
|
||||
version,
|
||||
¢s.split.high,
|
||||
);
|
||||
let low = LazyEagerIndexes::from_eager_indexes::<CentsUnsignedToDollars>(
|
||||
"price_low",
|
||||
version,
|
||||
¢s.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,
|
||||
¢s.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>,
|
||||
}
|
||||
@@ -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}")))?;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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`);
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
}),
|
||||
|
||||
@@ -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) ?? [])]);
|
||||
|
||||
Reference in New Issue
Block a user