computer: snapshot

This commit is contained in:
nym21
2026-02-27 10:54:36 +01:00
parent 72c17096ea
commit c75421f46e
44 changed files with 1079 additions and 722 deletions
+1
View File
@@ -42,6 +42,7 @@ fn main() -> brk_client::Result<()> {
.count
.block_count
.sum
._24h
.by
.day1()
.last(3)
+70 -70
View File
@@ -2193,7 +2193,7 @@ impl GreedInvestedInvestorNegNetPainSupplyTotalUnrealizedPattern {
/// Pattern struct for repeated tree structure.
pub struct BlocksCoinbaseDaysDominanceFeeSubsidyPattern {
pub blocks_mined: CumulativeHeightRollingPattern<StoredU32>,
pub blocks_mined: CumulativeHeightSumPattern<StoredU32>,
pub blocks_mined_1m_sum: MetricPattern1<StoredU32>,
pub blocks_mined_1w_sum: MetricPattern1<StoredU32>,
pub blocks_mined_1y_sum: MetricPattern1<StoredU32>,
@@ -2214,7 +2214,7 @@ impl BlocksCoinbaseDaysDominanceFeeSubsidyPattern {
/// Create a new pattern node with accumulated metric name.
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
Self {
blocks_mined: CumulativeHeightRollingPattern::new(client.clone(), _m(&acc, "blocks_mined")),
blocks_mined: CumulativeHeightSumPattern::new(client.clone(), _m(&acc, "blocks_mined")),
blocks_mined_1m_sum: MetricPattern1::new(client.clone(), _m(&acc, "blocks_mined_1m_sum")),
blocks_mined_1w_sum: MetricPattern1::new(client.clone(), _m(&acc, "blocks_mined_1w_sum")),
blocks_mined_1y_sum: MetricPattern1::new(client.clone(), _m(&acc, "blocks_mined_1y_sum")),
@@ -2817,8 +2817,8 @@ impl BalanceBothReactivatedReceivingSendingPattern {
/// Pattern struct for repeated tree structure.
pub struct CoinblocksCoindaysSatblocksSatdaysSentPattern {
pub coinblocks_destroyed: CumulativeHeightRollingPattern<StoredF64>,
pub coindays_destroyed: CumulativeHeightRollingPattern<StoredF64>,
pub coinblocks_destroyed: CumulativeHeightSumPattern<StoredF64>,
pub coindays_destroyed: CumulativeHeightSumPattern<StoredF64>,
pub satblocks_destroyed: MetricPattern20<Sats>,
pub satdays_destroyed: MetricPattern20<Sats>,
pub sent: BtcSatsUsdPattern2,
@@ -2829,8 +2829,8 @@ impl CoinblocksCoindaysSatblocksSatdaysSentPattern {
/// Create a new pattern node with accumulated metric name.
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
Self {
coinblocks_destroyed: CumulativeHeightRollingPattern::new(client.clone(), _m(&acc, "coinblocks_destroyed")),
coindays_destroyed: CumulativeHeightRollingPattern::new(client.clone(), _m(&acc, "coindays_destroyed")),
coinblocks_destroyed: CumulativeHeightSumPattern::new(client.clone(), _m(&acc, "coinblocks_destroyed")),
coindays_destroyed: CumulativeHeightSumPattern::new(client.clone(), _m(&acc, "coindays_destroyed")),
satblocks_destroyed: MetricPattern20::new(client.clone(), _m(&acc, "satblocks_destroyed")),
satdays_destroyed: MetricPattern20::new(client.clone(), _m(&acc, "satdays_destroyed")),
sent: BtcSatsUsdPattern2::new(client.clone(), _m(&acc, "sent")),
@@ -3002,13 +3002,13 @@ impl BtcSatsUsdPattern2 {
}
/// Pattern struct for repeated tree structure.
pub struct BtcSatsUsdPattern4 {
pub struct BtcSatsUsdPattern3 {
pub btc: MetricPattern1<Bitcoin>,
pub sats: CumulativeHeightRollingPattern<Sats>,
pub usd: CumulativeHeightRollingPattern<Dollars>,
}
impl BtcSatsUsdPattern4 {
impl BtcSatsUsdPattern3 {
/// Create a new pattern node with accumulated metric name.
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
Self {
@@ -3020,19 +3020,19 @@ impl BtcSatsUsdPattern4 {
}
/// Pattern struct for repeated tree structure.
pub struct BtcSatsUsdPattern3 {
pub struct BtcSatsUsdPattern4 {
pub btc: MetricPattern1<Bitcoin>,
pub sats: CumulativeHeightRollingPattern2<Sats>,
pub usd: CumulativeHeightRollingPattern2<Dollars>,
pub sats: CumulativeHeightSumPattern<Sats>,
pub usd: CumulativeHeightSumPattern<Dollars>,
}
impl BtcSatsUsdPattern3 {
impl BtcSatsUsdPattern4 {
/// Create a new pattern node with accumulated metric name.
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
Self {
btc: MetricPattern1::new(client.clone(), _m(&acc, "btc")),
sats: CumulativeHeightRollingPattern2::new(client.clone(), acc.clone()),
usd: CumulativeHeightRollingPattern2::new(client.clone(), _m(&acc, "usd")),
sats: CumulativeHeightSumPattern::new(client.clone(), acc.clone()),
usd: CumulativeHeightSumPattern::new(client.clone(), _m(&acc, "usd")),
}
}
}
@@ -3074,13 +3074,13 @@ impl HistogramLineSignalPattern {
}
/// Pattern struct for repeated tree structure.
pub struct CumulativeHeightRollingPattern2<T> {
pub struct CumulativeHeightRollingPattern<T> {
pub cumulative: MetricPattern1<T>,
pub height: MetricPattern20<T>,
pub rolling: AverageMaxMedianMinP10P25P75P90SumPattern,
}
impl<T: DeserializeOwned> CumulativeHeightRollingPattern2<T> {
impl<T: DeserializeOwned> CumulativeHeightRollingPattern<T> {
/// Create a new pattern node with accumulated metric name.
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
Self {
@@ -3092,19 +3092,19 @@ impl<T: DeserializeOwned> CumulativeHeightRollingPattern2<T> {
}
/// Pattern struct for repeated tree structure.
pub struct CumulativeHeightRollingPattern<T> {
pub struct CumulativeHeightSumPattern<T> {
pub cumulative: MetricPattern1<T>,
pub height: MetricPattern20<T>,
pub rolling: _1y24h30d7dPattern<T>,
pub sum: _1y24h30d7dPattern<T>,
}
impl<T: DeserializeOwned> CumulativeHeightRollingPattern<T> {
impl<T: DeserializeOwned> CumulativeHeightSumPattern<T> {
/// Create a new pattern node with accumulated metric name.
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
Self {
cumulative: MetricPattern1::new(client.clone(), _m(&acc, "cumulative")),
height: MetricPattern20::new(client.clone(), acc.clone()),
rolling: _1y24h30d7dPattern::new(client.clone(), acc.clone()),
sum: _1y24h30d7dPattern::new(client.clone(), acc.clone()),
}
}
}
@@ -3290,7 +3290,7 @@ pub struct MetricsTree_Blocks {
pub count: MetricsTree_Blocks_Count,
pub interval: AverageHeightMaxMedianMinP10P25P75P90Pattern<Timestamp>,
pub halving: MetricsTree_Blocks_Halving,
pub vbytes: CumulativeHeightRollingPattern2<StoredU64>,
pub vbytes: CumulativeHeightRollingPattern<StoredU64>,
pub size: AverageCumulativeMaxMedianMinP10P25P75P90SumPattern,
pub fullness: AverageHeightMaxMedianMinP10P25P75P90Pattern<StoredF32>,
}
@@ -3306,7 +3306,7 @@ impl MetricsTree_Blocks {
count: MetricsTree_Blocks_Count::new(client.clone(), format!("{base_path}_count")),
interval: AverageHeightMaxMedianMinP10P25P75P90Pattern::new(client.clone(), "block_interval".to_string()),
halving: MetricsTree_Blocks_Halving::new(client.clone(), format!("{base_path}_halving")),
vbytes: CumulativeHeightRollingPattern2::new(client.clone(), "block_vbytes".to_string()),
vbytes: CumulativeHeightRollingPattern::new(client.clone(), "block_vbytes".to_string()),
size: AverageCumulativeMaxMedianMinP10P25P75P90SumPattern::new(client.clone(), "block_size".to_string()),
fullness: AverageHeightMaxMedianMinP10P25P75P90Pattern::new(client.clone(), "block_fullness".to_string()),
}
@@ -3436,7 +3436,7 @@ impl MetricsTree_Blocks_Weight {
/// Metrics tree node.
pub struct MetricsTree_Blocks_Count {
pub block_count_target: MetricPattern1<StoredU64>,
pub block_count: CumulativeHeightRollingPattern<StoredU32>,
pub block_count: CumulativeHeightSumPattern<StoredU32>,
pub block_count_sum: _1y24h30d7dPattern<StoredU32>,
pub height_1h_ago: MetricPattern20<Height>,
pub height_24h_ago: MetricPattern20<Height>,
@@ -3475,7 +3475,7 @@ impl MetricsTree_Blocks_Count {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
block_count_target: MetricPattern1::new(client.clone(), "block_count_target".to_string()),
block_count: CumulativeHeightRollingPattern::new(client.clone(), "block_count".to_string()),
block_count: CumulativeHeightSumPattern::new(client.clone(), "block_count".to_string()),
block_count_sum: _1y24h30d7dPattern::new(client.clone(), "block_count_sum".to_string()),
height_1h_ago: MetricPattern20::new(client.clone(), "height_1h_ago".to_string()),
height_24h_ago: MetricPattern20::new(client.clone(), "height_24h_ago".to_string()),
@@ -3572,14 +3572,14 @@ impl MetricsTree_Transactions {
/// Metrics tree node.
pub struct MetricsTree_Transactions_Count {
pub tx_count: CumulativeHeightRollingPattern2<StoredU64>,
pub tx_count: CumulativeHeightRollingPattern<StoredU64>,
pub is_coinbase: MetricPattern21<StoredBool>,
}
impl MetricsTree_Transactions_Count {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
tx_count: CumulativeHeightRollingPattern2::new(client.clone(), "tx_count".to_string()),
tx_count: CumulativeHeightRollingPattern::new(client.clone(), "tx_count".to_string()),
is_coinbase: MetricPattern21::new(client.clone(), "is_coinbase".to_string()),
}
}
@@ -3621,17 +3621,17 @@ impl MetricsTree_Transactions_Fees {
/// Metrics tree node.
pub struct MetricsTree_Transactions_Versions {
pub v1: CumulativeHeightRollingPattern<StoredU64>,
pub v2: CumulativeHeightRollingPattern<StoredU64>,
pub v3: CumulativeHeightRollingPattern<StoredU64>,
pub v1: CumulativeHeightSumPattern<StoredU64>,
pub v2: CumulativeHeightSumPattern<StoredU64>,
pub v3: CumulativeHeightSumPattern<StoredU64>,
}
impl MetricsTree_Transactions_Versions {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
v1: CumulativeHeightRollingPattern::new(client.clone(), "tx_v1".to_string()),
v2: CumulativeHeightRollingPattern::new(client.clone(), "tx_v2".to_string()),
v3: CumulativeHeightRollingPattern::new(client.clone(), "tx_v3".to_string()),
v1: CumulativeHeightSumPattern::new(client.clone(), "tx_v1".to_string()),
v2: CumulativeHeightSumPattern::new(client.clone(), "tx_v2".to_string()),
v3: CumulativeHeightSumPattern::new(client.clone(), "tx_v3".to_string()),
}
}
}
@@ -3828,19 +3828,19 @@ impl MetricsTree_Scripts {
/// Metrics tree node.
pub struct MetricsTree_Scripts_Count {
pub p2a: CumulativeHeightRollingPattern<StoredU64>,
pub p2ms: CumulativeHeightRollingPattern<StoredU64>,
pub p2pk33: CumulativeHeightRollingPattern<StoredU64>,
pub p2pk65: CumulativeHeightRollingPattern<StoredU64>,
pub p2pkh: CumulativeHeightRollingPattern<StoredU64>,
pub p2sh: CumulativeHeightRollingPattern<StoredU64>,
pub p2tr: CumulativeHeightRollingPattern<StoredU64>,
pub p2wpkh: CumulativeHeightRollingPattern<StoredU64>,
pub p2wsh: CumulativeHeightRollingPattern<StoredU64>,
pub opreturn: CumulativeHeightRollingPattern<StoredU64>,
pub emptyoutput: CumulativeHeightRollingPattern<StoredU64>,
pub unknownoutput: CumulativeHeightRollingPattern<StoredU64>,
pub segwit: CumulativeHeightRollingPattern<StoredU64>,
pub p2a: CumulativeHeightSumPattern<StoredU64>,
pub p2ms: CumulativeHeightSumPattern<StoredU64>,
pub p2pk33: CumulativeHeightSumPattern<StoredU64>,
pub p2pk65: CumulativeHeightSumPattern<StoredU64>,
pub p2pkh: CumulativeHeightSumPattern<StoredU64>,
pub p2sh: CumulativeHeightSumPattern<StoredU64>,
pub p2tr: CumulativeHeightSumPattern<StoredU64>,
pub p2wpkh: CumulativeHeightSumPattern<StoredU64>,
pub p2wsh: CumulativeHeightSumPattern<StoredU64>,
pub opreturn: CumulativeHeightSumPattern<StoredU64>,
pub emptyoutput: CumulativeHeightSumPattern<StoredU64>,
pub unknownoutput: CumulativeHeightSumPattern<StoredU64>,
pub segwit: CumulativeHeightSumPattern<StoredU64>,
pub taproot_adoption: MetricPattern1<StoredF32>,
pub segwit_adoption: MetricPattern1<StoredF32>,
}
@@ -3848,19 +3848,19 @@ pub struct MetricsTree_Scripts_Count {
impl MetricsTree_Scripts_Count {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
p2a: CumulativeHeightRollingPattern::new(client.clone(), "p2a_count".to_string()),
p2ms: CumulativeHeightRollingPattern::new(client.clone(), "p2ms_count".to_string()),
p2pk33: CumulativeHeightRollingPattern::new(client.clone(), "p2pk33_count".to_string()),
p2pk65: CumulativeHeightRollingPattern::new(client.clone(), "p2pk65_count".to_string()),
p2pkh: CumulativeHeightRollingPattern::new(client.clone(), "p2pkh_count".to_string()),
p2sh: CumulativeHeightRollingPattern::new(client.clone(), "p2sh_count".to_string()),
p2tr: CumulativeHeightRollingPattern::new(client.clone(), "p2tr_count".to_string()),
p2wpkh: CumulativeHeightRollingPattern::new(client.clone(), "p2wpkh_count".to_string()),
p2wsh: CumulativeHeightRollingPattern::new(client.clone(), "p2wsh_count".to_string()),
opreturn: CumulativeHeightRollingPattern::new(client.clone(), "opreturn_count".to_string()),
emptyoutput: CumulativeHeightRollingPattern::new(client.clone(), "emptyoutput_count".to_string()),
unknownoutput: CumulativeHeightRollingPattern::new(client.clone(), "unknownoutput_count".to_string()),
segwit: CumulativeHeightRollingPattern::new(client.clone(), "segwit_count".to_string()),
p2a: CumulativeHeightSumPattern::new(client.clone(), "p2a_count".to_string()),
p2ms: CumulativeHeightSumPattern::new(client.clone(), "p2ms_count".to_string()),
p2pk33: CumulativeHeightSumPattern::new(client.clone(), "p2pk33_count".to_string()),
p2pk65: CumulativeHeightSumPattern::new(client.clone(), "p2pk65_count".to_string()),
p2pkh: CumulativeHeightSumPattern::new(client.clone(), "p2pkh_count".to_string()),
p2sh: CumulativeHeightSumPattern::new(client.clone(), "p2sh_count".to_string()),
p2tr: CumulativeHeightSumPattern::new(client.clone(), "p2tr_count".to_string()),
p2wpkh: CumulativeHeightSumPattern::new(client.clone(), "p2wpkh_count".to_string()),
p2wsh: CumulativeHeightSumPattern::new(client.clone(), "p2wsh_count".to_string()),
opreturn: CumulativeHeightSumPattern::new(client.clone(), "opreturn_count".to_string()),
emptyoutput: CumulativeHeightSumPattern::new(client.clone(), "emptyoutput_count".to_string()),
unknownoutput: CumulativeHeightSumPattern::new(client.clone(), "unknownoutput_count".to_string()),
segwit: CumulativeHeightSumPattern::new(client.clone(), "segwit_count".to_string()),
taproot_adoption: MetricPattern1::new(client.clone(), "taproot_adoption".to_string()),
segwit_adoption: MetricPattern1::new(client.clone(), "segwit_adoption".to_string()),
}
@@ -4023,8 +4023,8 @@ impl MetricsTree_Cointime {
/// Metrics tree node.
pub struct MetricsTree_Cointime_Activity {
pub coinblocks_created: CumulativeHeightRollingPattern<StoredF64>,
pub coinblocks_stored: CumulativeHeightRollingPattern<StoredF64>,
pub coinblocks_created: CumulativeHeightSumPattern<StoredF64>,
pub coinblocks_stored: CumulativeHeightSumPattern<StoredF64>,
pub liveliness: MetricPattern1<StoredF64>,
pub vaultedness: MetricPattern1<StoredF64>,
pub activity_to_vaultedness_ratio: MetricPattern1<StoredF64>,
@@ -4033,8 +4033,8 @@ pub struct MetricsTree_Cointime_Activity {
impl MetricsTree_Cointime_Activity {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
coinblocks_created: CumulativeHeightRollingPattern::new(client.clone(), "coinblocks_created".to_string()),
coinblocks_stored: CumulativeHeightRollingPattern::new(client.clone(), "coinblocks_stored".to_string()),
coinblocks_created: CumulativeHeightSumPattern::new(client.clone(), "coinblocks_created".to_string()),
coinblocks_stored: CumulativeHeightSumPattern::new(client.clone(), "coinblocks_stored".to_string()),
liveliness: MetricPattern1::new(client.clone(), "liveliness".to_string()),
vaultedness: MetricPattern1::new(client.clone(), "vaultedness".to_string()),
activity_to_vaultedness_ratio: MetricPattern1::new(client.clone(), "activity_to_vaultedness_ratio".to_string()),
@@ -4059,19 +4059,19 @@ impl MetricsTree_Cointime_Supply {
/// Metrics tree node.
pub struct MetricsTree_Cointime_Value {
pub cointime_value_destroyed: CumulativeHeightRollingPattern<StoredF64>,
pub cointime_value_created: CumulativeHeightRollingPattern<StoredF64>,
pub cointime_value_stored: CumulativeHeightRollingPattern<StoredF64>,
pub vocdd: CumulativeHeightRollingPattern<StoredF64>,
pub cointime_value_destroyed: CumulativeHeightSumPattern<StoredF64>,
pub cointime_value_created: CumulativeHeightSumPattern<StoredF64>,
pub cointime_value_stored: CumulativeHeightSumPattern<StoredF64>,
pub vocdd: CumulativeHeightSumPattern<StoredF64>,
}
impl MetricsTree_Cointime_Value {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
cointime_value_destroyed: CumulativeHeightRollingPattern::new(client.clone(), "cointime_value_destroyed".to_string()),
cointime_value_created: CumulativeHeightRollingPattern::new(client.clone(), "cointime_value_created".to_string()),
cointime_value_stored: CumulativeHeightRollingPattern::new(client.clone(), "cointime_value_stored".to_string()),
vocdd: CumulativeHeightRollingPattern::new(client.clone(), "vocdd".to_string()),
cointime_value_destroyed: CumulativeHeightSumPattern::new(client.clone(), "cointime_value_destroyed".to_string()),
cointime_value_created: CumulativeHeightSumPattern::new(client.clone(), "cointime_value_created".to_string()),
cointime_value_stored: CumulativeHeightSumPattern::new(client.clone(), "cointime_value_stored".to_string()),
vocdd: CumulativeHeightSumPattern::new(client.clone(), "vocdd".to_string()),
}
}
}
@@ -171,7 +171,7 @@ impl Vecs {
_30d: &self.height_1m_ago,
_1y: &self.height_1y_ago,
};
self.block_count.rolling.compute_rolling_sum(
self.block_count.sum.compute_rolling_sum(
starting_indexes.height,
&ws,
&self.block_count.height,
@@ -128,7 +128,7 @@ pub(crate) fn process_blocks(
debug!("txindex_to_height RangeMap built");
// Create reusable iterators for sequential txout/txin reads (16KB buffered)
let txout_iters = TxOutReaders::new(indexer);
let mut txout_iters = TxOutReaders::new(indexer);
let mut txin_iters = TxInReaders::new(indexer, inputs, &mut txindex_to_height);
// Pre-collect first address indexes per type for the block range
@@ -21,32 +21,40 @@ pub struct TxOutData {
pub typeindex: TypeIndex,
}
/// Readers for txout vectors. Uses collect_range for bulk reads.
/// Readers for txout vectors. Reuses internal buffers across blocks.
pub struct TxOutReaders<'a> {
indexer: &'a Indexer,
values_buf: Vec<Sats>,
outputtypes_buf: Vec<OutputType>,
typeindexes_buf: Vec<TypeIndex>,
}
impl<'a> TxOutReaders<'a> {
pub(crate) fn new(indexer: &'a Indexer) -> Self {
Self { indexer }
Self {
indexer,
values_buf: Vec::new(),
outputtypes_buf: Vec::new(),
typeindexes_buf: Vec::new(),
}
}
/// Collect output data for a block range using bulk reads.
/// Collect output data for a block range using bulk reads with buffer reuse.
pub(crate) fn collect_block_outputs(
&self,
&mut self,
first_txoutindex: usize,
output_count: usize,
) -> Vec<TxOutData> {
let end = first_txoutindex + output_count;
let values: Vec<Sats> = self.indexer.vecs.outputs.value.collect_range_at(first_txoutindex, end);
let outputtypes: Vec<OutputType> = self.indexer.vecs.outputs.outputtype.collect_range_at(first_txoutindex, end);
let typeindexes: Vec<TypeIndex> = self.indexer.vecs.outputs.typeindex.collect_range_at(first_txoutindex, end);
self.indexer.vecs.outputs.value.collect_range_into_at(first_txoutindex, end, &mut self.values_buf);
self.indexer.vecs.outputs.outputtype.collect_range_into_at(first_txoutindex, end, &mut self.outputtypes_buf);
self.indexer.vecs.outputs.typeindex.collect_range_into_at(first_txoutindex, end, &mut self.typeindexes_buf);
values
.into_iter()
.zip(outputtypes)
.zip(typeindexes)
.map(|((value, outputtype), typeindex)| TxOutData {
self.values_buf
.iter()
.zip(&self.outputtypes_buf)
.zip(&self.typeindexes_buf)
.map(|((&value, &outputtype), &typeindex)| TxOutData {
value,
outputtype,
typeindex,
@@ -55,11 +63,12 @@ impl<'a> TxOutReaders<'a> {
}
}
/// Readers for txin vectors. Uses collect_range for bulk reads.
/// Readers for txin vectors. Reuses outpoint buffer across blocks.
pub struct TxInReaders<'a> {
indexer: &'a Indexer,
txins: &'a inputs::Vecs,
txindex_to_height: &'a mut RangeMap<TxIndex, Height>,
outpoints_buf: Vec<OutPoint>,
}
impl<'a> TxInReaders<'a> {
@@ -72,11 +81,12 @@ impl<'a> TxInReaders<'a> {
indexer,
txins,
txindex_to_height,
outpoints_buf: Vec::new(),
}
}
/// Collect input data for a block range using bulk reads.
/// Computes prev_height on-the-fly from outpoint using RangeMap lookup.
/// Outpoint buffer is reused across blocks; returned vecs are fresh (caller-owned).
pub(crate) fn collect_block_inputs(
&mut self,
first_txinindex: usize,
@@ -85,11 +95,11 @@ impl<'a> TxInReaders<'a> {
) -> (Vec<Sats>, Vec<Height>, Vec<OutputType>, Vec<TypeIndex>) {
let end = first_txinindex + input_count;
let values: Vec<Sats> = self.txins.spent.value.collect_range_at(first_txinindex, end);
let outpoints: Vec<OutPoint> = self.indexer.vecs.inputs.outpoint.collect_range_at(first_txinindex, end);
self.indexer.vecs.inputs.outpoint.collect_range_into_at(first_txinindex, end, &mut self.outpoints_buf);
let outputtypes: Vec<OutputType> = self.indexer.vecs.inputs.outputtype.collect_range_at(first_txinindex, end);
let typeindexes: Vec<TypeIndex> = self.indexer.vecs.inputs.typeindex.collect_range_at(first_txinindex, end);
let prev_heights: Vec<Height> = outpoints
let prev_heights: Vec<Height> = self.outpoints_buf
.iter()
.map(|outpoint| {
if outpoint.is_coinbase() {
@@ -11,8 +11,6 @@ use brk_types::{
use rustc_hash::FxHashMap;
use vecdb::Bytes;
use crate::utils::OptionExt;
use super::{CachedUnrealizedState, Percentiles, UnrealizedState};
/// Type alias for the price-to-sats map used in cost basis data.
@@ -97,17 +95,17 @@ impl CostBasisData {
pub(crate) fn iter(&self) -> impl Iterator<Item = (CentsCompact, &Sats)> {
self.assert_pending_empty();
self.state.u().base.map.iter().map(|(&k, v)| (k, v))
self.state.as_ref().unwrap().base.map.iter().map(|(&k, v)| (k, v))
}
pub(crate) fn is_empty(&self) -> bool {
self.pending.is_empty() && self.state.u().base.map.is_empty()
self.pending.is_empty() && self.state.as_ref().unwrap().base.map.is_empty()
}
pub(crate) fn first_key_value(&self) -> Option<(CentsCompact, &Sats)> {
self.assert_pending_empty();
self.state
.u()
.as_ref().unwrap()
.base
.map
.first_key_value()
@@ -117,7 +115,7 @@ impl CostBasisData {
pub(crate) fn last_key_value(&self) -> Option<(CentsCompact, &Sats)> {
self.assert_pending_empty();
self.state
.u()
.as_ref().unwrap()
.base
.map
.last_key_value()
@@ -127,13 +125,13 @@ impl CostBasisData {
/// Get the exact cap_raw value (not recomputed from map).
pub(crate) fn cap_raw(&self) -> CentsSats {
self.assert_pending_empty();
self.state.u().cap_raw
self.state.as_ref().unwrap().cap_raw
}
/// Get the exact investor_cap_raw value (not recomputed from map).
pub(crate) fn investor_cap_raw(&self) -> CentsSquaredSats {
self.assert_pending_empty();
self.state.u().investor_cap_raw
self.state.as_ref().unwrap().investor_cap_raw
}
/// Increment with pre-computed typed values.
@@ -181,7 +179,7 @@ impl CostBasisData {
self.percentiles_dirty = true;
}
for (cents, (inc, dec)) in self.pending.drain() {
let entry = self.state.um().base.map.entry(cents).or_default();
let entry = self.state.as_mut().unwrap().base.map.entry(cents).or_default();
*entry += inc;
if *entry < dec {
panic!(
@@ -198,12 +196,12 @@ impl CostBasisData {
}
*entry -= dec;
if *entry == Sats::ZERO {
self.state.um().base.map.remove(&cents);
self.state.as_mut().unwrap().base.map.remove(&cents);
}
}
// Apply raw values
let state = self.state.um();
let state = self.state.as_mut().unwrap();
state.cap_raw += self.pending_raw.cap_inc;
// Check for underflow before subtracting
@@ -271,7 +269,7 @@ impl CostBasisData {
);
}
let map = &self.state.u().base.map;
let map = &self.state.as_ref().unwrap().base.map;
let date_state =
date_price.map(|p| CachedUnrealizedState::compute_full_standalone(p.into(), map));
@@ -336,7 +334,7 @@ impl CostBasisData {
}
}
fs::write(self.path_state(height), self.state.u().serialize()?)?;
fs::write(self.path_state(height), self.state.as_ref().unwrap().serialize()?)?;
Ok(())
}
@@ -48,19 +48,18 @@ impl Vecs {
while batch_start < target {
let batch_end = (batch_start + BATCH_SIZE).min(target);
let outpoints = indexer.vecs.inputs.outpoint.collect_range_at(batch_start, batch_end);
entries.clear();
for (j, outpoint) in outpoints.into_iter().enumerate() {
let txinindex = TxInIndex::from(batch_start + j);
let mut j = 0usize;
indexer.vecs.inputs.outpoint.for_each_range_at(batch_start, batch_end, |outpoint| {
entries.push(Entry {
txinindex,
txinindex: TxInIndex::from(batch_start + j),
txindex: outpoint.txindex(),
vout: outpoint.vout(),
txoutindex: TxOutIndex::COINBASE,
value: Sats::MAX,
});
}
j += 1;
});
// Coinbase entries (txindex MAX) sorted to end
entries.sort_unstable_by_key(|e| e.txindex);
+3 -3
View File
@@ -11,7 +11,7 @@ use vecdb::{
VecValue,
};
use crate::utils::get_percentile;
use brk_types::get_percentile;
use super::ComputedVecValue;
@@ -358,6 +358,7 @@ where
let window_starts_batch: Vec<I> = window_starts.collect_range_at(start, fi_len);
let zero = T::from(0_usize);
let mut values: Vec<T> = Vec::new();
first_indexes_batch
.iter()
@@ -389,8 +390,7 @@ where
vec.truncate_push_at(idx, zero)?;
}
} else {
let mut values: Vec<T> =
source.collect_range_at(range_start_usize, range_end_usize);
source.collect_range_into_at(range_start_usize, range_end_usize, &mut values);
// Compute sum before sorting
let len = values.len();
@@ -15,3 +15,31 @@ pub struct DistributionStats<A, B = A, C = A, D = A, E = A, F = A, G = A, H = A>
pub p75: G,
pub p90: H,
}
impl<A> DistributionStats<A> {
/// Apply a fallible operation to each of the 8 fields.
pub fn try_for_each_mut(&mut self, mut f: impl FnMut(&mut A) -> brk_error::Result<()>) -> brk_error::Result<()> {
f(&mut self.average)?;
f(&mut self.min)?;
f(&mut self.max)?;
f(&mut self.p10)?;
f(&mut self.p25)?;
f(&mut self.median)?;
f(&mut self.p75)?;
f(&mut self.p90)?;
Ok(())
}
/// Get minimum value by applying a function to each field.
pub fn min_by(&self, mut f: impl FnMut(&A) -> usize) -> usize {
f(&self.average)
.min(f(&self.min))
.min(f(&self.max))
.min(f(&self.p10))
.min(f(&self.p25))
.min(f(&self.median))
.min(f(&self.p75))
.min(f(&self.p90))
}
}
+1
View File
@@ -6,6 +6,7 @@ mod indexes;
mod lazy_eager_indexes;
mod multi;
mod single;
pub(crate) mod sliding_window;
mod traits;
mod windows;
@@ -24,7 +24,7 @@ where
{
pub height: M::Stored<EagerVec<PcoVec<Height, T>>>,
pub cumulative: ComputedFromHeightLast<T, M>,
pub rolling: RollingWindows<T, M>,
pub sum: RollingWindows<T, M>,
}
const VERSION: Version = Version::ZERO;
@@ -49,7 +49,7 @@ where
Ok(Self {
height,
cumulative,
rolling,
sum: rolling,
})
}
@@ -68,7 +68,7 @@ where
self.cumulative
.height
.compute_cumulative(max_from, &self.height, exit)?;
self.rolling
self.sum
.compute_rolling_sum(max_from, windows, &self.height, exit)?;
Ok(())
}
@@ -6,8 +6,8 @@ use vecdb::{AnyStoredVec, AnyVec, Database, EagerVec, Exit, PcoVec, ReadableVec,
use crate::{
ComputeIndexes, blocks, indexes,
internal::{ComputedFromHeightStdDevExtended, Price},
utils::get_percentile,
};
use brk_types::get_percentile;
use super::super::ComputedFromHeightLast;
@@ -7,7 +7,7 @@ use brk_types::{
};
use derive_more::{Deref, DerefMut};
use schemars::JsonSchema;
use vecdb::{LazyAggVec, ReadableBoxedVec, ReadableCloneableVec};
use vecdb::{LazyAggVec, ReadOnlyClone, ReadableBoxedVec, ReadableCloneableVec};
use crate::{
indexes, indexes_from,
@@ -41,6 +41,17 @@ pub struct ComputedHeightDerivedLast<T>(
where
T: ComputedVecValue + PartialOrd + JsonSchema;
/// Already read-only (no StorageMode); cloning is sufficient.
impl<T> ReadOnlyClone for ComputedHeightDerivedLast<T>
where
T: ComputedVecValue + PartialOrd + JsonSchema,
{
type ReadOnly = Self;
fn read_only_clone(&self) -> Self {
self.clone()
}
}
const VERSION: Version = Version::ZERO;
impl<T> ComputedHeightDerivedLast<T>
@@ -9,7 +9,9 @@ use brk_types::{
};
use derive_more::{Deref, DerefMut};
use schemars::JsonSchema;
use vecdb::{LazyVecFrom1, ReadableBoxedVec, ReadableCloneableVec, UnaryTransform, VecIndex, VecValue};
use vecdb::{
LazyVecFrom1, ReadableBoxedVec, ReadableCloneableVec, UnaryTransform, VecIndex, VecValue,
};
use crate::{
indexes, indexes_from,
@@ -108,7 +110,8 @@ where
where
S1T: NumericValue,
{
let derived = ComputedHeightDerivedLast::forced_import(name, height_source, version, indexes);
let derived =
ComputedHeightDerivedLast::forced_import(name, height_source, version, indexes);
Self::from_derived_computed::<F>(name, version, &derived)
}
@@ -66,62 +66,33 @@ where
T: Copy + Ord + From<f64> + Default,
f64: From<T>,
{
// Single pass per window: all 8 stats extracted from one sorted vec
compute_rolling_distribution_from_starts(
max_from,
windows._24h,
source,
&mut self.0.average._24h.height,
&mut self.0.min._24h.height,
&mut self.0.max._24h.height,
&mut self.0.p10._24h.height,
&mut self.0.p25._24h.height,
&mut self.0.median._24h.height,
&mut self.0.p75._24h.height,
&mut self.0.p90._24h.height,
exit,
max_from, windows._24h, source,
&mut self.0.average._24h.height, &mut self.0.min._24h.height,
&mut self.0.max._24h.height, &mut self.0.p10._24h.height,
&mut self.0.p25._24h.height, &mut self.0.median._24h.height,
&mut self.0.p75._24h.height, &mut self.0.p90._24h.height, exit,
)?;
compute_rolling_distribution_from_starts(
max_from,
windows._7d,
source,
&mut self.0.average._7d.height,
&mut self.0.min._7d.height,
&mut self.0.max._7d.height,
&mut self.0.p10._7d.height,
&mut self.0.p25._7d.height,
&mut self.0.median._7d.height,
&mut self.0.p75._7d.height,
&mut self.0.p90._7d.height,
exit,
max_from, windows._7d, source,
&mut self.0.average._7d.height, &mut self.0.min._7d.height,
&mut self.0.max._7d.height, &mut self.0.p10._7d.height,
&mut self.0.p25._7d.height, &mut self.0.median._7d.height,
&mut self.0.p75._7d.height, &mut self.0.p90._7d.height, exit,
)?;
compute_rolling_distribution_from_starts(
max_from,
windows._30d,
source,
&mut self.0.average._30d.height,
&mut self.0.min._30d.height,
&mut self.0.max._30d.height,
&mut self.0.p10._30d.height,
&mut self.0.p25._30d.height,
&mut self.0.median._30d.height,
&mut self.0.p75._30d.height,
&mut self.0.p90._30d.height,
exit,
max_from, windows._30d, source,
&mut self.0.average._30d.height, &mut self.0.min._30d.height,
&mut self.0.max._30d.height, &mut self.0.p10._30d.height,
&mut self.0.p25._30d.height, &mut self.0.median._30d.height,
&mut self.0.p75._30d.height, &mut self.0.p90._30d.height, exit,
)?;
compute_rolling_distribution_from_starts(
max_from,
windows._1y,
source,
&mut self.0.average._1y.height,
&mut self.0.min._1y.height,
&mut self.0.max._1y.height,
&mut self.0.p10._1y.height,
&mut self.0.p25._1y.height,
&mut self.0.median._1y.height,
&mut self.0.p75._1y.height,
&mut self.0.p90._1y.height,
exit,
max_from, windows._1y, source,
&mut self.0.average._1y.height, &mut self.0.min._1y.height,
&mut self.0.max._1y.height, &mut self.0.p10._1y.height,
&mut self.0.p25._1y.height, &mut self.0.median._1y.height,
&mut self.0.p75._1y.height, &mut self.0.p90._1y.height, exit,
)?;
Ok(())
@@ -72,18 +72,9 @@ impl StoredValueRollingWindows {
usd_source: &impl ReadableVec<Height, Dollars>,
exit: &Exit,
) -> Result<()> {
self.0
._24h
.compute_rolling_sum(max_from, windows._24h, sats_source, usd_source, exit)?;
self.0
._7d
.compute_rolling_sum(max_from, windows._7d, sats_source, usd_source, exit)?;
self.0
._30d
.compute_rolling_sum(max_from, windows._30d, sats_source, usd_source, exit)?;
self.0
._1y
.compute_rolling_sum(max_from, windows._1y, sats_source, usd_source, exit)?;
for (w, starts) in self.0.as_mut_array().into_iter().zip(windows.as_array()) {
w.compute_rolling_sum(max_from, starts, sats_source, usd_source, exit)?;
}
Ok(())
}
}
@@ -26,6 +26,12 @@ pub struct WindowStarts<'a> {
pub _1y: &'a EagerVec<PcoVec<Height, Height>>,
}
impl<'a> WindowStarts<'a> {
pub fn as_array(&self) -> [&'a EagerVec<PcoVec<Height, Height>>; 4] {
[self._24h, self._7d, self._30d, self._1y]
}
}
/// 4 rolling window vecs (24h, 7d, 30d, 1y), each with height data + all 17 index views.
#[derive(Deref, DerefMut, Traversable)]
#[traversable(transparent)]
@@ -64,22 +70,9 @@ where
where
T: Default + SubAssign,
{
self.0
._24h
.height
.compute_rolling_sum(max_from, windows._24h, source, exit)?;
self.0
._7d
.height
.compute_rolling_sum(max_from, windows._7d, source, exit)?;
self.0
._30d
.height
.compute_rolling_sum(max_from, windows._30d, source, exit)?;
self.0
._1y
.height
.compute_rolling_sum(max_from, windows._1y, source, exit)?;
for (w, starts) in self.0.as_mut_array().into_iter().zip(windows.as_array()) {
w.height.compute_rolling_sum(max_from, starts, source, exit)?;
}
Ok(())
}
}
@@ -1,6 +1,8 @@
mod block_count_target;
mod cents_to_dollars;
mod cents_to_sats;
mod ohlc_cents_to_dollars;
mod ohlc_cents_to_sats;
mod dollar_halve;
mod dollar_identity;
@@ -35,6 +37,8 @@ mod volatility_sqrt7;
pub use block_count_target::*;
pub use cents_to_dollars::*;
pub use cents_to_sats::*;
pub use ohlc_cents_to_dollars::*;
pub use ohlc_cents_to_sats::*;
pub use dollar_halve::*;
pub use dollar_identity::*;
@@ -0,0 +1,11 @@
use brk_types::{OHLCCents, OHLCDollars};
use vecdb::UnaryTransform;
pub struct OhlcCentsToDollars;
impl UnaryTransform<OHLCCents, OHLCDollars> for OhlcCentsToDollars {
#[inline(always)]
fn apply(cents: OHLCCents) -> OHLCDollars {
OHLCDollars::from(cents)
}
}
@@ -0,0 +1,19 @@
use brk_types::{Close, High, Low, OHLCCents, OHLCSats, Open};
use vecdb::UnaryTransform;
use super::CentsUnsignedToSats;
/// OHLCCents -> OHLCSats with high/low swapped (inverse price relationship).
pub struct OhlcCentsToSats;
impl UnaryTransform<OHLCCents, OHLCSats> for OhlcCentsToSats {
#[inline(always)]
fn apply(cents: OHLCCents) -> OHLCSats {
OHLCSats {
open: Open::new(CentsUnsignedToSats::apply(*cents.open)),
high: High::new(CentsUnsignedToSats::apply(*cents.low)),
low: Low::new(CentsUnsignedToSats::apply(*cents.high)),
close: Close::new(CentsUnsignedToSats::apply(*cents.close)),
}
}
}
@@ -0,0 +1,188 @@
/// Sqrt-decomposed sorted structure for O(sqrt(n)) insert/remove/kth.
///
/// Maintains `blocks` sorted sub-arrays where each block is sorted and
/// the blocks are ordered (max of block[i] <= min of block[i+1]).
/// Total element count is tracked via `total_len`.
struct SortedBlocks {
blocks: Vec<Vec<f64>>,
total_len: usize,
block_size: usize,
}
impl SortedBlocks {
fn new(capacity: usize) -> Self {
let block_size = ((capacity as f64).sqrt() as usize).max(64);
Self {
blocks: Vec::new(),
total_len: 0,
block_size,
}
}
fn len(&self) -> usize {
self.total_len
}
fn is_empty(&self) -> bool {
self.total_len == 0
}
/// Insert a value in sorted order. O(sqrt(n)).
fn insert(&mut self, value: f64) {
self.total_len += 1;
if self.blocks.is_empty() {
self.blocks.push(vec![value]);
return;
}
// Find the block where value belongs: first block whose max >= value
let block_idx = self.blocks.iter().position(|b| {
*b.last().unwrap() >= value
}).unwrap_or(self.blocks.len() - 1);
let block = &mut self.blocks[block_idx];
let pos = block.partition_point(|a| *a < value);
block.insert(pos, value);
// Split if block too large
if block.len() > 2 * self.block_size {
let mid = block.len() / 2;
let right = block[mid..].to_vec();
block.truncate(mid);
self.blocks.insert(block_idx + 1, right);
}
}
/// Remove one occurrence of value. O(sqrt(n)).
fn remove(&mut self, value: f64) -> bool {
for (bi, block) in self.blocks.iter_mut().enumerate() {
if block.is_empty() {
continue;
}
// If value > block max, it's not in this block
if *block.last().unwrap() < value {
continue;
}
let pos = block.partition_point(|a| *a < value);
if pos < block.len() && block[pos] == value {
block.remove(pos);
self.total_len -= 1;
if block.is_empty() {
self.blocks.remove(bi);
}
return true;
}
// Value not found (would be in this block range but isn't)
return false;
}
false
}
/// Get the k-th smallest element (0-indexed). O(sqrt(n)).
fn kth(&self, mut k: usize) -> f64 {
for block in &self.blocks {
if k < block.len() {
return block[k];
}
k -= block.len();
}
unreachable!("kth out of bounds")
}
fn first(&self) -> f64 {
self.blocks.first().unwrap().first().copied().unwrap()
}
fn last(&self) -> f64 {
self.blocks.last().unwrap().last().copied().unwrap()
}
}
/// Sorted sliding window for rolling distribution/median computations.
///
/// Uses sqrt-decomposition for O(sqrt(n)) insert/remove/kth instead of
/// O(n) memmoves with a flat sorted Vec.
pub(crate) struct SlidingWindowSorted {
sorted: SortedBlocks,
running_sum: f64,
prev_start: usize,
}
impl SlidingWindowSorted {
pub fn with_capacity(cap: usize) -> Self {
Self {
sorted: SortedBlocks::new(cap),
running_sum: 0.0,
prev_start: 0,
}
}
/// Reconstruct state from historical data (the elements in [range_start..skip]).
pub fn reconstruct(&mut self, partial_values: &[f64], range_start: usize, skip: usize) {
self.prev_start = range_start;
for idx in range_start..skip {
let v = partial_values[idx - range_start];
self.running_sum += v;
self.sorted.insert(v);
}
}
/// Add a new value and remove all expired values up to `new_start`.
pub fn advance(&mut self, value: f64, new_start: usize, partial_values: &[f64], range_start: usize) {
self.running_sum += value;
self.sorted.insert(value);
while self.prev_start < new_start {
let old = partial_values[self.prev_start - range_start];
self.running_sum -= old;
self.sorted.remove(old);
self.prev_start += 1;
}
}
#[inline]
pub fn is_empty(&self) -> bool {
self.sorted.is_empty()
}
#[inline]
pub fn average(&self) -> f64 {
if self.sorted.is_empty() {
0.0
} else {
self.running_sum / self.sorted.len() as f64
}
}
#[inline]
pub fn min(&self) -> f64 {
if self.sorted.is_empty() { 0.0 } else { self.sorted.first() }
}
#[inline]
pub fn max(&self) -> f64 {
if self.sorted.is_empty() { 0.0 } else { self.sorted.last() }
}
/// Extract a percentile (0.0-1.0) using linear interpolation.
#[inline]
pub fn percentile(&self, p: f64) -> f64 {
let len = self.sorted.len();
if len == 0 {
return 0.0;
}
if len == 1 {
return self.sorted.kth(0);
}
let rank = p * (len - 1) as f64;
let lo = rank.floor() as usize;
let hi = rank.ceil() as usize;
if lo == hi {
self.sorted.kth(lo)
} else {
let frac = rank - lo as f64;
self.sorted.kth(lo) * (1.0 - frac) + self.sorted.kth(hi) * frac
}
}
}
@@ -15,3 +15,9 @@ pub struct Windows<A, B = A, C = A, D = A> {
#[traversable(rename = "1y")]
pub _1y: D,
}
impl<A> Windows<A> {
pub fn as_mut_array(&mut self) -> [&mut A; 4] {
[&mut self._24h, &mut self._7d, &mut self._30d, &mut self._1y]
}
}
-1
View File
@@ -27,7 +27,6 @@ mod scripts;
mod supply;
mod traits;
mod transactions;
mod utils;
use indexes::ComputeIndexes;
@@ -23,7 +23,7 @@ impl Vecs {
exit: &Exit,
) -> Result<()> {
let h2d = &indexes.height.day1;
let close = &prices.usd.close.day1;
let close = &prices.usd.split.close.day1;
let first_price_di = Day1::try_from(Date::new(2010, 7, 12))
.unwrap()
@@ -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.close.day1.collect_or_default(),
"1w" => prices.usd.close.week1.collect_or_default(),
"1m" => prices.usd.close.month1.collect_or_default(),
"1y" => prices.usd.close.year1.collect_or_default(),
"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(),
_ => unreachable!(),
}
}
@@ -42,7 +42,7 @@ impl Vecs {
}
let h2d = &indexes.height.day1;
let closes: Vec<Dollars> = prices.usd.close.day1.collect_or_default();
let closes: Vec<Dollars> = prices.usd.split.close.day1.collect_or_default();
for (ema, period) in [
(&mut self.price_1w_ema, 7),
@@ -98,18 +98,15 @@ impl Vecs {
first_txinindex_data[batch_end_height.to_usize() + 1 - offset].to_usize()
};
// Collect and process txins
// Stream txins directly into pairs — avoids intermediate Vec allocation
pairs.clear();
let txoutindexes: Vec<TxOutIndex> = txinindex_to_txoutindex.collect_range_at(txin_start, txin_end);
for (j, txoutindex) in txoutindexes.into_iter().enumerate() {
let txinindex = TxInIndex::from(txin_start + j);
if txoutindex.is_coinbase() {
continue;
let mut j = txin_start;
txinindex_to_txoutindex.for_each_range_at(txin_start, txin_end, |txoutindex: TxOutIndex| {
if !txoutindex.is_coinbase() {
pairs.push((txoutindex, TxInIndex::from(j)));
}
pairs.push((txoutindex, txinindex));
}
j += 1;
});
pairs.sort_unstable_by_key(|(txoutindex, _)| *txoutindex);
+21 -13
View File
@@ -19,12 +19,24 @@ impl Vecs {
exit: &Exit,
) -> Result<()> {
self.compute_prices(indexer, starting_indexes, exit)?;
self.open
self.split
.open
.compute_first(starting_indexes, &self.price, indexes, exit)?;
self.high
self.split
.high
.compute_max(starting_indexes, &self.price, indexes, exit)?;
self.low
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(())
}
@@ -132,6 +144,10 @@ impl Vecs {
// 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
@@ -156,16 +172,8 @@ impl Vecs {
.unwrap_or(TxOutIndex::from(total_outputs))
.to_usize();
let values: Vec<Sats> = indexer
.vecs
.outputs
.value
.collect_range_at(out_start, out_end);
let output_types: Vec<OutputType> = indexer
.vecs
.outputs
.outputtype
.collect_range_at(out_start, out_end);
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() {
+11 -7
View File
@@ -5,6 +5,7 @@ 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(
@@ -16,23 +17,26 @@ impl Vecs {
let price = PcoVec::forced_import(db, "price_cents", version)?;
let open = EagerIndexes::forced_import(db, "price_cents_open", version)?;
let high = EagerIndexes::forced_import(db, "price_cents_high", version)?;
let low = EagerIndexes::forced_import(db, "price_cents_low", 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_cents_close",
"price_close_cents",
price.read_only_boxed_clone(),
version,
indexes,
);
Ok(Self {
price,
let split = SplitOhlc {
open,
high,
low,
close,
})
};
let ohlc = OhlcVecs::forced_import(db, "price_ohlc_cents", version)?;
Ok(Self { split, ohlc, price })
}
}
+10 -5
View File
@@ -1,14 +1,19 @@
use brk_traversable::Traversable;
use brk_types::{Cents, Height};
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>>,
pub open: EagerIndexes<Cents, M>,
pub high: EagerIndexes<Cents, M>,
pub low: EagerIndexes<Cents, M>,
pub close: ComputedHeightDerivedLast<Cents>,
}
+2
View File
@@ -1,4 +1,6 @@
mod compute;
pub(crate) mod ohlcs;
pub(crate) mod split;
pub mod cents;
pub mod sats;
+205
View File
@@ -0,0 +1,205 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{
Cents, Close, Day1, Day3, DifficultyEpoch, HalvingEpoch, High, Hour1, Hour4, Hour12, Low,
Minute1, Minute5, Minute10, Minute30, Month1, Month3, Month6, OHLCCents, Open, Version, Week1,
Year1, Year10,
};
use derive_more::{Deref, DerefMut};
use schemars::JsonSchema;
use serde::Serialize;
use vecdb::{
BytesVec, BytesVecValue, Database, EagerVec, Exit, Formattable, ImportableVec, LazyVecFrom1,
ReadableCloneableVec, ReadableVec, Rw, StorageMode, UnaryTransform,
};
use crate::{
ComputeIndexes, indexes, indexes_from,
internal::{ComputedHeightDerivedLast, EagerIndexes, Indexes},
};
// ── EagerOhlcIndexes ─────────────────────────────────────────────────
#[derive(Deref, DerefMut, Traversable)]
#[traversable(transparent)]
pub struct OhlcVecs<T, M: StorageMode = Rw>(
#[allow(clippy::type_complexity)]
pub Indexes<
<M as StorageMode>::Stored<EagerVec<BytesVec<Minute1, T>>>,
<M as StorageMode>::Stored<EagerVec<BytesVec<Minute5, T>>>,
<M as StorageMode>::Stored<EagerVec<BytesVec<Minute10, T>>>,
<M as StorageMode>::Stored<EagerVec<BytesVec<Minute30, T>>>,
<M as StorageMode>::Stored<EagerVec<BytesVec<Hour1, T>>>,
<M as StorageMode>::Stored<EagerVec<BytesVec<Hour4, T>>>,
<M as StorageMode>::Stored<EagerVec<BytesVec<Hour12, T>>>,
<M as StorageMode>::Stored<EagerVec<BytesVec<Day1, T>>>,
<M as StorageMode>::Stored<EagerVec<BytesVec<Day3, T>>>,
<M as StorageMode>::Stored<EagerVec<BytesVec<Week1, T>>>,
<M as StorageMode>::Stored<EagerVec<BytesVec<Month1, T>>>,
<M as StorageMode>::Stored<EagerVec<BytesVec<Month3, T>>>,
<M as StorageMode>::Stored<EagerVec<BytesVec<Month6, T>>>,
<M as StorageMode>::Stored<EagerVec<BytesVec<Year1, T>>>,
<M as StorageMode>::Stored<EagerVec<BytesVec<Year10, T>>>,
<M as StorageMode>::Stored<EagerVec<BytesVec<HalvingEpoch, T>>>,
<M as StorageMode>::Stored<EagerVec<BytesVec<DifficultyEpoch, T>>>,
>,
)
where
T: BytesVecValue + Formattable + Serialize + JsonSchema;
const EAGER_VERSION: Version = Version::ZERO;
impl<T> OhlcVecs<T>
where
T: BytesVecValue + Formattable + Serialize + JsonSchema,
{
pub(crate) fn forced_import(db: &Database, name: &str, version: Version) -> Result<Self> {
let v = version + EAGER_VERSION;
macro_rules! period {
($idx:ident) => {
ImportableVec::forced_import(db, &format!("{name}_{}", stringify!($idx)), v)?
};
}
Ok(Self(indexes_from!(period)))
}
}
impl OhlcVecs<OHLCCents> {
#[allow(clippy::too_many_arguments)]
pub(crate) fn compute_from_split(
&mut self,
starting_indexes: &ComputeIndexes,
open: &EagerIndexes<Cents>,
high: &EagerIndexes<Cents>,
low: &EagerIndexes<Cents>,
close: &ComputedHeightDerivedLast<Cents>,
indexes: &indexes::Vecs,
exit: &Exit,
) -> Result<()> {
macro_rules! period {
($field:ident) => {
self.0.$field.compute_transform(
starting_indexes.$field,
&indexes.$field.first_height,
|(idx, _first_h, _)| {
let o = open.$field.collect_one(idx).unwrap_or_default();
let h = high.$field.collect_one(idx).unwrap_or_default();
let l = low.$field.collect_one(idx).unwrap_or_default();
let c = close.$field.collect_one(idx).flatten().unwrap_or_default();
(
idx,
OHLCCents {
open: Open::new(o),
high: High::new(h),
low: Low::new(l),
close: Close::new(c),
},
)
},
exit,
)?;
};
}
macro_rules! epoch {
($field:ident) => {
self.0.$field.compute_transform(
starting_indexes.$field,
&indexes.$field.first_height,
|(idx, _first_h, _)| {
let o = open.$field.collect_one(idx).unwrap_or_default();
let h = high.$field.collect_one(idx).unwrap_or_default();
let l = low.$field.collect_one(idx).unwrap_or_default();
let c = close.$field.collect_one(idx).unwrap_or_default();
(
idx,
OHLCCents {
open: Open::new(o),
high: High::new(h),
low: Low::new(l),
close: Close::new(c),
},
)
},
exit,
)?;
};
}
period!(minute1);
period!(minute5);
period!(minute10);
period!(minute30);
period!(hour1);
period!(hour4);
period!(hour12);
period!(day1);
period!(day3);
period!(week1);
period!(month1);
period!(month3);
period!(month6);
period!(year1);
period!(year10);
epoch!(halvingepoch);
epoch!(difficultyepoch);
Ok(())
}
}
// ── LazyOhlcIndexes ──────────────────────────────────────────────────
#[derive(Clone, Deref, DerefMut, Traversable)]
#[traversable(transparent)]
pub struct LazyOhlcVecs<T, S>(
#[allow(clippy::type_complexity)]
pub Indexes<
LazyVecFrom1<Minute1, T, Minute1, S>,
LazyVecFrom1<Minute5, T, Minute5, S>,
LazyVecFrom1<Minute10, T, Minute10, S>,
LazyVecFrom1<Minute30, T, Minute30, S>,
LazyVecFrom1<Hour1, T, Hour1, S>,
LazyVecFrom1<Hour4, T, Hour4, S>,
LazyVecFrom1<Hour12, T, Hour12, S>,
LazyVecFrom1<Day1, T, Day1, S>,
LazyVecFrom1<Day3, T, Day3, S>,
LazyVecFrom1<Week1, T, Week1, S>,
LazyVecFrom1<Month1, T, Month1, S>,
LazyVecFrom1<Month3, T, Month3, S>,
LazyVecFrom1<Month6, T, Month6, S>,
LazyVecFrom1<Year1, T, Year1, S>,
LazyVecFrom1<Year10, T, Year10, S>,
LazyVecFrom1<HalvingEpoch, T, HalvingEpoch, S>,
LazyVecFrom1<DifficultyEpoch, T, DifficultyEpoch, S>,
>,
)
where
T: BytesVecValue + Formattable + Serialize + JsonSchema,
S: BytesVecValue;
impl<T, S> LazyOhlcVecs<T, S>
where
T: BytesVecValue + Formattable + Serialize + JsonSchema,
S: BytesVecValue + Formattable + Serialize + JsonSchema,
{
pub(crate) fn from_eager_ohlc_indexes<Transform: UnaryTransform<S, T>>(
name: &str,
version: Version,
source: &OhlcVecs<S>,
) -> Self {
macro_rules! period {
($idx:ident) => {
LazyVecFrom1::transformed::<Transform>(
&format!("{name}_{}", stringify!($idx)),
version,
source.$idx.read_only_boxed_clone(),
)
};
}
Self(indexes_from!(period))
}
}
+29 -11
View File
@@ -3,9 +3,10 @@ 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},
internal::{CentsUnsignedToSats, ComputedHeightDerivedLast, LazyEagerIndexes, OhlcCentsToSats},
};
impl Vecs {
@@ -21,26 +22,43 @@ impl Vecs {
);
// Sats are inversely related to cents (sats = 10B/cents), so high↔low are swapped
let open =
LazyEagerIndexes::from_eager_indexes::<CentsUnsignedToSats>("price_sats_open", version, &cents.open);
let high =
LazyEagerIndexes::from_eager_indexes::<CentsUnsignedToSats>("price_sats_high", version, &cents.low);
let low =
LazyEagerIndexes::from_eager_indexes::<CentsUnsignedToSats>("price_sats_low", version, &cents.high);
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_sats_close",
"price_close_sats",
price.read_only_boxed_clone(),
version,
indexes,
);
Self {
price,
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 }
}
}
+10 -5
View File
@@ -1,14 +1,19 @@
use brk_traversable::Traversable;
use brk_types::{Cents, Height, Sats};
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>,
pub open: LazyEagerIndexes<Sats, Cents>,
pub high: LazyEagerIndexes<Sats, Cents>,
pub low: LazyEagerIndexes<Sats, Cents>,
pub close: ComputedHeightDerivedLast<Sats>,
}
+9
View File
@@ -0,0 +1,9 @@
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,
}
+31 -12
View File
@@ -3,9 +3,12 @@ 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},
internal::{
CentsUnsignedToDollars, ComputedHeightDerivedLast, LazyEagerIndexes, OhlcCentsToDollars,
},
};
impl Vecs {
@@ -15,32 +18,48 @@ impl Vecs {
cents: &cents::Vecs,
) -> Self {
let price = LazyVecFrom1::transformed::<CentsUnsignedToDollars>(
"price_usd",
"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_usd_open", version, &cents.open);
let high =
LazyEagerIndexes::from_eager_indexes::<CentsUnsignedToDollars>("price_usd_high", version, &cents.high);
let low =
LazyEagerIndexes::from_eager_indexes::<CentsUnsignedToDollars>("price_usd_low", version, &cents.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_usd_close",
"price_close",
price.read_only_boxed_clone(),
version,
indexes,
);
Self {
price,
let split = SplitOhlc {
open,
high,
low,
close,
}
};
let ohlc = LazyOhlcVecs::from_eager_ohlc_indexes::<OhlcCentsToDollars>(
"price_ohlc",
version,
&cents.ohlc,
);
Self { split, ohlc, price }
}
}
+10 -5
View File
@@ -1,14 +1,19 @@
use brk_traversable::Traversable;
use brk_types::{Cents, Dollars, Height};
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>,
pub open: LazyEagerIndexes<Dollars, Cents>,
pub high: LazyEagerIndexes<Dollars, Cents>,
pub low: LazyEagerIndexes<Dollars, Cents>,
pub close: ComputedHeightDerivedLast<Dollars>,
}
@@ -62,16 +62,17 @@ impl Vecs {
let out_start = first_txoutindex.to_usize();
let out_end = next_first_txoutindex.to_usize();
// Sum opreturn values — batch read both ranges for the block
let values = indexer.vecs.outputs.value.collect_range_at(out_start, out_end);
// Sum opreturn values — fold over both vecs without allocation
let opreturn_value = indexer.vecs.outputs.outputtype.fold_range_at(
out_start, out_end,
(Sats::ZERO, 0_usize),
|(mut sum, idx), ot| {
if ot == OutputType::OpReturn {
sum += values[idx];
}
(sum, idx + 1)
(Sats::ZERO, out_start),
|(sum, vi), ot| {
let new_sum = if ot == OutputType::OpReturn {
sum + indexer.vecs.outputs.value.collect_one_at(vi).unwrap()
} else {
sum
};
(new_sum, vi + 1)
},
).0;
+148 -256
View File
@@ -5,6 +5,77 @@ use vecdb::{
WritableVec,
};
use crate::internal::sliding_window::SlidingWindowSorted;
/// Unified rolling extremum (min or max) from window starts.
///
/// `should_replace` determines whether to evict the deque back:
/// - For min: `|back, new| *back >= *new`
/// - For max: `|back, new| *back <= *new`
pub fn compute_rolling_extremum_from_starts<I, T, A>(
out: &mut EagerVec<PcoVec<I, T>>,
max_from: I,
window_starts: &impl ReadableVec<I, I>,
values: &impl ReadableVec<I, A>,
should_replace: fn(&A, &A) -> bool,
exit: &Exit,
) -> Result<()>
where
I: VecIndex,
T: PcoVecValue + From<A>,
A: VecValue + Ord,
{
out.validate_and_truncate(window_starts.version() + values.version(), max_from)?;
out.repeat_until_complete(exit, |this| {
let skip = this.len();
let mut deque: std::collections::VecDeque<(usize, A)> =
std::collections::VecDeque::new();
let start_offset = if skip > 0 {
window_starts.collect_one_at(skip - 1).unwrap().to_usize()
} else {
0
};
let end = window_starts.len().min(values.len());
let starts_batch = window_starts.collect_range_at(start_offset, end);
let values_batch = values.collect_range_at(start_offset, end);
for (j, (start, value)) in starts_batch.into_iter().zip(values_batch).enumerate() {
let i = start_offset + j;
let start_usize = start.to_usize();
while let Some(&(idx, _)) = deque.front() {
if idx < start_usize {
deque.pop_front();
} else {
break;
}
}
while let Some((_, back)) = deque.back() {
if should_replace(back, &value) {
deque.pop_back();
} else {
break;
}
}
deque.push_back((i, value));
if i >= skip {
let extremum = deque.front().unwrap().1.clone();
this.checked_push_at(i, T::from(extremum))?;
if this.batch_limit_reached() {
break;
}
}
}
Ok(())
})?;
Ok(())
}
pub trait ComputeRollingMinFromStarts<I: VecIndex, T> {
fn compute_rolling_min_from_starts<A>(
&mut self,
@@ -34,56 +105,14 @@ where
A: VecValue + Ord,
T: From<A>,
{
self.validate_computed_version_or_reset(window_starts.version() + values.version())?;
self.truncate_if_needed(max_from)?;
self.repeat_until_complete(exit, |this| {
let skip = this.len();
let mut deque: std::collections::VecDeque<(usize, A)> =
std::collections::VecDeque::new();
let start_offset = if skip > 0 {
window_starts.collect_one_at(skip - 1).unwrap().to_usize()
} else {
0
};
let end = window_starts.len().min(values.len());
let starts_batch = window_starts.collect_range_at(start_offset, end);
let values_batch = values.collect_range_at(start_offset, end);
for (j, (start, value)) in starts_batch.into_iter().zip(values_batch).enumerate() {
let i = start_offset + j;
let start_usize = start.to_usize();
while let Some(&(idx, _)) = deque.front() {
if idx < start_usize {
deque.pop_front();
} else {
break;
}
}
while let Some((_, back)) = deque.back() {
if *back >= value {
deque.pop_back();
} else {
break;
}
}
deque.push_back((i, value));
if i >= skip {
let min_val = deque.front().unwrap().1.clone();
this.checked_push_at(i, T::from(min_val))?;
if this.batch_limit_reached() {
break;
}
}
}
Ok(())
})?;
Ok(())
compute_rolling_extremum_from_starts(
self,
max_from,
window_starts,
values,
|back, new| *back >= *new,
exit,
)
}
}
@@ -116,56 +145,14 @@ where
A: VecValue + Ord,
T: From<A>,
{
self.validate_computed_version_or_reset(window_starts.version() + values.version())?;
self.truncate_if_needed(max_from)?;
self.repeat_until_complete(exit, |this| {
let skip = this.len();
let mut deque: std::collections::VecDeque<(usize, A)> =
std::collections::VecDeque::new();
let start_offset = if skip > 0 {
window_starts.collect_one_at(skip - 1).unwrap().to_usize()
} else {
0
};
let end = window_starts.len().min(values.len());
let starts_batch = window_starts.collect_range_at(start_offset, end);
let values_batch = values.collect_range_at(start_offset, end);
for (j, (start, value)) in starts_batch.into_iter().zip(values_batch).enumerate() {
let i = start_offset + j;
let start_usize = start.to_usize();
while let Some(&(idx, _)) = deque.front() {
if idx < start_usize {
deque.pop_front();
} else {
break;
}
}
while let Some((_, back)) = deque.back() {
if *back <= value {
deque.pop_back();
} else {
break;
}
}
deque.push_back((i, value));
if i >= skip {
let max_val = deque.front().unwrap().1.clone();
this.checked_push_at(i, T::from(max_val))?;
if this.batch_limit_reached() {
break;
}
}
}
Ok(())
})?;
Ok(())
compute_rolling_extremum_from_starts(
self,
max_from,
window_starts,
values,
|back, new| *back <= *new,
exit,
)
}
}
@@ -198,70 +185,47 @@ where
A: VecValue + Copy,
f64: From<A>,
{
self.validate_computed_version_or_reset(window_starts.version() + values.version())?;
self.truncate_if_needed(max_from)?;
self.validate_and_truncate(window_starts.version() + values.version(), max_from)?;
self.repeat_until_complete(exit, |this| {
let skip = this.len();
let end = window_starts.len().min(values.len());
// Only collect the range needed: from window start of previous
// element to end. For incremental (1 block) this is ~window_size
// instead of the full history.
let range_start = if skip > 0 {
window_starts.collect_one_at(skip - 1).unwrap().to_usize()
} else {
0
};
let partial_values: Vec<A> = values.collect_range_at(range_start, end);
let partial_values: Vec<f64> = values
.collect_range_at(range_start, end)
.into_iter()
.map(|a| f64::from(a))
.collect();
let mut sorted: Vec<f64> = Vec::new();
let mut prev_start_usize: usize = range_start;
let capacity = if skip > 0 && skip < end {
let first_start = window_starts.collect_one_at(skip).unwrap().to_usize();
(skip + 1).saturating_sub(first_start)
} else if !partial_values.is_empty() {
partial_values.len().min(1024)
} else {
0
};
let mut window = SlidingWindowSorted::with_capacity(capacity);
// Reconstruct state from historical data
if skip > 0 {
(range_start..skip).for_each(|idx| {
let v = f64::from(partial_values[idx - range_start]);
let pos = sorted
.binary_search_by(|a| {
a.partial_cmp(&v).unwrap_or(std::cmp::Ordering::Equal)
})
.unwrap_or_else(|x| x);
sorted.insert(pos, v);
});
window.reconstruct(&partial_values, range_start, skip);
}
let starts_batch = window_starts.collect_range_at(skip, end);
for (j, start) in starts_batch.into_iter().enumerate() {
let i = skip + j;
let v = f64::from(partial_values[i - range_start]);
let pos = sorted
.binary_search_by(|a| a.partial_cmp(&v).unwrap_or(std::cmp::Ordering::Equal))
.unwrap_or_else(|x| x);
sorted.insert(pos, v);
let v = partial_values[i - range_start];
let start_usize = start.to_usize();
while prev_start_usize < start_usize {
let old = f64::from(partial_values[prev_start_usize - range_start]);
if let Ok(pos) = sorted.binary_search_by(|a| {
a.partial_cmp(&old).unwrap_or(std::cmp::Ordering::Equal)
}) {
sorted.remove(pos);
}
prev_start_usize += 1;
}
let median = if sorted.is_empty() {
0.0
} else if sorted.len().is_multiple_of(2) {
let mid = sorted.len() / 2;
(sorted[mid - 1] + sorted[mid]) / 2.0
} else {
sorted[sorted.len() / 2]
};
window.advance(v, start_usize, &partial_values, range_start);
let median = window.percentile(0.50);
this.checked_push_at(i, T::from(median))?;
if this.batch_limit_reached() {
@@ -278,11 +242,6 @@ where
/// Compute all 8 rolling distribution stats (avg, min, max, p10, p25, median, p75, p90)
/// in a single sorted-vec pass per window.
///
/// Since the percentile pass already sorts data, min = sorted[0], max = sorted[last],
/// and average = running_sum / count — all extracted at negligible extra cost.
/// This replaces 3 separate passes (avg, min, max) + 1 percentile pass = 4 passes
/// with a single unified pass.
#[allow(clippy::too_many_arguments)]
pub fn compute_rolling_distribution_from_starts<I, T, A>(
max_from: I,
@@ -306,34 +265,12 @@ where
{
let version = window_starts.version() + values.version();
average_out.validate_computed_version_or_reset(version)?;
min_out.validate_computed_version_or_reset(version)?;
max_out.validate_computed_version_or_reset(version)?;
p10_out.validate_computed_version_or_reset(version)?;
p25_out.validate_computed_version_or_reset(version)?;
median_out.validate_computed_version_or_reset(version)?;
p75_out.validate_computed_version_or_reset(version)?;
p90_out.validate_computed_version_or_reset(version)?;
for v in [&mut *average_out, &mut *min_out, &mut *max_out, &mut *p10_out, &mut *p25_out, &mut *median_out, &mut *p75_out, &mut *p90_out] {
v.validate_and_truncate(version, max_from)?;
}
average_out.truncate_if_needed(max_from)?;
min_out.truncate_if_needed(max_from)?;
max_out.truncate_if_needed(max_from)?;
p10_out.truncate_if_needed(max_from)?;
p25_out.truncate_if_needed(max_from)?;
median_out.truncate_if_needed(max_from)?;
p75_out.truncate_if_needed(max_from)?;
p90_out.truncate_if_needed(max_from)?;
// All 8 vecs should be at the same length; use min to be safe
let skip = average_out
.len()
.min(min_out.len())
.min(max_out.len())
.min(p10_out.len())
.min(p25_out.len())
.min(median_out.len())
.min(p75_out.len())
.min(p90_out.len());
let skip = [average_out.len(), min_out.len(), max_out.len(), p10_out.len(), p25_out.len(), median_out.len(), p75_out.len(), p90_out.len()]
.into_iter().min().unwrap();
let end = window_starts.len().min(values.len());
if skip >= end {
@@ -345,113 +282,68 @@ where
} else {
0
};
let partial_values: Vec<A> = values.collect_range_at(range_start, end);
let partial_values: Vec<f64> = values
.collect_range_at(range_start, end)
.into_iter()
.map(|a| f64::from(a))
.collect();
let mut sorted: Vec<f64> = Vec::new();
let mut running_sum: f64 = 0.0;
let mut prev_start_usize: usize = range_start;
let capacity = if skip > 0 && skip < end {
let first_start = window_starts.collect_one_at(skip).unwrap().to_usize();
(skip + 1).saturating_sub(first_start)
} else if !partial_values.is_empty() {
partial_values.len().min(1024)
} else {
0
};
let mut window = SlidingWindowSorted::with_capacity(capacity);
// Reconstruct sorted state + running sum from historical data
if skip > 0 {
for idx in range_start..skip {
let v = f64::from(partial_values[idx - range_start]);
running_sum += v;
let pos = sorted
.binary_search_by(|a| a.partial_cmp(&v).unwrap_or(std::cmp::Ordering::Equal))
.unwrap_or_else(|x| x);
sorted.insert(pos, v);
}
window.reconstruct(&partial_values, range_start, skip);
}
let starts_batch = window_starts.collect_range_at(skip, end);
for (j, start) in starts_batch.into_iter().enumerate() {
let i = skip + j;
let v = f64::from(partial_values[i - range_start]);
running_sum += v;
let pos = sorted
.binary_search_by(|a| a.partial_cmp(&v).unwrap_or(std::cmp::Ordering::Equal))
.unwrap_or_else(|x| x);
sorted.insert(pos, v);
let v = partial_values[i - range_start];
let start_usize = start.to_usize();
while prev_start_usize < start_usize {
let old = f64::from(partial_values[prev_start_usize - range_start]);
running_sum -= old;
if let Ok(pos) = sorted
.binary_search_by(|a| a.partial_cmp(&old).unwrap_or(std::cmp::Ordering::Equal))
{
sorted.remove(pos);
}
prev_start_usize += 1;
}
window.advance(v, start_usize, &partial_values, range_start);
let len = sorted.len();
if len == 0 {
if window.is_empty() {
let zero = T::from(0.0);
average_out.checked_push_at(i, zero)?;
min_out.checked_push_at(i, zero)?;
max_out.checked_push_at(i, zero)?;
p10_out.checked_push_at(i, zero)?;
p25_out.checked_push_at(i, zero)?;
median_out.checked_push_at(i, zero)?;
p75_out.checked_push_at(i, zero)?;
p90_out.checked_push_at(i, zero)?;
for v in [&mut *average_out, &mut *min_out, &mut *max_out, &mut *p10_out, &mut *p25_out, &mut *median_out, &mut *p75_out, &mut *p90_out] {
v.checked_push_at(i, zero)?;
}
} else {
average_out.checked_push_at(i, T::from(running_sum / len as f64))?;
min_out.checked_push_at(i, T::from(sorted[0]))?;
max_out.checked_push_at(i, T::from(sorted[len - 1]))?;
p10_out.checked_push_at(i, T::from(percentile_of_sorted(&sorted, 0.10)))?;
p25_out.checked_push_at(i, T::from(percentile_of_sorted(&sorted, 0.25)))?;
median_out.checked_push_at(i, T::from(percentile_of_sorted(&sorted, 0.50)))?;
p75_out.checked_push_at(i, T::from(percentile_of_sorted(&sorted, 0.75)))?;
p90_out.checked_push_at(i, T::from(percentile_of_sorted(&sorted, 0.90)))?;
average_out.checked_push_at(i, T::from(window.average()))?;
min_out.checked_push_at(i, T::from(window.min()))?;
max_out.checked_push_at(i, T::from(window.max()))?;
p10_out.checked_push_at(i, T::from(window.percentile(0.10)))?;
p25_out.checked_push_at(i, T::from(window.percentile(0.25)))?;
median_out.checked_push_at(i, T::from(window.percentile(0.50)))?;
p75_out.checked_push_at(i, T::from(window.percentile(0.75)))?;
p90_out.checked_push_at(i, T::from(window.percentile(0.90)))?;
}
if average_out.batch_limit_reached() {
let _lock = exit.lock();
average_out.write()?;
min_out.write()?;
max_out.write()?;
p10_out.write()?;
p25_out.write()?;
median_out.write()?;
p75_out.write()?;
p90_out.write()?;
for v in [&mut *average_out, &mut *min_out, &mut *max_out, &mut *p10_out, &mut *p25_out, &mut *median_out, &mut *p75_out, &mut *p90_out] {
v.write()?;
}
}
}
// Final flush
let _lock = exit.lock();
average_out.write()?;
min_out.write()?;
max_out.write()?;
p10_out.write()?;
p25_out.write()?;
median_out.write()?;
p75_out.write()?;
p90_out.write()?;
for v in [average_out, min_out, max_out, p10_out, p25_out, median_out, p75_out, p90_out] {
v.write()?;
}
Ok(())
}
/// Extract a percentile (0.0-1.0) from a sorted slice using linear interpolation.
fn percentile_of_sorted(sorted: &[f64], p: f64) -> f64 {
let len = sorted.len();
if len == 1 {
return sorted[0];
}
let rank = p * (len - 1) as f64;
let lo = rank.floor() as usize;
let hi = rank.ceil() as usize;
if lo == hi {
sorted[lo]
} else {
let frac = rank - lo as f64;
sorted[lo] * (1.0 - frac) + sorted[hi] * frac
}
}
pub trait ComputeDrawdown<I: VecIndex> {
fn compute_drawdown<C, A>(
&mut self,
-47
View File
@@ -1,47 +0,0 @@
use std::ops::{Add, Div};
/// Extension trait for Option to provide shorter unwrap methods
pub trait OptionExt<T> {
/// Shorthand for `.as_ref().unwrap()`
fn u(&self) -> &T;
/// Shorthand for `.as_mut().unwrap()`
fn um(&mut self) -> &mut T;
}
impl<T> OptionExt<T> for Option<T> {
#[inline]
fn u(&self) -> &T {
self.as_ref().unwrap()
}
#[inline]
fn um(&mut self) -> &mut T {
self.as_mut().unwrap()
}
}
pub(crate) fn get_percentile<T>(sorted: &[T], percentile: f64) -> T
where
T: Clone + Div<usize, Output = T> + Add<T, Output = T>,
{
let len = sorted.len();
if len == 0 {
panic!();
} else if len == 1 {
sorted[0].clone()
} else {
let index = (len - 1) as f64 * percentile;
let fract = index.fract();
if fract != 0.0 {
let left = sorted.get(index as usize).unwrap().clone();
let right = sorted.get(index.ceil() as usize).unwrap().clone();
left / 2 + right / 2
} else {
// dbg!(sorted.len(), index);
sorted.get(index as usize).unwrap().clone()
}
}
}
+1
View File
@@ -85,6 +85,7 @@ impl Query {
let price = &self.computer().prices;
let spot = price
.cents
.split
.close
.day1
.collect_one_flat(day1)
+105 -105
View File
@@ -2763,7 +2763,7 @@ function createGreedInvestedInvestorNegNetPainSupplyTotalUnrealizedPattern(clien
/**
* @typedef {Object} BlocksCoinbaseDaysDominanceFeeSubsidyPattern
* @property {CumulativeHeightRollingPattern<StoredU32>} blocksMined
* @property {CumulativeHeightSumPattern<StoredU32>} blocksMined
* @property {MetricPattern1<StoredU32>} blocksMined1mSum
* @property {MetricPattern1<StoredU32>} blocksMined1wSum
* @property {MetricPattern1<StoredU32>} blocksMined1ySum
@@ -2788,7 +2788,7 @@ function createGreedInvestedInvestorNegNetPainSupplyTotalUnrealizedPattern(clien
*/
function createBlocksCoinbaseDaysDominanceFeeSubsidyPattern(client, acc) {
return {
blocksMined: createCumulativeHeightRollingPattern(client, _m(acc, 'blocks_mined')),
blocksMined: createCumulativeHeightSumPattern(client, _m(acc, 'blocks_mined')),
blocksMined1mSum: createMetricPattern1(client, _m(acc, 'blocks_mined_1m_sum')),
blocksMined1wSum: createMetricPattern1(client, _m(acc, 'blocks_mined_1w_sum')),
blocksMined1ySum: createMetricPattern1(client, _m(acc, 'blocks_mined_1y_sum')),
@@ -3455,8 +3455,8 @@ function createBalanceBothReactivatedReceivingSendingPattern(client, acc) {
/**
* @typedef {Object} CoinblocksCoindaysSatblocksSatdaysSentPattern
* @property {CumulativeHeightRollingPattern<StoredF64>} coinblocksDestroyed
* @property {CumulativeHeightRollingPattern<StoredF64>} coindaysDestroyed
* @property {CumulativeHeightSumPattern<StoredF64>} coinblocksDestroyed
* @property {CumulativeHeightSumPattern<StoredF64>} coindaysDestroyed
* @property {MetricPattern20<Sats>} satblocksDestroyed
* @property {MetricPattern20<Sats>} satdaysDestroyed
* @property {BtcSatsUsdPattern2} sent
@@ -3471,8 +3471,8 @@ function createBalanceBothReactivatedReceivingSendingPattern(client, acc) {
*/
function createCoinblocksCoindaysSatblocksSatdaysSentPattern(client, acc) {
return {
coinblocksDestroyed: createCumulativeHeightRollingPattern(client, _m(acc, 'coinblocks_destroyed')),
coindaysDestroyed: createCumulativeHeightRollingPattern(client, _m(acc, 'coindays_destroyed')),
coinblocksDestroyed: createCumulativeHeightSumPattern(client, _m(acc, 'coinblocks_destroyed')),
coindaysDestroyed: createCumulativeHeightSumPattern(client, _m(acc, 'coindays_destroyed')),
satblocksDestroyed: createMetricPattern20(client, _m(acc, 'satblocks_destroyed')),
satdaysDestroyed: createMetricPattern20(client, _m(acc, 'satdays_destroyed')),
sent: createBtcSatsUsdPattern2(client, _m(acc, 'sent')),
@@ -3673,33 +3673,12 @@ function createBtcSatsUsdPattern2(client, acc) {
}
/**
* @typedef {Object} BtcSatsUsdPattern4
* @typedef {Object} BtcSatsUsdPattern3
* @property {MetricPattern1<Bitcoin>} btc
* @property {CumulativeHeightRollingPattern<Sats>} sats
* @property {CumulativeHeightRollingPattern<Dollars>} usd
*/
/**
* Create a BtcSatsUsdPattern4 pattern node
* @param {BrkClientBase} client
* @param {string} acc - Accumulated metric name
* @returns {BtcSatsUsdPattern4}
*/
function createBtcSatsUsdPattern4(client, acc) {
return {
btc: createMetricPattern1(client, _m(acc, 'btc')),
sats: createCumulativeHeightRollingPattern(client, acc),
usd: createCumulativeHeightRollingPattern(client, _m(acc, 'usd')),
};
}
/**
* @typedef {Object} BtcSatsUsdPattern3
* @property {MetricPattern1<Bitcoin>} btc
* @property {CumulativeHeightRollingPattern2<Sats>} sats
* @property {CumulativeHeightRollingPattern2<Dollars>} usd
*/
/**
* Create a BtcSatsUsdPattern3 pattern node
* @param {BrkClientBase} client
@@ -3709,8 +3688,29 @@ function createBtcSatsUsdPattern4(client, acc) {
function createBtcSatsUsdPattern3(client, acc) {
return {
btc: createMetricPattern1(client, _m(acc, 'btc')),
sats: createCumulativeHeightRollingPattern2(client, acc),
usd: createCumulativeHeightRollingPattern2(client, _m(acc, 'usd')),
sats: createCumulativeHeightRollingPattern(client, acc),
usd: createCumulativeHeightRollingPattern(client, _m(acc, 'usd')),
};
}
/**
* @typedef {Object} BtcSatsUsdPattern4
* @property {MetricPattern1<Bitcoin>} btc
* @property {CumulativeHeightSumPattern<Sats>} sats
* @property {CumulativeHeightSumPattern<Dollars>} usd
*/
/**
* Create a BtcSatsUsdPattern4 pattern node
* @param {BrkClientBase} client
* @param {string} acc - Accumulated metric name
* @returns {BtcSatsUsdPattern4}
*/
function createBtcSatsUsdPattern4(client, acc) {
return {
btc: createMetricPattern1(client, _m(acc, 'btc')),
sats: createCumulativeHeightSumPattern(client, acc),
usd: createCumulativeHeightSumPattern(client, _m(acc, 'usd')),
};
}
@@ -3756,35 +3756,12 @@ function createHistogramLineSignalPattern(client, acc) {
};
}
/**
* @template T
* @typedef {Object} CumulativeHeightRollingPattern2
* @property {MetricPattern1<T>} cumulative
* @property {MetricPattern20<T>} height
* @property {AverageMaxMedianMinP10P25P75P90SumPattern} rolling
*/
/**
* Create a CumulativeHeightRollingPattern2 pattern node
* @template T
* @param {BrkClientBase} client
* @param {string} acc - Accumulated metric name
* @returns {CumulativeHeightRollingPattern2<T>}
*/
function createCumulativeHeightRollingPattern2(client, acc) {
return {
cumulative: createMetricPattern1(client, _m(acc, 'cumulative')),
height: createMetricPattern20(client, acc),
rolling: createAverageMaxMedianMinP10P25P75P90SumPattern(client, acc),
};
}
/**
* @template T
* @typedef {Object} CumulativeHeightRollingPattern
* @property {MetricPattern1<T>} cumulative
* @property {MetricPattern20<T>} height
* @property {_1y24h30d7dPattern<T>} rolling
* @property {AverageMaxMedianMinP10P25P75P90SumPattern} rolling
*/
/**
@@ -3798,7 +3775,30 @@ function createCumulativeHeightRollingPattern(client, acc) {
return {
cumulative: createMetricPattern1(client, _m(acc, 'cumulative')),
height: createMetricPattern20(client, acc),
rolling: create_1y24h30d7dPattern(client, acc),
rolling: createAverageMaxMedianMinP10P25P75P90SumPattern(client, acc),
};
}
/**
* @template T
* @typedef {Object} CumulativeHeightSumPattern
* @property {MetricPattern1<T>} cumulative
* @property {MetricPattern20<T>} height
* @property {_1y24h30d7dPattern<T>} sum
*/
/**
* Create a CumulativeHeightSumPattern pattern node
* @template T
* @param {BrkClientBase} client
* @param {string} acc - Accumulated metric name
* @returns {CumulativeHeightSumPattern<T>}
*/
function createCumulativeHeightSumPattern(client, acc) {
return {
cumulative: createMetricPattern1(client, _m(acc, 'cumulative')),
height: createMetricPattern20(client, acc),
sum: create_1y24h30d7dPattern(client, acc),
};
}
@@ -3986,7 +3986,7 @@ function createRatioPattern2(client, acc) {
* @property {MetricsTree_Blocks_Count} count
* @property {AverageHeightMaxMedianMinP10P25P75P90Pattern<Timestamp>} interval
* @property {MetricsTree_Blocks_Halving} halving
* @property {CumulativeHeightRollingPattern2<StoredU64>} vbytes
* @property {CumulativeHeightRollingPattern<StoredU64>} vbytes
* @property {AverageCumulativeMaxMedianMinP10P25P75P90SumPattern} size
* @property {AverageHeightMaxMedianMinP10P25P75P90Pattern<StoredF32>} fullness
*/
@@ -4048,7 +4048,7 @@ function createRatioPattern2(client, acc) {
/**
* @typedef {Object} MetricsTree_Blocks_Count
* @property {MetricPattern1<StoredU64>} blockCountTarget
* @property {CumulativeHeightRollingPattern<StoredU32>} blockCount
* @property {CumulativeHeightSumPattern<StoredU32>} blockCount
* @property {_1y24h30d7dPattern<StoredU32>} blockCountSum
* @property {MetricPattern20<Height>} height1hAgo
* @property {MetricPattern20<Height>} height24hAgo
@@ -4111,7 +4111,7 @@ function createRatioPattern2(client, acc) {
/**
* @typedef {Object} MetricsTree_Transactions_Count
* @property {CumulativeHeightRollingPattern2<StoredU64>} txCount
* @property {CumulativeHeightRollingPattern<StoredU64>} txCount
* @property {MetricPattern21<StoredBool>} isCoinbase
*/
@@ -4131,9 +4131,9 @@ function createRatioPattern2(client, acc) {
/**
* @typedef {Object} MetricsTree_Transactions_Versions
* @property {CumulativeHeightRollingPattern<StoredU64>} v1
* @property {CumulativeHeightRollingPattern<StoredU64>} v2
* @property {CumulativeHeightRollingPattern<StoredU64>} v3
* @property {CumulativeHeightSumPattern<StoredU64>} v1
* @property {CumulativeHeightSumPattern<StoredU64>} v2
* @property {CumulativeHeightSumPattern<StoredU64>} v3
*/
/**
@@ -4221,19 +4221,19 @@ function createRatioPattern2(client, acc) {
/**
* @typedef {Object} MetricsTree_Scripts_Count
* @property {CumulativeHeightRollingPattern<StoredU64>} p2a
* @property {CumulativeHeightRollingPattern<StoredU64>} p2ms
* @property {CumulativeHeightRollingPattern<StoredU64>} p2pk33
* @property {CumulativeHeightRollingPattern<StoredU64>} p2pk65
* @property {CumulativeHeightRollingPattern<StoredU64>} p2pkh
* @property {CumulativeHeightRollingPattern<StoredU64>} p2sh
* @property {CumulativeHeightRollingPattern<StoredU64>} p2tr
* @property {CumulativeHeightRollingPattern<StoredU64>} p2wpkh
* @property {CumulativeHeightRollingPattern<StoredU64>} p2wsh
* @property {CumulativeHeightRollingPattern<StoredU64>} opreturn
* @property {CumulativeHeightRollingPattern<StoredU64>} emptyoutput
* @property {CumulativeHeightRollingPattern<StoredU64>} unknownoutput
* @property {CumulativeHeightRollingPattern<StoredU64>} segwit
* @property {CumulativeHeightSumPattern<StoredU64>} p2a
* @property {CumulativeHeightSumPattern<StoredU64>} p2ms
* @property {CumulativeHeightSumPattern<StoredU64>} p2pk33
* @property {CumulativeHeightSumPattern<StoredU64>} p2pk65
* @property {CumulativeHeightSumPattern<StoredU64>} p2pkh
* @property {CumulativeHeightSumPattern<StoredU64>} p2sh
* @property {CumulativeHeightSumPattern<StoredU64>} p2tr
* @property {CumulativeHeightSumPattern<StoredU64>} p2wpkh
* @property {CumulativeHeightSumPattern<StoredU64>} p2wsh
* @property {CumulativeHeightSumPattern<StoredU64>} opreturn
* @property {CumulativeHeightSumPattern<StoredU64>} emptyoutput
* @property {CumulativeHeightSumPattern<StoredU64>} unknownoutput
* @property {CumulativeHeightSumPattern<StoredU64>} segwit
* @property {MetricPattern1<StoredF32>} taprootAdoption
* @property {MetricPattern1<StoredF32>} segwitAdoption
*/
@@ -4308,8 +4308,8 @@ function createRatioPattern2(client, acc) {
/**
* @typedef {Object} MetricsTree_Cointime_Activity
* @property {CumulativeHeightRollingPattern<StoredF64>} coinblocksCreated
* @property {CumulativeHeightRollingPattern<StoredF64>} coinblocksStored
* @property {CumulativeHeightSumPattern<StoredF64>} coinblocksCreated
* @property {CumulativeHeightSumPattern<StoredF64>} coinblocksStored
* @property {MetricPattern1<StoredF64>} liveliness
* @property {MetricPattern1<StoredF64>} vaultedness
* @property {MetricPattern1<StoredF64>} activityToVaultednessRatio
@@ -4323,10 +4323,10 @@ function createRatioPattern2(client, acc) {
/**
* @typedef {Object} MetricsTree_Cointime_Value
* @property {CumulativeHeightRollingPattern<StoredF64>} cointimeValueDestroyed
* @property {CumulativeHeightRollingPattern<StoredF64>} cointimeValueCreated
* @property {CumulativeHeightRollingPattern<StoredF64>} cointimeValueStored
* @property {CumulativeHeightRollingPattern<StoredF64>} vocdd
* @property {CumulativeHeightSumPattern<StoredF64>} cointimeValueDestroyed
* @property {CumulativeHeightSumPattern<StoredF64>} cointimeValueCreated
* @property {CumulativeHeightSumPattern<StoredF64>} cointimeValueStored
* @property {CumulativeHeightSumPattern<StoredF64>} vocdd
*/
/**
@@ -6576,7 +6576,7 @@ class BrkClient extends BrkClientBase {
},
count: {
blockCountTarget: createMetricPattern1(this, 'block_count_target'),
blockCount: createCumulativeHeightRollingPattern(this, 'block_count'),
blockCount: createCumulativeHeightSumPattern(this, 'block_count'),
blockCountSum: create_1y24h30d7dPattern(this, 'block_count_sum'),
height1hAgo: createMetricPattern20(this, 'height_1h_ago'),
height24hAgo: createMetricPattern20(this, 'height_24h_ago'),
@@ -6616,7 +6616,7 @@ class BrkClient extends BrkClientBase {
blocksBeforeNextHalving: createMetricPattern1(this, 'blocks_before_next_halving'),
daysBeforeNextHalving: createMetricPattern1(this, 'days_before_next_halving'),
},
vbytes: createCumulativeHeightRollingPattern2(this, 'block_vbytes'),
vbytes: createCumulativeHeightRollingPattern(this, 'block_vbytes'),
size: createAverageCumulativeMaxMedianMinP10P25P75P90SumPattern(this, 'block_size'),
fullness: createAverageHeightMaxMedianMinP10P25P75P90Pattern(this, 'block_fullness'),
},
@@ -6632,7 +6632,7 @@ class BrkClient extends BrkClientBase {
firstTxinindex: createMetricPattern21(this, 'first_txinindex'),
firstTxoutindex: createMetricPattern21(this, 'first_txoutindex'),
count: {
txCount: createCumulativeHeightRollingPattern2(this, 'tx_count'),
txCount: createCumulativeHeightRollingPattern(this, 'tx_count'),
isCoinbase: createMetricPattern21(this, 'is_coinbase'),
},
size: {
@@ -6646,9 +6646,9 @@ class BrkClient extends BrkClientBase {
feeRate: create_1h24hBlockTxindexPattern(this, 'fee_rate'),
},
versions: {
v1: createCumulativeHeightRollingPattern(this, 'tx_v1'),
v2: createCumulativeHeightRollingPattern(this, 'tx_v2'),
v3: createCumulativeHeightRollingPattern(this, 'tx_v3'),
v1: createCumulativeHeightSumPattern(this, 'tx_v1'),
v2: createCumulativeHeightSumPattern(this, 'tx_v2'),
v3: createCumulativeHeightSumPattern(this, 'tx_v3'),
},
volume: {
sentSum: createBtcRollingSatsUsdPattern(this, 'sent_sum'),
@@ -6713,19 +6713,19 @@ class BrkClient extends BrkClientBase {
p2msToTxindex: createMetricPattern27(this, 'txindex'),
unknownToTxindex: createMetricPattern35(this, 'txindex'),
count: {
p2a: createCumulativeHeightRollingPattern(this, 'p2a_count'),
p2ms: createCumulativeHeightRollingPattern(this, 'p2ms_count'),
p2pk33: createCumulativeHeightRollingPattern(this, 'p2pk33_count'),
p2pk65: createCumulativeHeightRollingPattern(this, 'p2pk65_count'),
p2pkh: createCumulativeHeightRollingPattern(this, 'p2pkh_count'),
p2sh: createCumulativeHeightRollingPattern(this, 'p2sh_count'),
p2tr: createCumulativeHeightRollingPattern(this, 'p2tr_count'),
p2wpkh: createCumulativeHeightRollingPattern(this, 'p2wpkh_count'),
p2wsh: createCumulativeHeightRollingPattern(this, 'p2wsh_count'),
opreturn: createCumulativeHeightRollingPattern(this, 'opreturn_count'),
emptyoutput: createCumulativeHeightRollingPattern(this, 'emptyoutput_count'),
unknownoutput: createCumulativeHeightRollingPattern(this, 'unknownoutput_count'),
segwit: createCumulativeHeightRollingPattern(this, 'segwit_count'),
p2a: createCumulativeHeightSumPattern(this, 'p2a_count'),
p2ms: createCumulativeHeightSumPattern(this, 'p2ms_count'),
p2pk33: createCumulativeHeightSumPattern(this, 'p2pk33_count'),
p2pk65: createCumulativeHeightSumPattern(this, 'p2pk65_count'),
p2pkh: createCumulativeHeightSumPattern(this, 'p2pkh_count'),
p2sh: createCumulativeHeightSumPattern(this, 'p2sh_count'),
p2tr: createCumulativeHeightSumPattern(this, 'p2tr_count'),
p2wpkh: createCumulativeHeightSumPattern(this, 'p2wpkh_count'),
p2wsh: createCumulativeHeightSumPattern(this, 'p2wsh_count'),
opreturn: createCumulativeHeightSumPattern(this, 'opreturn_count'),
emptyoutput: createCumulativeHeightSumPattern(this, 'emptyoutput_count'),
unknownoutput: createCumulativeHeightSumPattern(this, 'unknownoutput_count'),
segwit: createCumulativeHeightSumPattern(this, 'segwit_count'),
taprootAdoption: createMetricPattern1(this, 'taproot_adoption'),
segwitAdoption: createMetricPattern1(this, 'segwit_adoption'),
},
@@ -6777,8 +6777,8 @@ class BrkClient extends BrkClientBase {
},
cointime: {
activity: {
coinblocksCreated: createCumulativeHeightRollingPattern(this, 'coinblocks_created'),
coinblocksStored: createCumulativeHeightRollingPattern(this, 'coinblocks_stored'),
coinblocksCreated: createCumulativeHeightSumPattern(this, 'coinblocks_created'),
coinblocksStored: createCumulativeHeightSumPattern(this, 'coinblocks_stored'),
liveliness: createMetricPattern1(this, 'liveliness'),
vaultedness: createMetricPattern1(this, 'vaultedness'),
activityToVaultednessRatio: createMetricPattern1(this, 'activity_to_vaultedness_ratio'),
@@ -6788,10 +6788,10 @@ class BrkClient extends BrkClientBase {
activeSupply: createBtcSatsUsdPattern(this, 'active_supply'),
},
value: {
cointimeValueDestroyed: createCumulativeHeightRollingPattern(this, 'cointime_value_destroyed'),
cointimeValueCreated: createCumulativeHeightRollingPattern(this, 'cointime_value_created'),
cointimeValueStored: createCumulativeHeightRollingPattern(this, 'cointime_value_stored'),
vocdd: createCumulativeHeightRollingPattern(this, 'vocdd'),
cointimeValueDestroyed: createCumulativeHeightSumPattern(this, 'cointime_value_destroyed'),
cointimeValueCreated: createCumulativeHeightSumPattern(this, 'cointime_value_created'),
cointimeValueStored: createCumulativeHeightSumPattern(this, 'cointime_value_stored'),
vocdd: createCumulativeHeightSumPattern(this, 'vocdd'),
},
cap: {
thermoCap: createMetricPattern1(this, 'thermo_cap'),
+35 -35
View File
@@ -2660,7 +2660,7 @@ class BlocksCoinbaseDaysDominanceFeeSubsidyPattern:
def __init__(self, client: BrkClientBase, acc: str):
"""Create pattern node with accumulated metric name."""
self.blocks_mined: CumulativeHeightRollingPattern[StoredU32] = CumulativeHeightRollingPattern(client, _m(acc, 'blocks_mined'))
self.blocks_mined: CumulativeHeightSumPattern[StoredU32] = CumulativeHeightSumPattern(client, _m(acc, 'blocks_mined'))
self.blocks_mined_1m_sum: MetricPattern1[StoredU32] = MetricPattern1(client, _m(acc, 'blocks_mined_1m_sum'))
self.blocks_mined_1w_sum: MetricPattern1[StoredU32] = MetricPattern1(client, _m(acc, 'blocks_mined_1w_sum'))
self.blocks_mined_1y_sum: MetricPattern1[StoredU32] = MetricPattern1(client, _m(acc, 'blocks_mined_1y_sum'))
@@ -2972,8 +2972,8 @@ class CoinblocksCoindaysSatblocksSatdaysSentPattern:
def __init__(self, client: BrkClientBase, acc: str):
"""Create pattern node with accumulated metric name."""
self.coinblocks_destroyed: CumulativeHeightRollingPattern[StoredF64] = CumulativeHeightRollingPattern(client, _m(acc, 'coinblocks_destroyed'))
self.coindays_destroyed: CumulativeHeightRollingPattern[StoredF64] = CumulativeHeightRollingPattern(client, _m(acc, 'coindays_destroyed'))
self.coinblocks_destroyed: CumulativeHeightSumPattern[StoredF64] = CumulativeHeightSumPattern(client, _m(acc, 'coinblocks_destroyed'))
self.coindays_destroyed: CumulativeHeightSumPattern[StoredF64] = CumulativeHeightSumPattern(client, _m(acc, 'coindays_destroyed'))
self.satblocks_destroyed: MetricPattern20[Sats] = MetricPattern20(client, _m(acc, 'satblocks_destroyed'))
self.satdays_destroyed: MetricPattern20[Sats] = MetricPattern20(client, _m(acc, 'satdays_destroyed'))
self.sent: BtcSatsUsdPattern2 = BtcSatsUsdPattern2(client, _m(acc, 'sent'))
@@ -3060,7 +3060,7 @@ class BtcSatsUsdPattern2:
self.sats: CumulativeHeightPattern[Sats] = CumulativeHeightPattern(client, acc)
self.usd: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'usd'))
class BtcSatsUsdPattern4:
class BtcSatsUsdPattern3:
"""Pattern struct for repeated tree structure."""
def __init__(self, client: BrkClientBase, acc: str):
@@ -3069,14 +3069,14 @@ class BtcSatsUsdPattern4:
self.sats: CumulativeHeightRollingPattern[Sats] = CumulativeHeightRollingPattern(client, acc)
self.usd: CumulativeHeightRollingPattern[Dollars] = CumulativeHeightRollingPattern(client, _m(acc, 'usd'))
class BtcSatsUsdPattern3:
class BtcSatsUsdPattern4:
"""Pattern struct for repeated tree structure."""
def __init__(self, client: BrkClientBase, acc: str):
"""Create pattern node with accumulated metric name."""
self.btc: MetricPattern1[Bitcoin] = MetricPattern1(client, _m(acc, 'btc'))
self.sats: CumulativeHeightRollingPattern2[Sats] = CumulativeHeightRollingPattern2(client, acc)
self.usd: CumulativeHeightRollingPattern2[Dollars] = CumulativeHeightRollingPattern2(client, _m(acc, 'usd'))
self.sats: CumulativeHeightSumPattern[Sats] = CumulativeHeightSumPattern(client, acc)
self.usd: CumulativeHeightSumPattern[Dollars] = CumulativeHeightSumPattern(client, _m(acc, 'usd'))
class BtcSatsUsdPattern:
"""Pattern struct for repeated tree structure."""
@@ -3096,7 +3096,7 @@ class HistogramLineSignalPattern:
self.line: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'line_1y'))
self.signal: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'signal_1y'))
class CumulativeHeightRollingPattern2(Generic[T]):
class CumulativeHeightRollingPattern(Generic[T]):
"""Pattern struct for repeated tree structure."""
def __init__(self, client: BrkClientBase, acc: str):
@@ -3105,14 +3105,14 @@ class CumulativeHeightRollingPattern2(Generic[T]):
self.height: MetricPattern20[T] = MetricPattern20(client, acc)
self.rolling: AverageMaxMedianMinP10P25P75P90SumPattern = AverageMaxMedianMinP10P25P75P90SumPattern(client, acc)
class CumulativeHeightRollingPattern(Generic[T]):
class CumulativeHeightSumPattern(Generic[T]):
"""Pattern struct for repeated tree structure."""
def __init__(self, client: BrkClientBase, acc: str):
"""Create pattern node with accumulated metric name."""
self.cumulative: MetricPattern1[T] = MetricPattern1(client, _m(acc, 'cumulative'))
self.height: MetricPattern20[T] = MetricPattern20(client, acc)
self.rolling: _1y24h30d7dPattern[T] = _1y24h30d7dPattern(client, acc)
self.sum: _1y24h30d7dPattern[T] = _1y24h30d7dPattern(client, acc)
class _30dCountPattern:
"""Pattern struct for repeated tree structure."""
@@ -3242,7 +3242,7 @@ class MetricsTree_Blocks_Count:
def __init__(self, client: BrkClientBase, base_path: str = ''):
self.block_count_target: MetricPattern1[StoredU64] = MetricPattern1(client, 'block_count_target')
self.block_count: CumulativeHeightRollingPattern[StoredU32] = CumulativeHeightRollingPattern(client, 'block_count')
self.block_count: CumulativeHeightSumPattern[StoredU32] = CumulativeHeightSumPattern(client, 'block_count')
self.block_count_sum: _1y24h30d7dPattern[StoredU32] = _1y24h30d7dPattern(client, 'block_count_sum')
self.height_1h_ago: MetricPattern20[Height] = MetricPattern20(client, 'height_1h_ago')
self.height_24h_ago: MetricPattern20[Height] = MetricPattern20(client, 'height_24h_ago')
@@ -3296,7 +3296,7 @@ class MetricsTree_Blocks:
self.count: MetricsTree_Blocks_Count = MetricsTree_Blocks_Count(client)
self.interval: AverageHeightMaxMedianMinP10P25P75P90Pattern[Timestamp] = AverageHeightMaxMedianMinP10P25P75P90Pattern(client, 'block_interval')
self.halving: MetricsTree_Blocks_Halving = MetricsTree_Blocks_Halving(client)
self.vbytes: CumulativeHeightRollingPattern2[StoredU64] = CumulativeHeightRollingPattern2(client, 'block_vbytes')
self.vbytes: CumulativeHeightRollingPattern[StoredU64] = CumulativeHeightRollingPattern(client, 'block_vbytes')
self.size: AverageCumulativeMaxMedianMinP10P25P75P90SumPattern = AverageCumulativeMaxMedianMinP10P25P75P90SumPattern(client, 'block_size')
self.fullness: AverageHeightMaxMedianMinP10P25P75P90Pattern[StoredF32] = AverageHeightMaxMedianMinP10P25P75P90Pattern(client, 'block_fullness')
@@ -3304,7 +3304,7 @@ class MetricsTree_Transactions_Count:
"""Metrics tree node."""
def __init__(self, client: BrkClientBase, base_path: str = ''):
self.tx_count: CumulativeHeightRollingPattern2[StoredU64] = CumulativeHeightRollingPattern2(client, 'tx_count')
self.tx_count: CumulativeHeightRollingPattern[StoredU64] = CumulativeHeightRollingPattern(client, 'tx_count')
self.is_coinbase: MetricPattern21[StoredBool] = MetricPattern21(client, 'is_coinbase')
class MetricsTree_Transactions_Size:
@@ -3327,9 +3327,9 @@ class MetricsTree_Transactions_Versions:
"""Metrics tree node."""
def __init__(self, client: BrkClientBase, base_path: str = ''):
self.v1: CumulativeHeightRollingPattern[StoredU64] = CumulativeHeightRollingPattern(client, 'tx_v1')
self.v2: CumulativeHeightRollingPattern[StoredU64] = CumulativeHeightRollingPattern(client, 'tx_v2')
self.v3: CumulativeHeightRollingPattern[StoredU64] = CumulativeHeightRollingPattern(client, 'tx_v3')
self.v1: CumulativeHeightSumPattern[StoredU64] = CumulativeHeightSumPattern(client, 'tx_v1')
self.v2: CumulativeHeightSumPattern[StoredU64] = CumulativeHeightSumPattern(client, 'tx_v2')
self.v3: CumulativeHeightSumPattern[StoredU64] = CumulativeHeightSumPattern(client, 'tx_v3')
class MetricsTree_Transactions_Volume:
"""Metrics tree node."""
@@ -3431,19 +3431,19 @@ class MetricsTree_Scripts_Count:
"""Metrics tree node."""
def __init__(self, client: BrkClientBase, base_path: str = ''):
self.p2a: CumulativeHeightRollingPattern[StoredU64] = CumulativeHeightRollingPattern(client, 'p2a_count')
self.p2ms: CumulativeHeightRollingPattern[StoredU64] = CumulativeHeightRollingPattern(client, 'p2ms_count')
self.p2pk33: CumulativeHeightRollingPattern[StoredU64] = CumulativeHeightRollingPattern(client, 'p2pk33_count')
self.p2pk65: CumulativeHeightRollingPattern[StoredU64] = CumulativeHeightRollingPattern(client, 'p2pk65_count')
self.p2pkh: CumulativeHeightRollingPattern[StoredU64] = CumulativeHeightRollingPattern(client, 'p2pkh_count')
self.p2sh: CumulativeHeightRollingPattern[StoredU64] = CumulativeHeightRollingPattern(client, 'p2sh_count')
self.p2tr: CumulativeHeightRollingPattern[StoredU64] = CumulativeHeightRollingPattern(client, 'p2tr_count')
self.p2wpkh: CumulativeHeightRollingPattern[StoredU64] = CumulativeHeightRollingPattern(client, 'p2wpkh_count')
self.p2wsh: CumulativeHeightRollingPattern[StoredU64] = CumulativeHeightRollingPattern(client, 'p2wsh_count')
self.opreturn: CumulativeHeightRollingPattern[StoredU64] = CumulativeHeightRollingPattern(client, 'opreturn_count')
self.emptyoutput: CumulativeHeightRollingPattern[StoredU64] = CumulativeHeightRollingPattern(client, 'emptyoutput_count')
self.unknownoutput: CumulativeHeightRollingPattern[StoredU64] = CumulativeHeightRollingPattern(client, 'unknownoutput_count')
self.segwit: CumulativeHeightRollingPattern[StoredU64] = CumulativeHeightRollingPattern(client, 'segwit_count')
self.p2a: CumulativeHeightSumPattern[StoredU64] = CumulativeHeightSumPattern(client, 'p2a_count')
self.p2ms: CumulativeHeightSumPattern[StoredU64] = CumulativeHeightSumPattern(client, 'p2ms_count')
self.p2pk33: CumulativeHeightSumPattern[StoredU64] = CumulativeHeightSumPattern(client, 'p2pk33_count')
self.p2pk65: CumulativeHeightSumPattern[StoredU64] = CumulativeHeightSumPattern(client, 'p2pk65_count')
self.p2pkh: CumulativeHeightSumPattern[StoredU64] = CumulativeHeightSumPattern(client, 'p2pkh_count')
self.p2sh: CumulativeHeightSumPattern[StoredU64] = CumulativeHeightSumPattern(client, 'p2sh_count')
self.p2tr: CumulativeHeightSumPattern[StoredU64] = CumulativeHeightSumPattern(client, 'p2tr_count')
self.p2wpkh: CumulativeHeightSumPattern[StoredU64] = CumulativeHeightSumPattern(client, 'p2wpkh_count')
self.p2wsh: CumulativeHeightSumPattern[StoredU64] = CumulativeHeightSumPattern(client, 'p2wsh_count')
self.opreturn: CumulativeHeightSumPattern[StoredU64] = CumulativeHeightSumPattern(client, 'opreturn_count')
self.emptyoutput: CumulativeHeightSumPattern[StoredU64] = CumulativeHeightSumPattern(client, 'emptyoutput_count')
self.unknownoutput: CumulativeHeightSumPattern[StoredU64] = CumulativeHeightSumPattern(client, 'unknownoutput_count')
self.segwit: CumulativeHeightSumPattern[StoredU64] = CumulativeHeightSumPattern(client, 'segwit_count')
self.taproot_adoption: MetricPattern1[StoredF32] = MetricPattern1(client, 'taproot_adoption')
self.segwit_adoption: MetricPattern1[StoredF32] = MetricPattern1(client, 'segwit_adoption')
@@ -3528,8 +3528,8 @@ class MetricsTree_Cointime_Activity:
"""Metrics tree node."""
def __init__(self, client: BrkClientBase, base_path: str = ''):
self.coinblocks_created: CumulativeHeightRollingPattern[StoredF64] = CumulativeHeightRollingPattern(client, 'coinblocks_created')
self.coinblocks_stored: CumulativeHeightRollingPattern[StoredF64] = CumulativeHeightRollingPattern(client, 'coinblocks_stored')
self.coinblocks_created: CumulativeHeightSumPattern[StoredF64] = CumulativeHeightSumPattern(client, 'coinblocks_created')
self.coinblocks_stored: CumulativeHeightSumPattern[StoredF64] = CumulativeHeightSumPattern(client, 'coinblocks_stored')
self.liveliness: MetricPattern1[StoredF64] = MetricPattern1(client, 'liveliness')
self.vaultedness: MetricPattern1[StoredF64] = MetricPattern1(client, 'vaultedness')
self.activity_to_vaultedness_ratio: MetricPattern1[StoredF64] = MetricPattern1(client, 'activity_to_vaultedness_ratio')
@@ -3545,10 +3545,10 @@ class MetricsTree_Cointime_Value:
"""Metrics tree node."""
def __init__(self, client: BrkClientBase, base_path: str = ''):
self.cointime_value_destroyed: CumulativeHeightRollingPattern[StoredF64] = CumulativeHeightRollingPattern(client, 'cointime_value_destroyed')
self.cointime_value_created: CumulativeHeightRollingPattern[StoredF64] = CumulativeHeightRollingPattern(client, 'cointime_value_created')
self.cointime_value_stored: CumulativeHeightRollingPattern[StoredF64] = CumulativeHeightRollingPattern(client, 'cointime_value_stored')
self.vocdd: CumulativeHeightRollingPattern[StoredF64] = CumulativeHeightRollingPattern(client, 'vocdd')
self.cointime_value_destroyed: CumulativeHeightSumPattern[StoredF64] = CumulativeHeightSumPattern(client, 'cointime_value_destroyed')
self.cointime_value_created: CumulativeHeightSumPattern[StoredF64] = CumulativeHeightSumPattern(client, 'cointime_value_created')
self.cointime_value_stored: CumulativeHeightSumPattern[StoredF64] = CumulativeHeightSumPattern(client, 'cointime_value_stored')
self.vocdd: CumulativeHeightSumPattern[StoredF64] = CumulativeHeightSumPattern(client, 'vocdd')
class MetricsTree_Cointime_Cap:
"""Metrics tree node."""
+2 -2
View File
@@ -55,7 +55,7 @@ export function buildCohortData() {
name: shortNames.short,
title: shortNames.long,
color: colors.term.short,
tree: utxoCohorts.term.short,
tree: utxoCohorts.sth,
};
const longNames = TERM_NAMES.long;
@@ -63,7 +63,7 @@ export function buildCohortData() {
name: longNames.short,
title: longNames.long,
color: colors.term.long,
tree: utxoCohorts.term.long,
tree: utxoCohorts.lth,
};
// Max age cohorts (up to X time)