mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-24 22:59:58 -07:00
computer: stateful: split common into multiple impl files
This commit is contained in:
@@ -3,7 +3,7 @@ use brk_traversable::{Traversable, TreeNode};
|
||||
use brk_types::{Dollars, Height, Version};
|
||||
use vecdb::{AnyExportableVec, AnyStoredVec, Database, EagerVec, Exit, GenericStoredVec, PcoVec};
|
||||
|
||||
use crate::{Indexes, indexes};
|
||||
use crate::{Indexes, indexes, stateful::Flushable};
|
||||
|
||||
use super::{ComputedVecsFromHeight, Source, VecBuilderOptions};
|
||||
|
||||
@@ -84,7 +84,10 @@ impl PricePercentiles {
|
||||
.and_then(|i| self.vecs[i].as_ref())
|
||||
}
|
||||
|
||||
pub fn safe_flush(&mut self, exit: &Exit) -> Result<()> {
|
||||
}
|
||||
|
||||
impl Flushable for PricePercentiles {
|
||||
fn safe_flush(&mut self, exit: &Exit) -> Result<()> {
|
||||
for vec in self.vecs.iter_mut().flatten() {
|
||||
if let Some(height_vec) = vec.height.as_mut() {
|
||||
height_vec.safe_flush(exit)?;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
1241
crates/brk_computer/src/stateful/common/compute.rs
Normal file
1241
crates/brk_computer/src/stateful/common/compute.rs
Normal file
File diff suppressed because it is too large
Load Diff
914
crates/brk_computer/src/stateful/common/import.rs
Normal file
914
crates/brk_computer/src/stateful/common/import.rs
Normal file
@@ -0,0 +1,914 @@
|
||||
//! Import and validation methods for Vecs.
|
||||
//!
|
||||
//! This module contains methods for:
|
||||
//! - `forced_import`: Creating a new Vecs instance from database
|
||||
//! - `import_state`: Importing state when resuming from checkpoint
|
||||
//! - `validate_computed_versions`: Version validation
|
||||
//! - `min_height_vecs_len`: Finding minimum vector length
|
||||
|
||||
use brk_error::{Error, Result};
|
||||
use brk_grouper::{CohortContext, Filter};
|
||||
use brk_types::{DateIndex, Dollars, Height, Sats, StoredF32, StoredF64, Version};
|
||||
use vecdb::{
|
||||
AnyVec, Database, EagerVec, GenericStoredVec, ImportableVec, IterableCloneableVec, PcoVec,
|
||||
StoredVec, TypedVecIterator,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
grouped::{
|
||||
ComputedHeightValueVecs, ComputedRatioVecsFromDateIndex, ComputedValueVecsFromDateIndex,
|
||||
ComputedValueVecsFromHeight, ComputedVecsFromDateIndex, ComputedVecsFromHeight,
|
||||
PricePercentiles, Source, VecBuilderOptions,
|
||||
},
|
||||
indexes, price,
|
||||
states::CohortState,
|
||||
utils::OptionExt,
|
||||
};
|
||||
|
||||
use super::Vecs;
|
||||
|
||||
impl Vecs {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn forced_import(
|
||||
db: &Database,
|
||||
filter: Filter,
|
||||
context: CohortContext,
|
||||
parent_version: Version,
|
||||
indexes: &indexes::Vecs,
|
||||
price: Option<&price::Vecs>,
|
||||
) -> Result<Self> {
|
||||
let compute_dollars = price.is_some();
|
||||
let extended = filter.is_extended(context);
|
||||
let compute_rel_to_all = filter.compute_rel_to_all();
|
||||
let compute_adjusted = filter.compute_adjusted(context);
|
||||
|
||||
let version = parent_version + Version::ZERO;
|
||||
|
||||
let name_prefix = filter.to_full_name(context);
|
||||
let suffix = |s: &str| {
|
||||
if name_prefix.is_empty() {
|
||||
s.to_string()
|
||||
} else {
|
||||
format!("{name_prefix}_{s}")
|
||||
}
|
||||
};
|
||||
|
||||
// Helper macros for imports
|
||||
macro_rules! eager {
|
||||
($idx:ty, $val:ty, $name:expr, $v:expr) => {
|
||||
EagerVec::<PcoVec<$idx, $val>>::forced_import(db, &suffix($name), version + $v)
|
||||
.unwrap()
|
||||
};
|
||||
}
|
||||
macro_rules! computed_h {
|
||||
($name:expr, $source:expr, $v:expr, $opts:expr $(,)?) => {
|
||||
ComputedVecsFromHeight::forced_import(
|
||||
db,
|
||||
&suffix($name),
|
||||
$source,
|
||||
version + $v,
|
||||
indexes,
|
||||
$opts,
|
||||
)
|
||||
.unwrap()
|
||||
};
|
||||
}
|
||||
macro_rules! computed_di {
|
||||
($name:expr, $source:expr, $v:expr, $opts:expr $(,)?) => {
|
||||
ComputedVecsFromDateIndex::forced_import(
|
||||
db,
|
||||
&suffix($name),
|
||||
$source,
|
||||
version + $v,
|
||||
indexes,
|
||||
$opts,
|
||||
)
|
||||
.unwrap()
|
||||
};
|
||||
}
|
||||
|
||||
// Common version patterns
|
||||
let v0 = Version::ZERO;
|
||||
let v1 = Version::ONE;
|
||||
let v2 = Version::TWO;
|
||||
let v3 = Version::new(3);
|
||||
let last = || VecBuilderOptions::default().add_last();
|
||||
let sum = || VecBuilderOptions::default().add_sum();
|
||||
let sum_cum = || VecBuilderOptions::default().add_sum().add_cumulative();
|
||||
|
||||
// Pre-create dateindex vecs that are used in computed vecs
|
||||
let dateindex_to_supply_in_profit =
|
||||
compute_dollars.then(|| eager!(DateIndex, Sats, "supply_in_profit", v0));
|
||||
let dateindex_to_supply_in_loss =
|
||||
compute_dollars.then(|| eager!(DateIndex, Sats, "supply_in_loss", v0));
|
||||
let dateindex_to_unrealized_profit =
|
||||
compute_dollars.then(|| eager!(DateIndex, Dollars, "unrealized_profit", v0));
|
||||
let dateindex_to_unrealized_loss =
|
||||
compute_dollars.then(|| eager!(DateIndex, Dollars, "unrealized_loss", v0));
|
||||
|
||||
Ok(Self {
|
||||
filter,
|
||||
|
||||
// ==================== SUPPLY & UTXO COUNT ====================
|
||||
height_to_supply: EagerVec::forced_import(db, &suffix("supply"), version + v0)?,
|
||||
height_to_supply_value: ComputedHeightValueVecs::forced_import(
|
||||
db,
|
||||
&suffix("supply"),
|
||||
Source::None,
|
||||
version + v0,
|
||||
compute_dollars,
|
||||
)?,
|
||||
indexes_to_supply: ComputedValueVecsFromDateIndex::forced_import(
|
||||
db,
|
||||
&suffix("supply"),
|
||||
Source::Compute,
|
||||
version + v1,
|
||||
last(),
|
||||
compute_dollars,
|
||||
indexes,
|
||||
)?,
|
||||
height_to_utxo_count: EagerVec::forced_import(db, &suffix("utxo_count"), version + v0)?,
|
||||
indexes_to_utxo_count: computed_h!("utxo_count", Source::None, v0, last()),
|
||||
height_to_supply_half_value: ComputedHeightValueVecs::forced_import(
|
||||
db,
|
||||
&suffix("supply_half"),
|
||||
Source::Compute,
|
||||
version + v0,
|
||||
compute_dollars,
|
||||
)?,
|
||||
indexes_to_supply_half: ComputedValueVecsFromDateIndex::forced_import(
|
||||
db,
|
||||
&suffix("supply_half"),
|
||||
Source::Compute,
|
||||
version + v0,
|
||||
last(),
|
||||
compute_dollars,
|
||||
indexes,
|
||||
)?,
|
||||
|
||||
// ==================== ACTIVITY ====================
|
||||
height_to_sent: EagerVec::forced_import(db, &suffix("sent"), version + v0)?,
|
||||
indexes_to_sent: ComputedValueVecsFromHeight::forced_import(
|
||||
db,
|
||||
&suffix("sent"),
|
||||
Source::Compute,
|
||||
version + v0,
|
||||
sum(),
|
||||
compute_dollars,
|
||||
indexes,
|
||||
)?,
|
||||
height_to_satblocks_destroyed: EagerVec::forced_import(
|
||||
db,
|
||||
&suffix("satblocks_destroyed"),
|
||||
version + v0,
|
||||
)?,
|
||||
height_to_satdays_destroyed: EagerVec::forced_import(
|
||||
db,
|
||||
&suffix("satdays_destroyed"),
|
||||
version + v0,
|
||||
)?,
|
||||
indexes_to_coinblocks_destroyed: computed_h!(
|
||||
"coinblocks_destroyed",
|
||||
Source::Compute,
|
||||
v2,
|
||||
sum_cum(),
|
||||
),
|
||||
indexes_to_coindays_destroyed: computed_h!(
|
||||
"coindays_destroyed",
|
||||
Source::Compute,
|
||||
v2,
|
||||
sum_cum(),
|
||||
),
|
||||
|
||||
// ==================== REALIZED CAP & PRICE ====================
|
||||
height_to_realized_cap: compute_dollars
|
||||
.then(|| eager!(Height, Dollars, "realized_cap", v0)),
|
||||
indexes_to_realized_cap: compute_dollars
|
||||
.then(|| computed_h!("realized_cap", Source::None, v0, last())),
|
||||
indexes_to_realized_price: compute_dollars
|
||||
.then(|| computed_h!("realized_price", Source::Compute, v0, last())),
|
||||
indexes_to_realized_price_extra: compute_dollars.then(|| {
|
||||
ComputedRatioVecsFromDateIndex::forced_import(
|
||||
db,
|
||||
&suffix("realized_price"),
|
||||
Source::None,
|
||||
version + v0,
|
||||
indexes,
|
||||
extended,
|
||||
)
|
||||
.unwrap()
|
||||
}),
|
||||
indexes_to_realized_cap_rel_to_own_market_cap: (compute_dollars && extended).then(
|
||||
|| {
|
||||
computed_h!(
|
||||
"realized_cap_rel_to_own_market_cap",
|
||||
Source::Compute,
|
||||
v0,
|
||||
last()
|
||||
)
|
||||
},
|
||||
),
|
||||
indexes_to_realized_cap_30d_delta: compute_dollars
|
||||
.then(|| computed_di!("realized_cap_30d_delta", Source::Compute, v0, last())),
|
||||
|
||||
// ==================== REALIZED PROFIT & LOSS ====================
|
||||
height_to_realized_profit: compute_dollars
|
||||
.then(|| eager!(Height, Dollars, "realized_profit", v0)),
|
||||
indexes_to_realized_profit: compute_dollars
|
||||
.then(|| computed_h!("realized_profit", Source::None, v0, sum_cum())),
|
||||
height_to_realized_loss: compute_dollars
|
||||
.then(|| eager!(Height, Dollars, "realized_loss", v0)),
|
||||
indexes_to_realized_loss: compute_dollars
|
||||
.then(|| computed_h!("realized_loss", Source::None, v0, sum_cum())),
|
||||
indexes_to_neg_realized_loss: compute_dollars
|
||||
.then(|| computed_h!("neg_realized_loss", Source::Compute, v1, sum_cum())),
|
||||
indexes_to_net_realized_pnl: compute_dollars
|
||||
.then(|| computed_h!("net_realized_pnl", Source::Compute, v0, sum_cum())),
|
||||
indexes_to_realized_value: compute_dollars
|
||||
.then(|| computed_h!("realized_value", Source::Compute, v0, sum())),
|
||||
indexes_to_realized_profit_rel_to_realized_cap: compute_dollars.then(|| {
|
||||
computed_h!(
|
||||
"realized_profit_rel_to_realized_cap",
|
||||
Source::Compute,
|
||||
v0,
|
||||
sum()
|
||||
)
|
||||
}),
|
||||
indexes_to_realized_loss_rel_to_realized_cap: compute_dollars.then(|| {
|
||||
computed_h!(
|
||||
"realized_loss_rel_to_realized_cap",
|
||||
Source::Compute,
|
||||
v0,
|
||||
sum()
|
||||
)
|
||||
}),
|
||||
indexes_to_net_realized_pnl_rel_to_realized_cap: compute_dollars.then(|| {
|
||||
computed_h!(
|
||||
"net_realized_pnl_rel_to_realized_cap",
|
||||
Source::Compute,
|
||||
v1,
|
||||
sum()
|
||||
)
|
||||
}),
|
||||
height_to_total_realized_pnl: compute_dollars
|
||||
.then(|| eager!(Height, Dollars, "total_realized_pnl", v0)),
|
||||
indexes_to_total_realized_pnl: compute_dollars
|
||||
.then(|| computed_di!("total_realized_pnl", Source::Compute, v1, sum())),
|
||||
dateindex_to_realized_profit_to_loss_ratio: (compute_dollars && extended)
|
||||
.then(|| eager!(DateIndex, StoredF64, "realized_profit_to_loss_ratio", v1)),
|
||||
|
||||
// ==================== VALUE CREATED & DESTROYED ====================
|
||||
height_to_value_created: compute_dollars
|
||||
.then(|| eager!(Height, Dollars, "value_created", v0)),
|
||||
indexes_to_value_created: compute_dollars
|
||||
.then(|| computed_h!("value_created", Source::None, v0, sum())),
|
||||
height_to_value_destroyed: compute_dollars
|
||||
.then(|| eager!(Height, Dollars, "value_destroyed", v0)),
|
||||
indexes_to_value_destroyed: compute_dollars
|
||||
.then(|| computed_h!("value_destroyed", Source::None, v0, sum())),
|
||||
height_to_adjusted_value_created: (compute_dollars && compute_adjusted)
|
||||
.then(|| eager!(Height, Dollars, "adjusted_value_created", v0)),
|
||||
indexes_to_adjusted_value_created: (compute_dollars && compute_adjusted)
|
||||
.then(|| computed_h!("adjusted_value_created", Source::None, v0, sum())),
|
||||
height_to_adjusted_value_destroyed: (compute_dollars && compute_adjusted)
|
||||
.then(|| eager!(Height, Dollars, "adjusted_value_destroyed", v0)),
|
||||
indexes_to_adjusted_value_destroyed: (compute_dollars && compute_adjusted)
|
||||
.then(|| computed_h!("adjusted_value_destroyed", Source::None, v0, sum())),
|
||||
|
||||
// ==================== SOPR ====================
|
||||
dateindex_to_sopr: compute_dollars.then(|| eager!(DateIndex, StoredF64, "sopr", v1)),
|
||||
dateindex_to_sopr_7d_ema: compute_dollars
|
||||
.then(|| eager!(DateIndex, StoredF64, "sopr_7d_ema", v1)),
|
||||
dateindex_to_sopr_30d_ema: compute_dollars
|
||||
.then(|| eager!(DateIndex, StoredF64, "sopr_30d_ema", v1)),
|
||||
dateindex_to_adjusted_sopr: (compute_dollars && compute_adjusted)
|
||||
.then(|| eager!(DateIndex, StoredF64, "adjusted_sopr", v1)),
|
||||
dateindex_to_adjusted_sopr_7d_ema: (compute_dollars && compute_adjusted)
|
||||
.then(|| eager!(DateIndex, StoredF64, "adjusted_sopr_7d_ema", v1)),
|
||||
dateindex_to_adjusted_sopr_30d_ema: (compute_dollars && compute_adjusted)
|
||||
.then(|| eager!(DateIndex, StoredF64, "adjusted_sopr_30d_ema", v1)),
|
||||
|
||||
// ==================== SELL SIDE RISK ====================
|
||||
dateindex_to_sell_side_risk_ratio: compute_dollars
|
||||
.then(|| eager!(DateIndex, StoredF32, "sell_side_risk_ratio", v1)),
|
||||
dateindex_to_sell_side_risk_ratio_7d_ema: compute_dollars
|
||||
.then(|| eager!(DateIndex, StoredF32, "sell_side_risk_ratio_7d_ema", v1)),
|
||||
dateindex_to_sell_side_risk_ratio_30d_ema: compute_dollars
|
||||
.then(|| eager!(DateIndex, StoredF32, "sell_side_risk_ratio_30d_ema", v1)),
|
||||
|
||||
// ==================== SUPPLY IN PROFIT/LOSS ====================
|
||||
height_to_supply_in_profit: compute_dollars
|
||||
.then(|| eager!(Height, Sats, "supply_in_profit", v0)),
|
||||
indexes_to_supply_in_profit: compute_dollars.then(|| {
|
||||
ComputedValueVecsFromDateIndex::forced_import(
|
||||
db,
|
||||
&suffix("supply_in_profit"),
|
||||
dateindex_to_supply_in_profit
|
||||
.as_ref()
|
||||
.map(|v| v.boxed_clone())
|
||||
.into(),
|
||||
version + v0,
|
||||
last(),
|
||||
compute_dollars,
|
||||
indexes,
|
||||
)
|
||||
.unwrap()
|
||||
}),
|
||||
height_to_supply_in_loss: compute_dollars
|
||||
.then(|| eager!(Height, Sats, "supply_in_loss", v0)),
|
||||
indexes_to_supply_in_loss: compute_dollars.then(|| {
|
||||
ComputedValueVecsFromDateIndex::forced_import(
|
||||
db,
|
||||
&suffix("supply_in_loss"),
|
||||
dateindex_to_supply_in_loss
|
||||
.as_ref()
|
||||
.map(|v| v.boxed_clone())
|
||||
.into(),
|
||||
version + v0,
|
||||
last(),
|
||||
compute_dollars,
|
||||
indexes,
|
||||
)
|
||||
.unwrap()
|
||||
}),
|
||||
dateindex_to_supply_in_profit,
|
||||
dateindex_to_supply_in_loss,
|
||||
height_to_supply_in_profit_value: compute_dollars.then(|| {
|
||||
ComputedHeightValueVecs::forced_import(
|
||||
db,
|
||||
&suffix("supply_in_profit"),
|
||||
Source::None,
|
||||
version + v0,
|
||||
compute_dollars,
|
||||
)
|
||||
.unwrap()
|
||||
}),
|
||||
height_to_supply_in_loss_value: compute_dollars.then(|| {
|
||||
ComputedHeightValueVecs::forced_import(
|
||||
db,
|
||||
&suffix("supply_in_loss"),
|
||||
Source::None,
|
||||
version + v0,
|
||||
compute_dollars,
|
||||
)
|
||||
.unwrap()
|
||||
}),
|
||||
|
||||
// ==================== UNREALIZED PROFIT & LOSS ====================
|
||||
height_to_unrealized_profit: compute_dollars
|
||||
.then(|| eager!(Height, Dollars, "unrealized_profit", v0)),
|
||||
indexes_to_unrealized_profit: compute_dollars.then(|| {
|
||||
ComputedVecsFromDateIndex::forced_import(
|
||||
db,
|
||||
&suffix("unrealized_profit"),
|
||||
dateindex_to_unrealized_profit
|
||||
.as_ref()
|
||||
.map(|v| v.boxed_clone())
|
||||
.into(),
|
||||
version + v0,
|
||||
indexes,
|
||||
last(),
|
||||
)
|
||||
.unwrap()
|
||||
}),
|
||||
height_to_unrealized_loss: compute_dollars
|
||||
.then(|| eager!(Height, Dollars, "unrealized_loss", v0)),
|
||||
indexes_to_unrealized_loss: compute_dollars.then(|| {
|
||||
ComputedVecsFromDateIndex::forced_import(
|
||||
db,
|
||||
&suffix("unrealized_loss"),
|
||||
dateindex_to_unrealized_loss
|
||||
.as_ref()
|
||||
.map(|v| v.boxed_clone())
|
||||
.into(),
|
||||
version + v0,
|
||||
indexes,
|
||||
last(),
|
||||
)
|
||||
.unwrap()
|
||||
}),
|
||||
dateindex_to_unrealized_profit,
|
||||
dateindex_to_unrealized_loss,
|
||||
height_to_neg_unrealized_loss: compute_dollars
|
||||
.then(|| eager!(Height, Dollars, "neg_unrealized_loss", v0)),
|
||||
indexes_to_neg_unrealized_loss: compute_dollars
|
||||
.then(|| computed_di!("neg_unrealized_loss", Source::Compute, v0, last())),
|
||||
height_to_net_unrealized_pnl: compute_dollars
|
||||
.then(|| eager!(Height, Dollars, "net_unrealized_pnl", v0)),
|
||||
indexes_to_net_unrealized_pnl: compute_dollars
|
||||
.then(|| computed_di!("net_unrealized_pnl", Source::Compute, v0, last())),
|
||||
height_to_total_unrealized_pnl: compute_dollars
|
||||
.then(|| eager!(Height, Dollars, "total_unrealized_pnl", v0)),
|
||||
indexes_to_total_unrealized_pnl: compute_dollars
|
||||
.then(|| computed_di!("total_unrealized_pnl", Source::Compute, v0, last())),
|
||||
|
||||
// ==================== PRICE PAID ====================
|
||||
height_to_min_price_paid: compute_dollars
|
||||
.then(|| eager!(Height, Dollars, "min_price_paid", v0)),
|
||||
indexes_to_min_price_paid: compute_dollars
|
||||
.then(|| computed_h!("min_price_paid", Source::None, v0, last())),
|
||||
height_to_max_price_paid: compute_dollars
|
||||
.then(|| eager!(Height, Dollars, "max_price_paid", v0)),
|
||||
indexes_to_max_price_paid: compute_dollars
|
||||
.then(|| computed_h!("max_price_paid", Source::None, v0, last())),
|
||||
price_percentiles: (compute_dollars && extended).then(|| {
|
||||
PricePercentiles::forced_import(db, &suffix(""), version + v0, indexes, true)
|
||||
.unwrap()
|
||||
}),
|
||||
|
||||
// ==================== RELATIVE METRICS: UNREALIZED vs MARKET CAP ====================
|
||||
height_to_unrealized_profit_rel_to_market_cap: compute_dollars
|
||||
.then(|| eager!(Height, StoredF32, "unrealized_profit_rel_to_market_cap", v0)),
|
||||
height_to_unrealized_loss_rel_to_market_cap: compute_dollars
|
||||
.then(|| eager!(Height, StoredF32, "unrealized_loss_rel_to_market_cap", v0)),
|
||||
height_to_neg_unrealized_loss_rel_to_market_cap: compute_dollars.then(|| {
|
||||
eager!(
|
||||
Height,
|
||||
StoredF32,
|
||||
"neg_unrealized_loss_rel_to_market_cap",
|
||||
v0
|
||||
)
|
||||
}),
|
||||
height_to_net_unrealized_pnl_rel_to_market_cap: compute_dollars.then(|| {
|
||||
eager!(
|
||||
Height,
|
||||
StoredF32,
|
||||
"net_unrealized_pnl_rel_to_market_cap",
|
||||
v1
|
||||
)
|
||||
}),
|
||||
indexes_to_unrealized_profit_rel_to_market_cap: compute_dollars.then(|| {
|
||||
computed_di!(
|
||||
"unrealized_profit_rel_to_market_cap",
|
||||
Source::Compute,
|
||||
v1,
|
||||
last()
|
||||
)
|
||||
}),
|
||||
indexes_to_unrealized_loss_rel_to_market_cap: compute_dollars.then(|| {
|
||||
computed_di!(
|
||||
"unrealized_loss_rel_to_market_cap",
|
||||
Source::Compute,
|
||||
v1,
|
||||
last()
|
||||
)
|
||||
}),
|
||||
indexes_to_neg_unrealized_loss_rel_to_market_cap: compute_dollars.then(|| {
|
||||
computed_di!(
|
||||
"neg_unrealized_loss_rel_to_market_cap",
|
||||
Source::Compute,
|
||||
v1,
|
||||
last()
|
||||
)
|
||||
}),
|
||||
indexes_to_net_unrealized_pnl_rel_to_market_cap: compute_dollars.then(|| {
|
||||
computed_di!(
|
||||
"net_unrealized_pnl_rel_to_market_cap",
|
||||
Source::Compute,
|
||||
v1,
|
||||
last()
|
||||
)
|
||||
}),
|
||||
|
||||
// ==================== RELATIVE METRICS: UNREALIZED vs OWN MARKET CAP ====================
|
||||
height_to_unrealized_profit_rel_to_own_market_cap: (compute_dollars
|
||||
&& extended
|
||||
&& compute_rel_to_all)
|
||||
.then(|| {
|
||||
eager!(
|
||||
Height,
|
||||
StoredF32,
|
||||
"unrealized_profit_rel_to_own_market_cap",
|
||||
v1
|
||||
)
|
||||
}),
|
||||
height_to_unrealized_loss_rel_to_own_market_cap: (compute_dollars
|
||||
&& extended
|
||||
&& compute_rel_to_all)
|
||||
.then(|| {
|
||||
eager!(
|
||||
Height,
|
||||
StoredF32,
|
||||
"unrealized_loss_rel_to_own_market_cap",
|
||||
v1
|
||||
)
|
||||
}),
|
||||
height_to_neg_unrealized_loss_rel_to_own_market_cap: (compute_dollars
|
||||
&& extended
|
||||
&& compute_rel_to_all)
|
||||
.then(|| {
|
||||
eager!(
|
||||
Height,
|
||||
StoredF32,
|
||||
"neg_unrealized_loss_rel_to_own_market_cap",
|
||||
v1
|
||||
)
|
||||
}),
|
||||
height_to_net_unrealized_pnl_rel_to_own_market_cap: (compute_dollars
|
||||
&& extended
|
||||
&& compute_rel_to_all)
|
||||
.then(|| {
|
||||
eager!(
|
||||
Height,
|
||||
StoredF32,
|
||||
"net_unrealized_pnl_rel_to_own_market_cap",
|
||||
v2
|
||||
)
|
||||
}),
|
||||
indexes_to_unrealized_profit_rel_to_own_market_cap: (compute_dollars
|
||||
&& extended
|
||||
&& compute_rel_to_all)
|
||||
.then(|| {
|
||||
computed_di!(
|
||||
"unrealized_profit_rel_to_own_market_cap",
|
||||
Source::Compute,
|
||||
v2,
|
||||
last()
|
||||
)
|
||||
}),
|
||||
indexes_to_unrealized_loss_rel_to_own_market_cap: (compute_dollars
|
||||
&& extended
|
||||
&& compute_rel_to_all)
|
||||
.then(|| {
|
||||
computed_di!(
|
||||
"unrealized_loss_rel_to_own_market_cap",
|
||||
Source::Compute,
|
||||
v2,
|
||||
last()
|
||||
)
|
||||
}),
|
||||
indexes_to_neg_unrealized_loss_rel_to_own_market_cap: (compute_dollars
|
||||
&& extended
|
||||
&& compute_rel_to_all)
|
||||
.then(|| {
|
||||
computed_di!(
|
||||
"neg_unrealized_loss_rel_to_own_market_cap",
|
||||
Source::Compute,
|
||||
v2,
|
||||
last()
|
||||
)
|
||||
}),
|
||||
indexes_to_net_unrealized_pnl_rel_to_own_market_cap: (compute_dollars
|
||||
&& extended
|
||||
&& compute_rel_to_all)
|
||||
.then(|| {
|
||||
computed_di!(
|
||||
"net_unrealized_pnl_rel_to_own_market_cap",
|
||||
Source::Compute,
|
||||
v2,
|
||||
last()
|
||||
)
|
||||
}),
|
||||
|
||||
// ==================== RELATIVE METRICS: UNREALIZED vs OWN TOTAL UNREALIZED ====================
|
||||
height_to_unrealized_profit_rel_to_own_total_unrealized_pnl: (compute_dollars
|
||||
&& extended)
|
||||
.then(|| {
|
||||
eager!(
|
||||
Height,
|
||||
StoredF32,
|
||||
"unrealized_profit_rel_to_own_total_unrealized_pnl",
|
||||
v0
|
||||
)
|
||||
}),
|
||||
height_to_unrealized_loss_rel_to_own_total_unrealized_pnl: (compute_dollars
|
||||
&& extended)
|
||||
.then(|| {
|
||||
eager!(
|
||||
Height,
|
||||
StoredF32,
|
||||
"unrealized_loss_rel_to_own_total_unrealized_pnl",
|
||||
v0
|
||||
)
|
||||
}),
|
||||
height_to_neg_unrealized_loss_rel_to_own_total_unrealized_pnl: (compute_dollars
|
||||
&& extended)
|
||||
.then(|| {
|
||||
eager!(
|
||||
Height,
|
||||
StoredF32,
|
||||
"neg_unrealized_loss_rel_to_own_total_unrealized_pnl",
|
||||
v0
|
||||
)
|
||||
}),
|
||||
height_to_net_unrealized_pnl_rel_to_own_total_unrealized_pnl: (compute_dollars
|
||||
&& extended)
|
||||
.then(|| {
|
||||
eager!(
|
||||
Height,
|
||||
StoredF32,
|
||||
"net_unrealized_pnl_rel_to_own_total_unrealized_pnl",
|
||||
v1
|
||||
)
|
||||
}),
|
||||
indexes_to_unrealized_profit_rel_to_own_total_unrealized_pnl: (compute_dollars
|
||||
&& extended)
|
||||
.then(|| {
|
||||
computed_di!(
|
||||
"unrealized_profit_rel_to_own_total_unrealized_pnl",
|
||||
Source::Compute,
|
||||
v1,
|
||||
last()
|
||||
)
|
||||
}),
|
||||
indexes_to_unrealized_loss_rel_to_own_total_unrealized_pnl: (compute_dollars
|
||||
&& extended)
|
||||
.then(|| {
|
||||
computed_di!(
|
||||
"unrealized_loss_rel_to_own_total_unrealized_pnl",
|
||||
Source::Compute,
|
||||
v1,
|
||||
last()
|
||||
)
|
||||
}),
|
||||
indexes_to_neg_unrealized_loss_rel_to_own_total_unrealized_pnl: (compute_dollars
|
||||
&& extended)
|
||||
.then(|| {
|
||||
computed_di!(
|
||||
"neg_unrealized_loss_rel_to_own_total_unrealized_pnl",
|
||||
Source::Compute,
|
||||
v1,
|
||||
last()
|
||||
)
|
||||
}),
|
||||
indexes_to_net_unrealized_pnl_rel_to_own_total_unrealized_pnl: (compute_dollars
|
||||
&& extended)
|
||||
.then(|| {
|
||||
computed_di!(
|
||||
"net_unrealized_pnl_rel_to_own_total_unrealized_pnl",
|
||||
Source::Compute,
|
||||
v1,
|
||||
last()
|
||||
)
|
||||
}),
|
||||
|
||||
// ==================== RELATIVE METRICS: SUPPLY vs CIRCULATING/OWN ====================
|
||||
indexes_to_supply_rel_to_circulating_supply: compute_rel_to_all.then(|| {
|
||||
computed_h!(
|
||||
"supply_rel_to_circulating_supply",
|
||||
Source::Compute,
|
||||
v1,
|
||||
last()
|
||||
)
|
||||
}),
|
||||
height_to_supply_in_profit_rel_to_own_supply: compute_dollars
|
||||
.then(|| eager!(Height, StoredF64, "supply_in_profit_rel_to_own_supply", v1)),
|
||||
height_to_supply_in_loss_rel_to_own_supply: compute_dollars
|
||||
.then(|| eager!(Height, StoredF64, "supply_in_loss_rel_to_own_supply", v1)),
|
||||
indexes_to_supply_in_profit_rel_to_own_supply: compute_dollars.then(|| {
|
||||
computed_di!(
|
||||
"supply_in_profit_rel_to_own_supply",
|
||||
Source::Compute,
|
||||
v1,
|
||||
last()
|
||||
)
|
||||
}),
|
||||
indexes_to_supply_in_loss_rel_to_own_supply: compute_dollars.then(|| {
|
||||
computed_di!(
|
||||
"supply_in_loss_rel_to_own_supply",
|
||||
Source::Compute,
|
||||
v1,
|
||||
last()
|
||||
)
|
||||
}),
|
||||
height_to_supply_in_profit_rel_to_circulating_supply: (compute_rel_to_all
|
||||
&& compute_dollars)
|
||||
.then(|| {
|
||||
eager!(
|
||||
Height,
|
||||
StoredF64,
|
||||
"supply_in_profit_rel_to_circulating_supply",
|
||||
v1
|
||||
)
|
||||
}),
|
||||
height_to_supply_in_loss_rel_to_circulating_supply: (compute_rel_to_all
|
||||
&& compute_dollars)
|
||||
.then(|| {
|
||||
eager!(
|
||||
Height,
|
||||
StoredF64,
|
||||
"supply_in_loss_rel_to_circulating_supply",
|
||||
v1
|
||||
)
|
||||
}),
|
||||
indexes_to_supply_in_profit_rel_to_circulating_supply: (compute_rel_to_all
|
||||
&& compute_dollars)
|
||||
.then(|| {
|
||||
computed_di!(
|
||||
"supply_in_profit_rel_to_circulating_supply",
|
||||
Source::Compute,
|
||||
v1,
|
||||
last()
|
||||
)
|
||||
}),
|
||||
indexes_to_supply_in_loss_rel_to_circulating_supply: (compute_rel_to_all
|
||||
&& compute_dollars)
|
||||
.then(|| {
|
||||
computed_di!(
|
||||
"supply_in_loss_rel_to_circulating_supply",
|
||||
Source::Compute,
|
||||
v1,
|
||||
last()
|
||||
)
|
||||
}),
|
||||
|
||||
// ==================== NET REALIZED PNL DELTAS ====================
|
||||
indexes_to_net_realized_pnl_cumulative_30d_delta: compute_dollars.then(|| {
|
||||
computed_di!(
|
||||
"net_realized_pnl_cumulative_30d_delta",
|
||||
Source::Compute,
|
||||
v3,
|
||||
last()
|
||||
)
|
||||
}),
|
||||
indexes_to_net_realized_pnl_cumulative_30d_delta_rel_to_realized_cap: compute_dollars
|
||||
.then(|| {
|
||||
computed_di!(
|
||||
"net_realized_pnl_cumulative_30d_delta_rel_to_realized_cap",
|
||||
Source::Compute,
|
||||
v3,
|
||||
last()
|
||||
)
|
||||
}),
|
||||
indexes_to_net_realized_pnl_cumulative_30d_delta_rel_to_market_cap: compute_dollars
|
||||
.then(|| {
|
||||
computed_di!(
|
||||
"net_realized_pnl_cumulative_30d_delta_rel_to_market_cap",
|
||||
Source::Compute,
|
||||
v3,
|
||||
last()
|
||||
)
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the minimum length of all height-indexed vectors.
|
||||
/// Used to determine the starting point for processing.
|
||||
pub fn min_height_vecs_len(&self) -> usize {
|
||||
[
|
||||
self.height_to_supply.len(),
|
||||
self.height_to_utxo_count.len(),
|
||||
self.height_to_realized_cap
|
||||
.as_ref()
|
||||
.map_or(usize::MAX, |v| v.len()),
|
||||
self.height_to_realized_profit
|
||||
.as_ref()
|
||||
.map_or(usize::MAX, |v| v.len()),
|
||||
self.height_to_realized_loss
|
||||
.as_ref()
|
||||
.map_or(usize::MAX, |v| v.len()),
|
||||
self.height_to_value_created
|
||||
.as_ref()
|
||||
.map_or(usize::MAX, |v| v.len()),
|
||||
self.height_to_adjusted_value_created
|
||||
.as_ref()
|
||||
.map_or(usize::MAX, |v| v.len()),
|
||||
self.height_to_value_destroyed
|
||||
.as_ref()
|
||||
.map_or(usize::MAX, |v| v.len()),
|
||||
self.height_to_adjusted_value_destroyed
|
||||
.as_ref()
|
||||
.map_or(usize::MAX, |v| v.len()),
|
||||
self.height_to_supply_in_profit
|
||||
.as_ref()
|
||||
.map_or(usize::MAX, |v| v.len()),
|
||||
self.height_to_supply_in_loss
|
||||
.as_ref()
|
||||
.map_or(usize::MAX, |v| v.len()),
|
||||
self.height_to_unrealized_profit
|
||||
.as_ref()
|
||||
.map_or(usize::MAX, |v| v.len()),
|
||||
self.height_to_unrealized_loss
|
||||
.as_ref()
|
||||
.map_or(usize::MAX, |v| v.len()),
|
||||
self.height_to_min_price_paid
|
||||
.as_ref()
|
||||
.map_or(usize::MAX, |v| v.len()),
|
||||
self.height_to_max_price_paid
|
||||
.as_ref()
|
||||
.map_or(usize::MAX, |v| v.len()),
|
||||
self.height_to_sent.len(),
|
||||
self.height_to_satdays_destroyed.len(),
|
||||
self.height_to_satblocks_destroyed.len(),
|
||||
]
|
||||
.into_iter()
|
||||
.min()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Import state from a checkpoint when resuming processing.
|
||||
/// Returns the next height to process from.
|
||||
pub fn import_state(
|
||||
&mut self,
|
||||
starting_height: Height,
|
||||
state: &mut CohortState,
|
||||
) -> Result<Height> {
|
||||
if let Some(mut prev_height) = starting_height.decremented() {
|
||||
if self.height_to_realized_cap.as_mut().is_some() {
|
||||
prev_height = state.import_at_or_before(prev_height)?;
|
||||
}
|
||||
|
||||
state.supply.value = self.height_to_supply.into_iter().get_unwrap(prev_height);
|
||||
state.supply.utxo_count = *self
|
||||
.height_to_utxo_count
|
||||
.into_iter()
|
||||
.get_unwrap(prev_height);
|
||||
|
||||
if let Some(height_to_realized_cap) = self.height_to_realized_cap.as_mut() {
|
||||
state.realized.um().cap =
|
||||
height_to_realized_cap.into_iter().get_unwrap(prev_height);
|
||||
}
|
||||
|
||||
Ok(prev_height.incremented())
|
||||
} else {
|
||||
Err(Error::Str("Unset"))
|
||||
}
|
||||
}
|
||||
|
||||
/// Validate that all computed versions match expected values, resetting if needed.
|
||||
pub fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> {
|
||||
// Always-present vecs
|
||||
self.height_to_supply.validate_computed_version_or_reset(
|
||||
base_version + self.height_to_supply.inner_version(),
|
||||
)?;
|
||||
self.height_to_utxo_count
|
||||
.validate_computed_version_or_reset(
|
||||
base_version + self.height_to_utxo_count.inner_version(),
|
||||
)?;
|
||||
self.height_to_sent.validate_computed_version_or_reset(
|
||||
base_version + self.height_to_sent.inner_version(),
|
||||
)?;
|
||||
self.height_to_satblocks_destroyed
|
||||
.validate_computed_version_or_reset(
|
||||
base_version + self.height_to_satblocks_destroyed.inner_version(),
|
||||
)?;
|
||||
self.height_to_satdays_destroyed
|
||||
.validate_computed_version_or_reset(
|
||||
base_version + self.height_to_satdays_destroyed.inner_version(),
|
||||
)?;
|
||||
|
||||
// Dollar-dependent vecs
|
||||
if let Some(height_to_realized_cap) = self.height_to_realized_cap.as_mut().as_mut() {
|
||||
height_to_realized_cap.validate_computed_version_or_reset(
|
||||
base_version + height_to_realized_cap.inner_version(),
|
||||
)?;
|
||||
|
||||
Self::validate_optional_vec_version(&mut self.height_to_realized_profit, base_version)?;
|
||||
Self::validate_optional_vec_version(&mut self.height_to_realized_loss, base_version)?;
|
||||
Self::validate_optional_vec_version(&mut self.height_to_value_created, base_version)?;
|
||||
Self::validate_optional_vec_version(&mut self.height_to_value_destroyed, base_version)?;
|
||||
Self::validate_optional_vec_version(
|
||||
&mut self.height_to_supply_in_profit,
|
||||
base_version,
|
||||
)?;
|
||||
Self::validate_optional_vec_version(&mut self.height_to_supply_in_loss, base_version)?;
|
||||
Self::validate_optional_vec_version(
|
||||
&mut self.height_to_unrealized_profit,
|
||||
base_version,
|
||||
)?;
|
||||
Self::validate_optional_vec_version(&mut self.height_to_unrealized_loss, base_version)?;
|
||||
Self::validate_optional_vec_version(
|
||||
&mut self.dateindex_to_supply_in_profit,
|
||||
base_version,
|
||||
)?;
|
||||
Self::validate_optional_vec_version(
|
||||
&mut self.dateindex_to_supply_in_loss,
|
||||
base_version,
|
||||
)?;
|
||||
Self::validate_optional_vec_version(
|
||||
&mut self.dateindex_to_unrealized_profit,
|
||||
base_version,
|
||||
)?;
|
||||
Self::validate_optional_vec_version(
|
||||
&mut self.dateindex_to_unrealized_loss,
|
||||
base_version,
|
||||
)?;
|
||||
Self::validate_optional_vec_version(&mut self.height_to_min_price_paid, base_version)?;
|
||||
Self::validate_optional_vec_version(&mut self.height_to_max_price_paid, base_version)?;
|
||||
|
||||
if self.height_to_adjusted_value_created.is_some() {
|
||||
Self::validate_optional_vec_version(
|
||||
&mut self.height_to_adjusted_value_created,
|
||||
base_version,
|
||||
)?;
|
||||
Self::validate_optional_vec_version(
|
||||
&mut self.height_to_adjusted_value_destroyed,
|
||||
base_version,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Helper to validate an optional vec's version.
|
||||
fn validate_optional_vec_version<V: StoredVec>(
|
||||
vec: &mut Option<EagerVec<V>>,
|
||||
base_version: Version,
|
||||
) -> Result<()> {
|
||||
if let Some(v) = vec.as_mut() {
|
||||
v.validate_computed_version_or_reset(base_version + v.inner_version())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
19
crates/brk_computer/src/stateful/common/mod.rs
Normal file
19
crates/brk_computer/src/stateful/common/mod.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
//! Common vector structs and logic shared between UTXO and Address cohorts.
|
||||
//!
|
||||
//! This module contains the `Vecs` struct which holds all the computed vectors
|
||||
//! for a single cohort, along with methods for importing, flushing, and computing.
|
||||
//!
|
||||
//! ## Module Organization
|
||||
//!
|
||||
//! The implementation is split across multiple files for maintainability:
|
||||
//! - `vecs.rs`: Struct definition with field documentation
|
||||
//! - `import.rs`: Import, validation, and initialization methods
|
||||
//! - `push.rs`: Per-block push and flush methods
|
||||
//! - `compute.rs`: Post-processing computation methods
|
||||
|
||||
mod compute;
|
||||
mod import;
|
||||
mod push;
|
||||
mod vecs;
|
||||
|
||||
pub use vecs::Vecs;
|
||||
178
crates/brk_computer/src/stateful/common/push.rs
Normal file
178
crates/brk_computer/src/stateful/common/push.rs
Normal file
@@ -0,0 +1,178 @@
|
||||
//! Push and flush methods for Vecs.
|
||||
//!
|
||||
//! This module contains methods for:
|
||||
//! - `truncate_push`: Push state values to height-indexed vectors
|
||||
//! - `compute_then_truncate_push_unrealized_states`: Compute and push unrealized states
|
||||
//! - `safe_flush_stateful_vecs`: Safely flush all stateful vectors
|
||||
|
||||
use brk_error::Result;
|
||||
use brk_types::{DateIndex, Dollars, Height, StoredU64};
|
||||
use vecdb::{AnyStoredVec, Exit, GenericStoredVec};
|
||||
|
||||
use crate::{stateful::Flushable, states::CohortState, utils::OptionExt};
|
||||
|
||||
use super::Vecs;
|
||||
|
||||
impl Vecs {
|
||||
pub fn truncate_push(&mut self, height: Height, state: &CohortState) -> Result<()> {
|
||||
self.height_to_supply
|
||||
.truncate_push(height, state.supply.value)?;
|
||||
|
||||
self.height_to_utxo_count
|
||||
.truncate_push(height, StoredU64::from(state.supply.utxo_count))?;
|
||||
|
||||
self.height_to_sent.truncate_push(height, state.sent)?;
|
||||
|
||||
self.height_to_satblocks_destroyed
|
||||
.truncate_push(height, state.satblocks_destroyed)?;
|
||||
|
||||
self.height_to_satdays_destroyed
|
||||
.truncate_push(height, state.satdays_destroyed)?;
|
||||
|
||||
if let Some(height_to_realized_cap) = self.height_to_realized_cap.as_mut() {
|
||||
let realized = state.realized.as_ref().unwrap_or_else(|| {
|
||||
dbg!((&state.realized, &state.supply));
|
||||
panic!();
|
||||
});
|
||||
|
||||
height_to_realized_cap.truncate_push(height, realized.cap)?;
|
||||
|
||||
self.height_to_realized_profit
|
||||
.um()
|
||||
.truncate_push(height, realized.profit)?;
|
||||
self.height_to_realized_loss
|
||||
.um()
|
||||
.truncate_push(height, realized.loss)?;
|
||||
self.height_to_value_created
|
||||
.um()
|
||||
.truncate_push(height, realized.value_created)?;
|
||||
self.height_to_value_destroyed
|
||||
.um()
|
||||
.truncate_push(height, realized.value_destroyed)?;
|
||||
|
||||
if self.height_to_adjusted_value_created.is_some() {
|
||||
self.height_to_adjusted_value_created
|
||||
.um()
|
||||
.truncate_push(height, realized.adj_value_created)?;
|
||||
self.height_to_adjusted_value_destroyed
|
||||
.um()
|
||||
.truncate_push(height, realized.adj_value_destroyed)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn compute_then_truncate_push_unrealized_states(
|
||||
&mut self,
|
||||
height: Height,
|
||||
height_price: Option<Dollars>,
|
||||
dateindex: Option<DateIndex>,
|
||||
date_price: Option<Option<Dollars>>,
|
||||
state: &CohortState,
|
||||
) -> Result<()> {
|
||||
if let Some(height_price) = height_price {
|
||||
self.height_to_min_price_paid.um().truncate_push(
|
||||
height,
|
||||
state
|
||||
.price_to_amount_first_key_value()
|
||||
.map(|(&dollars, _)| dollars)
|
||||
.unwrap_or(Dollars::NAN),
|
||||
)?;
|
||||
self.height_to_max_price_paid.um().truncate_push(
|
||||
height,
|
||||
state
|
||||
.price_to_amount_last_key_value()
|
||||
.map(|(&dollars, _)| dollars)
|
||||
.unwrap_or(Dollars::NAN),
|
||||
)?;
|
||||
|
||||
let (height_unrealized_state, date_unrealized_state) =
|
||||
state.compute_unrealized_states(height_price, date_price.unwrap());
|
||||
|
||||
self.height_to_supply_in_profit
|
||||
.um()
|
||||
.truncate_push(height, height_unrealized_state.supply_in_profit)?;
|
||||
self.height_to_supply_in_loss
|
||||
.um()
|
||||
.truncate_push(height, height_unrealized_state.supply_in_loss)?;
|
||||
self.height_to_unrealized_profit
|
||||
.um()
|
||||
.truncate_push(height, height_unrealized_state.unrealized_profit)?;
|
||||
self.height_to_unrealized_loss
|
||||
.um()
|
||||
.truncate_push(height, height_unrealized_state.unrealized_loss)?;
|
||||
|
||||
if let Some(date_unrealized_state) = date_unrealized_state {
|
||||
let dateindex = dateindex.unwrap();
|
||||
|
||||
self.dateindex_to_supply_in_profit
|
||||
.um()
|
||||
.truncate_push(dateindex, date_unrealized_state.supply_in_profit)?;
|
||||
self.dateindex_to_supply_in_loss
|
||||
.um()
|
||||
.truncate_push(dateindex, date_unrealized_state.supply_in_loss)?;
|
||||
self.dateindex_to_unrealized_profit
|
||||
.um()
|
||||
.truncate_push(dateindex, date_unrealized_state.unrealized_profit)?;
|
||||
self.dateindex_to_unrealized_loss
|
||||
.um()
|
||||
.truncate_push(dateindex, date_unrealized_state.unrealized_loss)?;
|
||||
}
|
||||
|
||||
// Compute and push price percentiles
|
||||
if let Some(price_percentiles) = self.price_percentiles.as_mut() {
|
||||
let percentile_prices = state.compute_percentile_prices();
|
||||
price_percentiles.truncate_push(height, &percentile_prices)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn safe_flush_stateful_vecs(
|
||||
&mut self,
|
||||
height: Height,
|
||||
exit: &Exit,
|
||||
state: &mut CohortState,
|
||||
) -> Result<()> {
|
||||
self.height_to_supply.safe_flush(exit)?;
|
||||
self.height_to_utxo_count.safe_flush(exit)?;
|
||||
self.height_to_sent.safe_flush(exit)?;
|
||||
self.height_to_satdays_destroyed.safe_flush(exit)?;
|
||||
self.height_to_satblocks_destroyed.safe_flush(exit)?;
|
||||
|
||||
if let Some(height_to_realized_cap) = self.height_to_realized_cap.as_mut() {
|
||||
height_to_realized_cap.safe_flush(exit)?;
|
||||
self.height_to_realized_profit.um().safe_flush(exit)?;
|
||||
self.height_to_realized_loss.um().safe_flush(exit)?;
|
||||
self.height_to_value_created.um().safe_flush(exit)?;
|
||||
self.height_to_value_destroyed.um().safe_flush(exit)?;
|
||||
self.height_to_supply_in_profit.um().safe_flush(exit)?;
|
||||
self.height_to_supply_in_loss.um().safe_flush(exit)?;
|
||||
self.height_to_unrealized_profit.um().safe_flush(exit)?;
|
||||
self.height_to_unrealized_loss.um().safe_flush(exit)?;
|
||||
self.dateindex_to_supply_in_profit.um().safe_flush(exit)?;
|
||||
self.dateindex_to_supply_in_loss.um().safe_flush(exit)?;
|
||||
self.dateindex_to_unrealized_profit.um().safe_flush(exit)?;
|
||||
self.dateindex_to_unrealized_loss.um().safe_flush(exit)?;
|
||||
self.height_to_min_price_paid.um().safe_flush(exit)?;
|
||||
self.height_to_max_price_paid.um().safe_flush(exit)?;
|
||||
|
||||
if self.height_to_adjusted_value_created.is_some() {
|
||||
self.height_to_adjusted_value_created
|
||||
.um()
|
||||
.safe_flush(exit)?;
|
||||
self.height_to_adjusted_value_destroyed
|
||||
.um()
|
||||
.safe_flush(exit)?;
|
||||
}
|
||||
|
||||
// Uses Flushable trait - Option<T> impl handles None case
|
||||
self.price_percentiles.safe_flush(exit)?;
|
||||
}
|
||||
|
||||
state.commit(height)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
210
crates/brk_computer/src/stateful/common/vecs.rs
Normal file
210
crates/brk_computer/src/stateful/common/vecs.rs
Normal file
@@ -0,0 +1,210 @@
|
||||
use brk_grouper::Filter;
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{DateIndex, Dollars, Height, Sats, StoredF32, StoredF64, StoredU64};
|
||||
use vecdb::{EagerVec, PcoVec};
|
||||
|
||||
use crate::grouped::{
|
||||
ComputedHeightValueVecs, ComputedRatioVecsFromDateIndex, ComputedValueVecsFromDateIndex,
|
||||
ComputedValueVecsFromHeight, ComputedVecsFromDateIndex, ComputedVecsFromHeight,
|
||||
PricePercentiles,
|
||||
};
|
||||
|
||||
/// Common vectors shared between UTXO and Address cohorts.
|
||||
///
|
||||
/// This struct contains all the computed vectors for a single cohort. The fields are
|
||||
/// organized into logical groups matching the initialization order in `forced_import`.
|
||||
///
|
||||
/// ## Field Groups
|
||||
/// - **Supply & UTXO count**: Basic supply metrics (always computed)
|
||||
/// - **Activity**: Sent amounts, satblocks/satdays destroyed
|
||||
/// - **Realized**: Realized cap, profit/loss, value created/destroyed, SOPR
|
||||
/// - **Unrealized**: Unrealized profit/loss, supply in profit/loss
|
||||
/// - **Price**: Min/max price paid, price percentiles
|
||||
/// - **Relative metrics**: Ratios relative to market cap, realized cap, etc.
|
||||
#[derive(Clone, Traversable)]
|
||||
pub struct Vecs {
|
||||
#[traversable(skip)]
|
||||
pub filter: Filter,
|
||||
|
||||
// ==================== SUPPLY & UTXO COUNT ====================
|
||||
// Always computed - core supply metrics
|
||||
pub height_to_supply: EagerVec<PcoVec<Height, Sats>>,
|
||||
pub height_to_supply_value: ComputedHeightValueVecs,
|
||||
pub indexes_to_supply: ComputedValueVecsFromDateIndex,
|
||||
pub height_to_utxo_count: EagerVec<PcoVec<Height, StoredU64>>,
|
||||
pub indexes_to_utxo_count: ComputedVecsFromHeight<StoredU64>,
|
||||
pub height_to_supply_half_value: ComputedHeightValueVecs,
|
||||
pub indexes_to_supply_half: ComputedValueVecsFromDateIndex,
|
||||
|
||||
// ==================== ACTIVITY ====================
|
||||
// Always computed - transaction activity metrics
|
||||
pub height_to_sent: EagerVec<PcoVec<Height, Sats>>,
|
||||
pub indexes_to_sent: ComputedValueVecsFromHeight,
|
||||
pub height_to_satblocks_destroyed: EagerVec<PcoVec<Height, Sats>>,
|
||||
pub height_to_satdays_destroyed: EagerVec<PcoVec<Height, Sats>>,
|
||||
pub indexes_to_coinblocks_destroyed: ComputedVecsFromHeight<StoredF64>,
|
||||
pub indexes_to_coindays_destroyed: ComputedVecsFromHeight<StoredF64>,
|
||||
|
||||
// ==================== REALIZED CAP & PRICE ====================
|
||||
// Conditional on compute_dollars
|
||||
pub height_to_realized_cap: Option<EagerVec<PcoVec<Height, Dollars>>>,
|
||||
pub indexes_to_realized_cap: Option<ComputedVecsFromHeight<Dollars>>,
|
||||
pub indexes_to_realized_price: Option<ComputedVecsFromHeight<Dollars>>,
|
||||
pub indexes_to_realized_price_extra: Option<ComputedRatioVecsFromDateIndex>,
|
||||
pub indexes_to_realized_cap_rel_to_own_market_cap: Option<ComputedVecsFromHeight<StoredF32>>,
|
||||
pub indexes_to_realized_cap_30d_delta: Option<ComputedVecsFromDateIndex<Dollars>>,
|
||||
|
||||
// ==================== REALIZED PROFIT & LOSS ====================
|
||||
// Conditional on compute_dollars
|
||||
pub height_to_realized_profit: Option<EagerVec<PcoVec<Height, Dollars>>>,
|
||||
pub indexes_to_realized_profit: Option<ComputedVecsFromHeight<Dollars>>,
|
||||
pub height_to_realized_loss: Option<EagerVec<PcoVec<Height, Dollars>>>,
|
||||
pub indexes_to_realized_loss: Option<ComputedVecsFromHeight<Dollars>>,
|
||||
pub indexes_to_neg_realized_loss: Option<ComputedVecsFromHeight<Dollars>>,
|
||||
pub indexes_to_net_realized_pnl: Option<ComputedVecsFromHeight<Dollars>>,
|
||||
pub indexes_to_realized_value: Option<ComputedVecsFromHeight<Dollars>>,
|
||||
pub indexes_to_realized_profit_rel_to_realized_cap: Option<ComputedVecsFromHeight<StoredF32>>,
|
||||
pub indexes_to_realized_loss_rel_to_realized_cap: Option<ComputedVecsFromHeight<StoredF32>>,
|
||||
pub indexes_to_net_realized_pnl_rel_to_realized_cap: Option<ComputedVecsFromHeight<StoredF32>>,
|
||||
pub height_to_total_realized_pnl: Option<EagerVec<PcoVec<Height, Dollars>>>,
|
||||
pub indexes_to_total_realized_pnl: Option<ComputedVecsFromDateIndex<Dollars>>,
|
||||
pub dateindex_to_realized_profit_to_loss_ratio: Option<EagerVec<PcoVec<DateIndex, StoredF64>>>,
|
||||
|
||||
// ==================== VALUE CREATED & DESTROYED ====================
|
||||
// Conditional on compute_dollars
|
||||
pub height_to_value_created: Option<EagerVec<PcoVec<Height, Dollars>>>,
|
||||
pub indexes_to_value_created: Option<ComputedVecsFromHeight<Dollars>>,
|
||||
pub height_to_value_destroyed: Option<EagerVec<PcoVec<Height, Dollars>>>,
|
||||
pub indexes_to_value_destroyed: Option<ComputedVecsFromHeight<Dollars>>,
|
||||
pub height_to_adjusted_value_created: Option<EagerVec<PcoVec<Height, Dollars>>>,
|
||||
pub indexes_to_adjusted_value_created: Option<ComputedVecsFromHeight<Dollars>>,
|
||||
pub height_to_adjusted_value_destroyed: Option<EagerVec<PcoVec<Height, Dollars>>>,
|
||||
pub indexes_to_adjusted_value_destroyed: Option<ComputedVecsFromHeight<Dollars>>,
|
||||
|
||||
// ==================== SOPR ====================
|
||||
// Spent Output Profit Ratio - conditional on compute_dollars
|
||||
pub dateindex_to_sopr: Option<EagerVec<PcoVec<DateIndex, StoredF64>>>,
|
||||
pub dateindex_to_sopr_7d_ema: Option<EagerVec<PcoVec<DateIndex, StoredF64>>>,
|
||||
pub dateindex_to_sopr_30d_ema: Option<EagerVec<PcoVec<DateIndex, StoredF64>>>,
|
||||
pub dateindex_to_adjusted_sopr: Option<EagerVec<PcoVec<DateIndex, StoredF64>>>,
|
||||
pub dateindex_to_adjusted_sopr_7d_ema: Option<EagerVec<PcoVec<DateIndex, StoredF64>>>,
|
||||
pub dateindex_to_adjusted_sopr_30d_ema: Option<EagerVec<PcoVec<DateIndex, StoredF64>>>,
|
||||
|
||||
// ==================== SELL SIDE RISK ====================
|
||||
// Conditional on compute_dollars
|
||||
pub dateindex_to_sell_side_risk_ratio: Option<EagerVec<PcoVec<DateIndex, StoredF32>>>,
|
||||
pub dateindex_to_sell_side_risk_ratio_7d_ema: Option<EagerVec<PcoVec<DateIndex, StoredF32>>>,
|
||||
pub dateindex_to_sell_side_risk_ratio_30d_ema: Option<EagerVec<PcoVec<DateIndex, StoredF32>>>,
|
||||
|
||||
// ==================== SUPPLY IN PROFIT/LOSS ====================
|
||||
// Conditional on compute_dollars
|
||||
pub height_to_supply_in_profit: Option<EagerVec<PcoVec<Height, Sats>>>,
|
||||
pub indexes_to_supply_in_profit: Option<ComputedValueVecsFromDateIndex>,
|
||||
pub height_to_supply_in_loss: Option<EagerVec<PcoVec<Height, Sats>>>,
|
||||
pub indexes_to_supply_in_loss: Option<ComputedValueVecsFromDateIndex>,
|
||||
pub dateindex_to_supply_in_profit: Option<EagerVec<PcoVec<DateIndex, Sats>>>,
|
||||
pub dateindex_to_supply_in_loss: Option<EagerVec<PcoVec<DateIndex, Sats>>>,
|
||||
pub height_to_supply_in_profit_value: Option<ComputedHeightValueVecs>,
|
||||
pub height_to_supply_in_loss_value: Option<ComputedHeightValueVecs>,
|
||||
|
||||
// ==================== UNREALIZED PROFIT & LOSS ====================
|
||||
// Conditional on compute_dollars
|
||||
pub height_to_unrealized_profit: Option<EagerVec<PcoVec<Height, Dollars>>>,
|
||||
pub indexes_to_unrealized_profit: Option<ComputedVecsFromDateIndex<Dollars>>,
|
||||
pub height_to_unrealized_loss: Option<EagerVec<PcoVec<Height, Dollars>>>,
|
||||
pub indexes_to_unrealized_loss: Option<ComputedVecsFromDateIndex<Dollars>>,
|
||||
pub dateindex_to_unrealized_profit: Option<EagerVec<PcoVec<DateIndex, Dollars>>>,
|
||||
pub dateindex_to_unrealized_loss: Option<EagerVec<PcoVec<DateIndex, Dollars>>>,
|
||||
pub height_to_neg_unrealized_loss: Option<EagerVec<PcoVec<Height, Dollars>>>,
|
||||
pub indexes_to_neg_unrealized_loss: Option<ComputedVecsFromDateIndex<Dollars>>,
|
||||
pub height_to_net_unrealized_pnl: Option<EagerVec<PcoVec<Height, Dollars>>>,
|
||||
pub indexes_to_net_unrealized_pnl: Option<ComputedVecsFromDateIndex<Dollars>>,
|
||||
pub height_to_total_unrealized_pnl: Option<EagerVec<PcoVec<Height, Dollars>>>,
|
||||
pub indexes_to_total_unrealized_pnl: Option<ComputedVecsFromDateIndex<Dollars>>,
|
||||
|
||||
// ==================== PRICE PAID ====================
|
||||
// Conditional on compute_dollars
|
||||
pub height_to_min_price_paid: Option<EagerVec<PcoVec<Height, Dollars>>>,
|
||||
pub indexes_to_min_price_paid: Option<ComputedVecsFromHeight<Dollars>>,
|
||||
pub height_to_max_price_paid: Option<EagerVec<PcoVec<Height, Dollars>>>,
|
||||
pub indexes_to_max_price_paid: Option<ComputedVecsFromHeight<Dollars>>,
|
||||
pub price_percentiles: Option<PricePercentiles>,
|
||||
|
||||
// ==================== RELATIVE METRICS: UNREALIZED vs MARKET CAP ====================
|
||||
// Conditional on compute_dollars
|
||||
pub height_to_unrealized_profit_rel_to_market_cap: Option<EagerVec<PcoVec<Height, StoredF32>>>,
|
||||
pub height_to_unrealized_loss_rel_to_market_cap: Option<EagerVec<PcoVec<Height, StoredF32>>>,
|
||||
pub height_to_neg_unrealized_loss_rel_to_market_cap:
|
||||
Option<EagerVec<PcoVec<Height, StoredF32>>>,
|
||||
pub height_to_net_unrealized_pnl_rel_to_market_cap: Option<EagerVec<PcoVec<Height, StoredF32>>>,
|
||||
pub indexes_to_unrealized_profit_rel_to_market_cap:
|
||||
Option<ComputedVecsFromDateIndex<StoredF32>>,
|
||||
pub indexes_to_unrealized_loss_rel_to_market_cap: Option<ComputedVecsFromDateIndex<StoredF32>>,
|
||||
pub indexes_to_neg_unrealized_loss_rel_to_market_cap:
|
||||
Option<ComputedVecsFromDateIndex<StoredF32>>,
|
||||
pub indexes_to_net_unrealized_pnl_rel_to_market_cap:
|
||||
Option<ComputedVecsFromDateIndex<StoredF32>>,
|
||||
|
||||
// ==================== RELATIVE METRICS: UNREALIZED vs OWN MARKET CAP ====================
|
||||
// Conditional on compute_dollars && extended && compute_rel_to_all
|
||||
pub height_to_unrealized_profit_rel_to_own_market_cap:
|
||||
Option<EagerVec<PcoVec<Height, StoredF32>>>,
|
||||
pub height_to_unrealized_loss_rel_to_own_market_cap:
|
||||
Option<EagerVec<PcoVec<Height, StoredF32>>>,
|
||||
pub height_to_neg_unrealized_loss_rel_to_own_market_cap:
|
||||
Option<EagerVec<PcoVec<Height, StoredF32>>>,
|
||||
pub height_to_net_unrealized_pnl_rel_to_own_market_cap:
|
||||
Option<EagerVec<PcoVec<Height, StoredF32>>>,
|
||||
pub indexes_to_unrealized_profit_rel_to_own_market_cap:
|
||||
Option<ComputedVecsFromDateIndex<StoredF32>>,
|
||||
pub indexes_to_unrealized_loss_rel_to_own_market_cap:
|
||||
Option<ComputedVecsFromDateIndex<StoredF32>>,
|
||||
pub indexes_to_neg_unrealized_loss_rel_to_own_market_cap:
|
||||
Option<ComputedVecsFromDateIndex<StoredF32>>,
|
||||
pub indexes_to_net_unrealized_pnl_rel_to_own_market_cap:
|
||||
Option<ComputedVecsFromDateIndex<StoredF32>>,
|
||||
|
||||
// ==================== RELATIVE METRICS: UNREALIZED vs OWN TOTAL UNREALIZED ====================
|
||||
// Conditional on compute_dollars && extended
|
||||
pub height_to_unrealized_profit_rel_to_own_total_unrealized_pnl:
|
||||
Option<EagerVec<PcoVec<Height, StoredF32>>>,
|
||||
pub height_to_unrealized_loss_rel_to_own_total_unrealized_pnl:
|
||||
Option<EagerVec<PcoVec<Height, StoredF32>>>,
|
||||
pub height_to_neg_unrealized_loss_rel_to_own_total_unrealized_pnl:
|
||||
Option<EagerVec<PcoVec<Height, StoredF32>>>,
|
||||
pub height_to_net_unrealized_pnl_rel_to_own_total_unrealized_pnl:
|
||||
Option<EagerVec<PcoVec<Height, StoredF32>>>,
|
||||
pub indexes_to_unrealized_profit_rel_to_own_total_unrealized_pnl:
|
||||
Option<ComputedVecsFromDateIndex<StoredF32>>,
|
||||
pub indexes_to_unrealized_loss_rel_to_own_total_unrealized_pnl:
|
||||
Option<ComputedVecsFromDateIndex<StoredF32>>,
|
||||
pub indexes_to_neg_unrealized_loss_rel_to_own_total_unrealized_pnl:
|
||||
Option<ComputedVecsFromDateIndex<StoredF32>>,
|
||||
pub indexes_to_net_unrealized_pnl_rel_to_own_total_unrealized_pnl:
|
||||
Option<ComputedVecsFromDateIndex<StoredF32>>,
|
||||
|
||||
// ==================== RELATIVE METRICS: SUPPLY vs CIRCULATING/OWN ====================
|
||||
// Conditional on compute_dollars
|
||||
pub indexes_to_supply_rel_to_circulating_supply: Option<ComputedVecsFromHeight<StoredF64>>,
|
||||
pub height_to_supply_in_profit_rel_to_own_supply: Option<EagerVec<PcoVec<Height, StoredF64>>>,
|
||||
pub height_to_supply_in_loss_rel_to_own_supply: Option<EagerVec<PcoVec<Height, StoredF64>>>,
|
||||
pub indexes_to_supply_in_profit_rel_to_own_supply: Option<ComputedVecsFromDateIndex<StoredF64>>,
|
||||
pub indexes_to_supply_in_loss_rel_to_own_supply: Option<ComputedVecsFromDateIndex<StoredF64>>,
|
||||
pub height_to_supply_in_profit_rel_to_circulating_supply:
|
||||
Option<EagerVec<PcoVec<Height, StoredF64>>>,
|
||||
pub height_to_supply_in_loss_rel_to_circulating_supply:
|
||||
Option<EagerVec<PcoVec<Height, StoredF64>>>,
|
||||
pub indexes_to_supply_in_profit_rel_to_circulating_supply:
|
||||
Option<ComputedVecsFromDateIndex<StoredF64>>,
|
||||
pub indexes_to_supply_in_loss_rel_to_circulating_supply:
|
||||
Option<ComputedVecsFromDateIndex<StoredF64>>,
|
||||
|
||||
// ==================== NET REALIZED PNL DELTAS ====================
|
||||
// Conditional on compute_dollars
|
||||
pub indexes_to_net_realized_pnl_cumulative_30d_delta:
|
||||
Option<ComputedVecsFromDateIndex<Dollars>>,
|
||||
pub indexes_to_net_realized_pnl_cumulative_30d_delta_rel_to_realized_cap:
|
||||
Option<ComputedVecsFromDateIndex<StoredF32>>,
|
||||
pub indexes_to_net_realized_pnl_cumulative_30d_delta_rel_to_market_cap:
|
||||
Option<ComputedVecsFromDateIndex<StoredF32>>,
|
||||
}
|
||||
70
crates/brk_computer/src/stateful/flushable.rs
Normal file
70
crates/brk_computer/src/stateful/flushable.rs
Normal file
@@ -0,0 +1,70 @@
|
||||
//! Traits for consistent state flushing and importing.
|
||||
//!
|
||||
//! These traits ensure all stateful components follow the same patterns
|
||||
//! for checkpoint/resume operations, preventing bugs where new fields
|
||||
//! are forgotten during flush operations.
|
||||
|
||||
use brk_error::Result;
|
||||
use brk_types::Height;
|
||||
use vecdb::Exit;
|
||||
|
||||
/// Trait for components that can be flushed to disk.
|
||||
///
|
||||
/// This is for simple flush operations that don't require height tracking.
|
||||
pub trait Flushable {
|
||||
/// Safely flush data to disk.
|
||||
fn safe_flush(&mut self, exit: &Exit) -> Result<()>;
|
||||
}
|
||||
|
||||
/// Trait for stateful components that track data indexed by height.
|
||||
///
|
||||
/// This ensures consistent patterns for:
|
||||
/// - Flushing state at checkpoints
|
||||
/// - Importing state when resuming from a checkpoint
|
||||
/// - Resetting state when starting from scratch
|
||||
pub trait HeightFlushable {
|
||||
/// Flush state to disk at the given height checkpoint.
|
||||
fn flush_at_height(&mut self, height: Height, exit: &Exit) -> Result<()>;
|
||||
|
||||
/// Import state from the most recent checkpoint at or before the given height.
|
||||
/// Returns the actual height that was imported.
|
||||
fn import_at_or_before(&mut self, height: Height) -> Result<Height>;
|
||||
|
||||
/// Reset state for starting from scratch.
|
||||
fn reset(&mut self) -> Result<()>;
|
||||
}
|
||||
|
||||
/// Blanket implementation for Option<T> where T: Flushable
|
||||
impl<T: Flushable> Flushable for Option<T> {
|
||||
fn safe_flush(&mut self, exit: &Exit) -> Result<()> {
|
||||
if let Some(inner) = self.as_mut() {
|
||||
inner.safe_flush(exit)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Blanket implementation for Option<T> where T: HeightFlushable
|
||||
impl<T: HeightFlushable> HeightFlushable for Option<T> {
|
||||
fn flush_at_height(&mut self, height: Height, exit: &Exit) -> Result<()> {
|
||||
if let Some(inner) = self.as_mut() {
|
||||
inner.flush_at_height(height, exit)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn import_at_or_before(&mut self, height: Height) -> Result<Height> {
|
||||
if let Some(inner) = self.as_mut() {
|
||||
inner.import_at_or_before(height)
|
||||
} else {
|
||||
Ok(height)
|
||||
}
|
||||
}
|
||||
|
||||
fn reset(&mut self) -> Result<()> {
|
||||
if let Some(inner) = self.as_mut() {
|
||||
inner.reset()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,33 @@
|
||||
//! Stateful computation module for Bitcoin UTXO and address cohort analysis.
|
||||
//!
|
||||
//! This module contains the main computation loop that processes blocks and computes
|
||||
//! various metrics for UTXO cohorts (grouped by age, amount, etc.) and address cohorts.
|
||||
//!
|
||||
//! ## Architecture
|
||||
//!
|
||||
//! The module is organized as follows:
|
||||
//!
|
||||
//! - **`Vecs`**: Main struct holding all computed vectors and state
|
||||
//! - **Cohort Types**:
|
||||
//! - **Separate cohorts**: Have full state tracking (e.g., UTXOs 1-2 years old)
|
||||
//! - **Aggregate cohorts**: Computed from separate cohorts (e.g., all, sth, lth)
|
||||
//!
|
||||
//! ## Checkpoint/Resume
|
||||
//!
|
||||
//! The computation supports checkpointing via `flush_states()` which saves:
|
||||
//! - All separate cohorts' state (via `safe_flush_stateful_vecs`)
|
||||
//! - Aggregate cohorts' `price_to_amount` (via `HeightFlushable` trait)
|
||||
//! - Aggregate cohorts' `price_percentiles` (via `Flushable` trait)
|
||||
//!
|
||||
//! Resume is handled by:
|
||||
//! - `import_state()` for separate cohorts
|
||||
//! - `import_aggregate_price_to_amount()` for aggregate cohorts
|
||||
//!
|
||||
//! ## Key Traits
|
||||
//!
|
||||
//! - `Flushable`: Simple flush operations (no height tracking)
|
||||
//! - `HeightFlushable`: Height-indexed state (flush, import, reset)
|
||||
|
||||
use std::{cmp::Ordering, collections::BTreeSet, mem, path::Path, thread};
|
||||
|
||||
use brk_error::Result;
|
||||
@@ -34,6 +64,7 @@ mod address_cohorts;
|
||||
mod address_indexes;
|
||||
mod addresstype;
|
||||
mod common;
|
||||
mod flushable;
|
||||
mod range_map;
|
||||
mod readers;
|
||||
mod r#trait;
|
||||
@@ -42,6 +73,8 @@ mod utxo_cohort;
|
||||
mod utxo_cohorts;
|
||||
mod withaddressdatasource;
|
||||
|
||||
pub use flushable::{Flushable, HeightFlushable};
|
||||
|
||||
use address_indexes::{AddressesDataVecs, AnyAddressIndexesVecs};
|
||||
use addresstype::*;
|
||||
use range_map::*;
|
||||
|
||||
@@ -18,7 +18,7 @@ use vecdb::{Database, Exit, IterableVec, VecIndex};
|
||||
|
||||
use crate::{
|
||||
Indexes, indexes, price,
|
||||
stateful::r#trait::DynCohortVecs,
|
||||
stateful::{Flushable, HeightFlushable, r#trait::DynCohortVecs},
|
||||
states::{BlockState, Transacted},
|
||||
utils::OptionExt,
|
||||
};
|
||||
@@ -614,13 +614,10 @@ impl Vecs {
|
||||
.try_for_each(|v| v.safe_flush_stateful_vecs(height, exit))?;
|
||||
|
||||
// Flush aggregate cohorts' price_to_amount and price_percentiles
|
||||
// Using traits ensures we can't forget to flush any field
|
||||
self.0.par_iter_aggregate_mut().try_for_each(|v| {
|
||||
if let Some(p2a) = v.price_to_amount.as_mut() {
|
||||
p2a.flush(height)?;
|
||||
}
|
||||
if let Some(pp) = v.inner.price_percentiles.as_mut() {
|
||||
pp.safe_flush(exit)?;
|
||||
}
|
||||
v.price_to_amount.flush_at_height(height, exit)?;
|
||||
v.inner.price_percentiles.safe_flush(exit)?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
@@ -628,11 +625,7 @@ impl Vecs {
|
||||
/// Reset aggregate cohorts' price_to_amount when starting from scratch
|
||||
pub fn reset_aggregate_price_to_amount(&mut self) -> Result<()> {
|
||||
self.0.iter_aggregate_mut().try_for_each(|v| {
|
||||
if let Some(p2a) = v.price_to_amount.as_mut() {
|
||||
p2a.clean()?;
|
||||
p2a.init();
|
||||
}
|
||||
Ok(())
|
||||
v.price_to_amount.reset()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -651,10 +644,8 @@ impl Vecs {
|
||||
};
|
||||
|
||||
for v in self.0.iter_aggregate_mut() {
|
||||
if let Some(p2a) = v.price_to_amount.as_mut() {
|
||||
// Match separate vecs: update prev_height to the checkpoint found
|
||||
prev_height = prev_height.min(p2a.import_at_or_before(prev_height)?);
|
||||
}
|
||||
// Using HeightFlushable trait - if price_to_amount is None, returns height unchanged
|
||||
prev_height = prev_height.min(v.price_to_amount.import_at_or_before(prev_height)?);
|
||||
}
|
||||
// Return prev_height + 1, matching separate vecs behavior
|
||||
Ok(prev_height.incremented())
|
||||
|
||||
@@ -9,9 +9,9 @@ use brk_types::{Dollars, Height, Sats};
|
||||
use derive_deref::{Deref, DerefMut};
|
||||
use pco::standalone::{simple_decompress, simpler_compress};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vecdb::Bytes;
|
||||
use vecdb::{Bytes, Exit};
|
||||
|
||||
use crate::{states::SupplyState, utils::OptionExt};
|
||||
use crate::{stateful::HeightFlushable, states::SupplyState, utils::OptionExt};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PriceToAmount {
|
||||
@@ -128,6 +128,22 @@ impl PriceToAmount {
|
||||
}
|
||||
}
|
||||
|
||||
impl HeightFlushable for PriceToAmount {
|
||||
fn flush_at_height(&mut self, height: Height, _exit: &Exit) -> Result<()> {
|
||||
self.flush(height)
|
||||
}
|
||||
|
||||
fn import_at_or_before(&mut self, height: Height) -> Result<Height> {
|
||||
PriceToAmount::import_at_or_before(self, height)
|
||||
}
|
||||
|
||||
fn reset(&mut self) -> Result<()> {
|
||||
self.clean()?;
|
||||
self.init();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Debug, Deref, DerefMut, Serialize, Deserialize)]
|
||||
struct State(BTreeMap<Dollars, Sats>);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user