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