mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-24 06:39:58 -07:00
global: snapshot
This commit is contained in:
@@ -1591,6 +1591,32 @@ impl _1m1w1y24hBpsPercentRatioPattern {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Pattern struct for repeated tree structure.
|
||||||
|
pub struct CoindaysCoinyearsDormancySentVelocityPattern {
|
||||||
|
pub coindays_destroyed: CumulativeRawSumPattern<StoredF64>,
|
||||||
|
pub coindays_destroyed_supply_adjusted: MetricPattern1<StoredF32>,
|
||||||
|
pub coinyears_destroyed: MetricPattern1<StoredF64>,
|
||||||
|
pub coinyears_destroyed_supply_adjusted: MetricPattern1<StoredF32>,
|
||||||
|
pub dormancy: MetricPattern1<StoredF32>,
|
||||||
|
pub sent: RawSumPattern3<Sats>,
|
||||||
|
pub velocity: MetricPattern1<StoredF32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CoindaysCoinyearsDormancySentVelocityPattern {
|
||||||
|
/// Create a new pattern node with accumulated metric name.
|
||||||
|
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
|
||||||
|
Self {
|
||||||
|
coindays_destroyed: CumulativeRawSumPattern::new(client.clone(), _m(&acc, "coindays_destroyed")),
|
||||||
|
coindays_destroyed_supply_adjusted: MetricPattern1::new(client.clone(), _m(&acc, "coindays_destroyed_supply_adjusted")),
|
||||||
|
coinyears_destroyed: MetricPattern1::new(client.clone(), _m(&acc, "coinyears_destroyed")),
|
||||||
|
coinyears_destroyed_supply_adjusted: MetricPattern1::new(client.clone(), _m(&acc, "coinyears_destroyed_supply_adjusted")),
|
||||||
|
dormancy: MetricPattern1::new(client.clone(), _m(&acc, "dormancy")),
|
||||||
|
sent: RawSumPattern3::new(client.clone(), _m(&acc, "sent")),
|
||||||
|
velocity: MetricPattern1::new(client.clone(), _m(&acc, "velocity")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Pattern struct for repeated tree structure.
|
/// Pattern struct for repeated tree structure.
|
||||||
pub struct CumulativeDistributionRawRelSumValuePattern {
|
pub struct CumulativeDistributionRawRelSumValuePattern {
|
||||||
pub cumulative: MetricPattern1<Cents>,
|
pub cumulative: MetricPattern1<Cents>,
|
||||||
@@ -2225,26 +2251,6 @@ impl CentsRelUsdPattern2 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pattern struct for repeated tree structure.
|
|
||||||
pub struct CoindaysDormancySentVelocityPattern {
|
|
||||||
pub coindays_destroyed: CumulativeRawSumPattern<StoredF64>,
|
|
||||||
pub dormancy: MetricPattern1<StoredF32>,
|
|
||||||
pub sent: RawSumPattern3<Sats>,
|
|
||||||
pub velocity: MetricPattern1<StoredF32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CoindaysDormancySentVelocityPattern {
|
|
||||||
/// Create a new pattern node with accumulated metric name.
|
|
||||||
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
|
|
||||||
Self {
|
|
||||||
coindays_destroyed: CumulativeRawSumPattern::new(client.clone(), _m(&acc, "coindays_destroyed")),
|
|
||||||
dormancy: MetricPattern1::new(client.clone(), _m(&acc, "dormancy")),
|
|
||||||
sent: RawSumPattern3::new(client.clone(), _m(&acc, "sent")),
|
|
||||||
velocity: MetricPattern1::new(client.clone(), _m(&acc, "velocity")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Pattern struct for repeated tree structure.
|
/// Pattern struct for repeated tree structure.
|
||||||
pub struct CumulativeNegativeRawSumPattern {
|
pub struct CumulativeNegativeRawSumPattern {
|
||||||
pub cumulative: MetricPattern1<Cents>,
|
pub cumulative: MetricPattern1<Cents>,
|
||||||
@@ -4245,6 +4251,7 @@ pub struct MetricsTree_Cointime_Cap {
|
|||||||
pub vaulted_cap: CentsUsdPattern,
|
pub vaulted_cap: CentsUsdPattern,
|
||||||
pub active_cap: CentsUsdPattern,
|
pub active_cap: CentsUsdPattern,
|
||||||
pub cointime_cap: CentsUsdPattern,
|
pub cointime_cap: CentsUsdPattern,
|
||||||
|
pub aviv: BpsRatioPattern,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MetricsTree_Cointime_Cap {
|
impl MetricsTree_Cointime_Cap {
|
||||||
@@ -4255,6 +4262,7 @@ impl MetricsTree_Cointime_Cap {
|
|||||||
vaulted_cap: CentsUsdPattern::new(client.clone(), "vaulted_cap".to_string()),
|
vaulted_cap: CentsUsdPattern::new(client.clone(), "vaulted_cap".to_string()),
|
||||||
active_cap: CentsUsdPattern::new(client.clone(), "active_cap".to_string()),
|
active_cap: CentsUsdPattern::new(client.clone(), "active_cap".to_string()),
|
||||||
cointime_cap: CentsUsdPattern::new(client.clone(), "cointime_cap".to_string()),
|
cointime_cap: CentsUsdPattern::new(client.clone(), "cointime_cap".to_string()),
|
||||||
|
aviv: BpsRatioPattern::new(client.clone(), "aviv_ratio".to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6796,7 +6804,7 @@ impl MetricsTree_Distribution_UtxoCohorts {
|
|||||||
pub struct MetricsTree_Distribution_UtxoCohorts_All {
|
pub struct MetricsTree_Distribution_UtxoCohorts_All {
|
||||||
pub supply: MetricsTree_Distribution_UtxoCohorts_All_Supply,
|
pub supply: MetricsTree_Distribution_UtxoCohorts_All_Supply,
|
||||||
pub outputs: UtxoPattern3,
|
pub outputs: UtxoPattern3,
|
||||||
pub activity: CoindaysDormancySentVelocityPattern,
|
pub activity: CoindaysCoinyearsDormancySentVelocityPattern,
|
||||||
pub realized: CapGrossInvestorLossMvrvNetNuplPeakPriceProfitSentSoprPattern,
|
pub realized: CapGrossInvestorLossMvrvNetNuplPeakPriceProfitSentSoprPattern,
|
||||||
pub cost_basis: InvestedMaxMinPercentilesPattern,
|
pub cost_basis: InvestedMaxMinPercentilesPattern,
|
||||||
pub unrealized: MetricsTree_Distribution_UtxoCohorts_All_Unrealized,
|
pub unrealized: MetricsTree_Distribution_UtxoCohorts_All_Unrealized,
|
||||||
@@ -6807,7 +6815,7 @@ impl MetricsTree_Distribution_UtxoCohorts_All {
|
|||||||
Self {
|
Self {
|
||||||
supply: MetricsTree_Distribution_UtxoCohorts_All_Supply::new(client.clone(), format!("{base_path}_supply")),
|
supply: MetricsTree_Distribution_UtxoCohorts_All_Supply::new(client.clone(), format!("{base_path}_supply")),
|
||||||
outputs: UtxoPattern3::new(client.clone(), "utxo_count".to_string()),
|
outputs: UtxoPattern3::new(client.clone(), "utxo_count".to_string()),
|
||||||
activity: CoindaysDormancySentVelocityPattern::new(client.clone(), "".to_string()),
|
activity: CoindaysCoinyearsDormancySentVelocityPattern::new(client.clone(), "".to_string()),
|
||||||
realized: CapGrossInvestorLossMvrvNetNuplPeakPriceProfitSentSoprPattern::new(client.clone(), "".to_string()),
|
realized: CapGrossInvestorLossMvrvNetNuplPeakPriceProfitSentSoprPattern::new(client.clone(), "".to_string()),
|
||||||
cost_basis: InvestedMaxMinPercentilesPattern::new(client.clone(), "".to_string()),
|
cost_basis: InvestedMaxMinPercentilesPattern::new(client.clone(), "".to_string()),
|
||||||
unrealized: MetricsTree_Distribution_UtxoCohorts_All_Unrealized::new(client.clone(), format!("{base_path}_unrealized")),
|
unrealized: MetricsTree_Distribution_UtxoCohorts_All_Unrealized::new(client.clone(), format!("{base_path}_unrealized")),
|
||||||
@@ -6923,7 +6931,7 @@ pub struct MetricsTree_Distribution_UtxoCohorts_Sth {
|
|||||||
pub realized: CapGrossInvestorLossMvrvNetNuplPeakPriceProfitSentSoprPattern,
|
pub realized: CapGrossInvestorLossMvrvNetNuplPeakPriceProfitSentSoprPattern,
|
||||||
pub supply: DeltaHalvedRelTotalPattern2,
|
pub supply: DeltaHalvedRelTotalPattern2,
|
||||||
pub outputs: UtxoPattern3,
|
pub outputs: UtxoPattern3,
|
||||||
pub activity: CoindaysDormancySentVelocityPattern,
|
pub activity: CoindaysCoinyearsDormancySentVelocityPattern,
|
||||||
pub cost_basis: InvestedMaxMinPercentilesPattern,
|
pub cost_basis: InvestedMaxMinPercentilesPattern,
|
||||||
pub unrealized: GrossInvestedInvestorLossNetProfitSentimentPattern2,
|
pub unrealized: GrossInvestedInvestorLossNetProfitSentimentPattern2,
|
||||||
}
|
}
|
||||||
@@ -6934,7 +6942,7 @@ impl MetricsTree_Distribution_UtxoCohorts_Sth {
|
|||||||
realized: CapGrossInvestorLossMvrvNetNuplPeakPriceProfitSentSoprPattern::new(client.clone(), "sth".to_string()),
|
realized: CapGrossInvestorLossMvrvNetNuplPeakPriceProfitSentSoprPattern::new(client.clone(), "sth".to_string()),
|
||||||
supply: DeltaHalvedRelTotalPattern2::new(client.clone(), "sth_supply".to_string()),
|
supply: DeltaHalvedRelTotalPattern2::new(client.clone(), "sth_supply".to_string()),
|
||||||
outputs: UtxoPattern3::new(client.clone(), "sth_utxo_count".to_string()),
|
outputs: UtxoPattern3::new(client.clone(), "sth_utxo_count".to_string()),
|
||||||
activity: CoindaysDormancySentVelocityPattern::new(client.clone(), "sth".to_string()),
|
activity: CoindaysCoinyearsDormancySentVelocityPattern::new(client.clone(), "sth".to_string()),
|
||||||
cost_basis: InvestedMaxMinPercentilesPattern::new(client.clone(), "sth".to_string()),
|
cost_basis: InvestedMaxMinPercentilesPattern::new(client.clone(), "sth".to_string()),
|
||||||
unrealized: GrossInvestedInvestorLossNetProfitSentimentPattern2::new(client.clone(), "sth".to_string()),
|
unrealized: GrossInvestedInvestorLossNetProfitSentimentPattern2::new(client.clone(), "sth".to_string()),
|
||||||
}
|
}
|
||||||
@@ -6945,7 +6953,7 @@ impl MetricsTree_Distribution_UtxoCohorts_Sth {
|
|||||||
pub struct MetricsTree_Distribution_UtxoCohorts_Lth {
|
pub struct MetricsTree_Distribution_UtxoCohorts_Lth {
|
||||||
pub supply: DeltaHalvedRelTotalPattern2,
|
pub supply: DeltaHalvedRelTotalPattern2,
|
||||||
pub outputs: UtxoPattern3,
|
pub outputs: UtxoPattern3,
|
||||||
pub activity: CoindaysDormancySentVelocityPattern,
|
pub activity: CoindaysCoinyearsDormancySentVelocityPattern,
|
||||||
pub realized: MetricsTree_Distribution_UtxoCohorts_Lth_Realized,
|
pub realized: MetricsTree_Distribution_UtxoCohorts_Lth_Realized,
|
||||||
pub cost_basis: InvestedMaxMinPercentilesPattern,
|
pub cost_basis: InvestedMaxMinPercentilesPattern,
|
||||||
pub unrealized: GrossInvestedInvestorLossNetProfitSentimentPattern2,
|
pub unrealized: GrossInvestedInvestorLossNetProfitSentimentPattern2,
|
||||||
@@ -6956,7 +6964,7 @@ impl MetricsTree_Distribution_UtxoCohorts_Lth {
|
|||||||
Self {
|
Self {
|
||||||
supply: DeltaHalvedRelTotalPattern2::new(client.clone(), "lth_supply".to_string()),
|
supply: DeltaHalvedRelTotalPattern2::new(client.clone(), "lth_supply".to_string()),
|
||||||
outputs: UtxoPattern3::new(client.clone(), "lth_utxo_count".to_string()),
|
outputs: UtxoPattern3::new(client.clone(), "lth_utxo_count".to_string()),
|
||||||
activity: CoindaysDormancySentVelocityPattern::new(client.clone(), "lth".to_string()),
|
activity: CoindaysCoinyearsDormancySentVelocityPattern::new(client.clone(), "lth".to_string()),
|
||||||
realized: MetricsTree_Distribution_UtxoCohorts_Lth_Realized::new(client.clone(), format!("{base_path}_realized")),
|
realized: MetricsTree_Distribution_UtxoCohorts_Lth_Realized::new(client.clone(), format!("{base_path}_realized")),
|
||||||
cost_basis: InvestedMaxMinPercentilesPattern::new(client.clone(), "lth".to_string()),
|
cost_basis: InvestedMaxMinPercentilesPattern::new(client.clone(), "lth".to_string()),
|
||||||
unrealized: GrossInvestedInvestorLossNetProfitSentimentPattern2::new(client.clone(), "lth".to_string()),
|
unrealized: GrossInvestedInvestorLossNetProfitSentimentPattern2::new(client.clone(), "lth".to_string()),
|
||||||
|
|||||||
@@ -64,6 +64,14 @@ impl Vecs {
|
|||||||
exit,
|
exit,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
// AVIV = active_cap / investor_cap
|
||||||
|
self.aviv.compute_ratio(
|
||||||
|
starting_indexes,
|
||||||
|
&self.active_cap.cents.height,
|
||||||
|
&self.investor_cap.cents.height,
|
||||||
|
exit,
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use brk_types::Version;
|
|||||||
use vecdb::Database;
|
use vecdb::Database;
|
||||||
|
|
||||||
use super::Vecs;
|
use super::Vecs;
|
||||||
use crate::{indexes, internal::FiatPerBlock};
|
use crate::{indexes, internal::{FiatPerBlock, RatioPerBlock}};
|
||||||
|
|
||||||
impl Vecs {
|
impl Vecs {
|
||||||
pub(crate) fn forced_import(
|
pub(crate) fn forced_import(
|
||||||
@@ -17,6 +17,7 @@ impl Vecs {
|
|||||||
vaulted_cap: FiatPerBlock::forced_import(db, "vaulted_cap", version, indexes)?,
|
vaulted_cap: FiatPerBlock::forced_import(db, "vaulted_cap", version, indexes)?,
|
||||||
active_cap: FiatPerBlock::forced_import(db, "active_cap", version, indexes)?,
|
active_cap: FiatPerBlock::forced_import(db, "active_cap", version, indexes)?,
|
||||||
cointime_cap: FiatPerBlock::forced_import(db, "cointime_cap", version, indexes)?,
|
cointime_cap: FiatPerBlock::forced_import(db, "cointime_cap", version, indexes)?,
|
||||||
|
aviv: RatioPerBlock::forced_import(db, "aviv", version, indexes)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use brk_traversable::Traversable;
|
use brk_traversable::Traversable;
|
||||||
use brk_types::Cents;
|
use brk_types::{BasisPoints32, Cents};
|
||||||
use vecdb::{Rw, StorageMode};
|
use vecdb::{Rw, StorageMode};
|
||||||
|
|
||||||
use crate::internal::FiatPerBlock;
|
use crate::internal::{FiatPerBlock, RatioPerBlock};
|
||||||
|
|
||||||
#[derive(Traversable)]
|
#[derive(Traversable)]
|
||||||
pub struct Vecs<M: StorageMode = Rw> {
|
pub struct Vecs<M: StorageMode = Rw> {
|
||||||
@@ -11,4 +11,5 @@ pub struct Vecs<M: StorageMode = Rw> {
|
|||||||
pub vaulted_cap: FiatPerBlock<Cents, M>,
|
pub vaulted_cap: FiatPerBlock<Cents, M>,
|
||||||
pub active_cap: FiatPerBlock<Cents, M>,
|
pub active_cap: FiatPerBlock<Cents, M>,
|
||||||
pub cointime_cap: FiatPerBlock<Cents, M>,
|
pub cointime_cap: FiatPerBlock<Cents, M>,
|
||||||
|
pub aviv: RatioPerBlock<BasisPoints32, M>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,14 +64,14 @@ pub struct UTXOCohortVecs<M: CohortMetricsState> {
|
|||||||
state_starting_height: Option<Height>,
|
state_starting_height: Option<Height>,
|
||||||
|
|
||||||
#[traversable(skip)]
|
#[traversable(skip)]
|
||||||
pub state: Option<Box<UTXOCohortState<M::Realized>>>,
|
pub state: Option<Box<UTXOCohortState<M::Realized, M::CostBasis>>>,
|
||||||
|
|
||||||
#[traversable(flatten)]
|
#[traversable(flatten)]
|
||||||
pub metrics: M,
|
pub metrics: M,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<M: CohortMetricsState> UTXOCohortVecs<M> {
|
impl<M: CohortMetricsState> UTXOCohortVecs<M> {
|
||||||
pub(crate) fn new(state: Option<Box<UTXOCohortState<M::Realized>>>, metrics: M) -> Self {
|
pub(crate) fn new(state: Option<Box<UTXOCohortState<M::Realized, M::CostBasis>>>, metrics: M) -> Self {
|
||||||
Self {
|
Self {
|
||||||
state_starting_height: None,
|
state_starting_height: None,
|
||||||
state,
|
state,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use vecdb::{AnyStoredVec, AnyVec, Exit, Rw, StorageMode, WritableVec};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
blocks,
|
blocks,
|
||||||
distribution::{metrics::ImportConfig, state::{CohortState, RealizedOps}},
|
distribution::{metrics::ImportConfig, state::{CohortState, CostBasisOps, RealizedOps}},
|
||||||
internal::PerBlockWithSum24h,
|
internal::PerBlockWithSum24h,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ impl ActivityCore {
|
|||||||
pub(crate) fn truncate_push(
|
pub(crate) fn truncate_push(
|
||||||
&mut self,
|
&mut self,
|
||||||
height: Height,
|
height: Height,
|
||||||
state: &CohortState<impl RealizedOps>,
|
state: &CohortState<impl RealizedOps, impl CostBasisOps>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
self.sent.raw.height.truncate_push(height, state.sent)?;
|
self.sent.raw.height.truncate_push(height, state.sent)?;
|
||||||
self.coindays_destroyed.raw.height.truncate_push(
|
self.coindays_destroyed.raw.height.truncate_push(
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ use brk_error::Result;
|
|||||||
use brk_traversable::Traversable;
|
use brk_traversable::Traversable;
|
||||||
use brk_types::{Bitcoin, Height, Indexes, Sats, StoredF32, StoredF64, Version};
|
use brk_types::{Bitcoin, Height, Indexes, Sats, StoredF32, StoredF64, Version};
|
||||||
use derive_more::{Deref, DerefMut};
|
use derive_more::{Deref, DerefMut};
|
||||||
use vecdb::{AnyStoredVec, Exit, ReadableVec, Rw, StorageMode};
|
use vecdb::{AnyStoredVec, Exit, ReadableCloneableVec, ReadableVec, Rw, StorageMode};
|
||||||
|
|
||||||
use crate::internal::{ComputedPerBlock, RollingWindowsFrom1w};
|
use crate::internal::{ComputedPerBlock, Identity, LazyPerBlock, RollingWindowsFrom1w};
|
||||||
|
|
||||||
use crate::{blocks, distribution::{metrics::ImportConfig, state::{CohortState, RealizedOps}}};
|
use crate::{blocks, distribution::{metrics::ImportConfig, state::{CohortState, CostBasisOps, RealizedOps}}};
|
||||||
|
|
||||||
use super::ActivityCore;
|
use super::ActivityCore;
|
||||||
|
|
||||||
@@ -25,20 +25,37 @@ pub struct ActivityFull<M: StorageMode = Rw> {
|
|||||||
#[traversable(wrap = "sent", rename = "sum")]
|
#[traversable(wrap = "sent", rename = "sum")]
|
||||||
pub sent_sum_extended: RollingWindowsFrom1w<Sats, M>,
|
pub sent_sum_extended: RollingWindowsFrom1w<Sats, M>,
|
||||||
|
|
||||||
|
pub coinyears_destroyed: LazyPerBlock<StoredF64, StoredF64>,
|
||||||
|
|
||||||
pub dormancy: ComputedPerBlock<StoredF32, M>,
|
pub dormancy: ComputedPerBlock<StoredF32, M>,
|
||||||
pub velocity: ComputedPerBlock<StoredF32, M>,
|
pub velocity: ComputedPerBlock<StoredF32, M>,
|
||||||
|
pub coindays_destroyed_supply_adjusted: ComputedPerBlock<StoredF32, M>,
|
||||||
|
pub coinyears_destroyed_supply_adjusted: ComputedPerBlock<StoredF32, M>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ActivityFull {
|
impl ActivityFull {
|
||||||
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
|
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
|
||||||
let v1 = Version::ONE;
|
let v1 = Version::ONE;
|
||||||
|
let coindays_destroyed_sum: RollingWindowsFrom1w<StoredF64> =
|
||||||
|
cfg.import("coindays_destroyed", v1)?;
|
||||||
|
|
||||||
|
let coinyears_destroyed = LazyPerBlock::from_computed::<Identity<StoredF64>>(
|
||||||
|
&cfg.name("coinyears_destroyed"),
|
||||||
|
v1,
|
||||||
|
coindays_destroyed_sum._1y.height.read_only_boxed_clone(),
|
||||||
|
&coindays_destroyed_sum._1y,
|
||||||
|
);
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
inner: ActivityCore::forced_import(cfg)?,
|
inner: ActivityCore::forced_import(cfg)?,
|
||||||
coindays_destroyed_cumulative: cfg.import("coindays_destroyed_cumulative", v1)?,
|
coindays_destroyed_cumulative: cfg.import("coindays_destroyed_cumulative", v1)?,
|
||||||
coindays_destroyed_sum: cfg.import("coindays_destroyed", v1)?,
|
coindays_destroyed_sum,
|
||||||
sent_sum_extended: cfg.import("sent", v1)?,
|
sent_sum_extended: cfg.import("sent", v1)?,
|
||||||
|
coinyears_destroyed,
|
||||||
dormancy: cfg.import("dormancy", v1)?,
|
dormancy: cfg.import("dormancy", v1)?,
|
||||||
velocity: cfg.import("velocity", v1)?,
|
velocity: cfg.import("velocity", v1)?,
|
||||||
|
coindays_destroyed_supply_adjusted: cfg.import("coindays_destroyed_supply_adjusted", v1)?,
|
||||||
|
coinyears_destroyed_supply_adjusted: cfg.import("coinyears_destroyed_supply_adjusted", v1)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +66,7 @@ impl ActivityFull {
|
|||||||
pub(crate) fn full_truncate_push(
|
pub(crate) fn full_truncate_push(
|
||||||
&mut self,
|
&mut self,
|
||||||
height: Height,
|
height: Height,
|
||||||
state: &CohortState<impl RealizedOps>,
|
state: &CohortState<impl RealizedOps, impl CostBasisOps>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
self.inner.truncate_push(height, state)
|
self.inner.truncate_push(height, state)
|
||||||
}
|
}
|
||||||
@@ -58,6 +75,8 @@ impl ActivityFull {
|
|||||||
let mut vecs = self.inner.collect_vecs_mut();
|
let mut vecs = self.inner.collect_vecs_mut();
|
||||||
vecs.push(&mut self.dormancy.height);
|
vecs.push(&mut self.dormancy.height);
|
||||||
vecs.push(&mut self.velocity.height);
|
vecs.push(&mut self.velocity.height);
|
||||||
|
vecs.push(&mut self.coindays_destroyed_supply_adjusted.height);
|
||||||
|
vecs.push(&mut self.coinyears_destroyed_supply_adjusted.height);
|
||||||
vecs
|
vecs
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,6 +161,38 @@ impl ActivityFull {
|
|||||||
exit,
|
exit,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
// Supply-Adjusted CDD = sum_24h(CDD) / circulating_supply
|
||||||
|
self.coindays_destroyed_supply_adjusted.height.compute_transform2(
|
||||||
|
starting_indexes.height,
|
||||||
|
&self.inner.coindays_destroyed.sum._24h.height,
|
||||||
|
supply_total_sats,
|
||||||
|
|(i, cdd_24h, supply_sats, ..)| {
|
||||||
|
let supply = f64::from(Bitcoin::from(supply_sats));
|
||||||
|
if supply == 0.0 {
|
||||||
|
(i, StoredF32::from(0.0f32))
|
||||||
|
} else {
|
||||||
|
(i, StoredF32::from((f64::from(cdd_24h) / supply) as f32))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
exit,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Supply-Adjusted CYD = CYD / circulating_supply (CYD = 1y rolling sum of CDD)
|
||||||
|
self.coinyears_destroyed_supply_adjusted.height.compute_transform2(
|
||||||
|
starting_indexes.height,
|
||||||
|
&self.coinyears_destroyed.height,
|
||||||
|
supply_total_sats,
|
||||||
|
|(i, cdd_1y, supply_sats, ..)| {
|
||||||
|
let supply = f64::from(Bitcoin::from(supply_sats));
|
||||||
|
if supply == 0.0 {
|
||||||
|
(i, StoredF32::from(0.0f32))
|
||||||
|
} else {
|
||||||
|
(i, StoredF32::from((f64::from(cdd_1y) / supply) as f32))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
exit,
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use brk_error::Result;
|
|||||||
use brk_types::{Height, Indexes, Version};
|
use brk_types::{Height, Indexes, Version};
|
||||||
use vecdb::Exit;
|
use vecdb::Exit;
|
||||||
|
|
||||||
use crate::{blocks, distribution::state::{CohortState, RealizedOps}};
|
use crate::{blocks, distribution::state::{CohortState, CostBasisOps, RealizedOps}};
|
||||||
|
|
||||||
pub trait ActivityLike: Send + Sync {
|
pub trait ActivityLike: Send + Sync {
|
||||||
fn as_core(&self) -> &ActivityCore;
|
fn as_core(&self) -> &ActivityCore;
|
||||||
@@ -17,7 +17,7 @@ pub trait ActivityLike: Send + Sync {
|
|||||||
fn truncate_push<R: RealizedOps>(
|
fn truncate_push<R: RealizedOps>(
|
||||||
&mut self,
|
&mut self,
|
||||||
height: Height,
|
height: Height,
|
||||||
state: &CohortState<R>,
|
state: &CohortState<R, impl CostBasisOps>,
|
||||||
) -> Result<()>;
|
) -> Result<()>;
|
||||||
fn validate_computed_versions(&mut self, base_version: Version) -> Result<()>;
|
fn validate_computed_versions(&mut self, base_version: Version) -> Result<()>;
|
||||||
fn compute_from_stateful(
|
fn compute_from_stateful(
|
||||||
@@ -38,7 +38,7 @@ impl ActivityLike for ActivityCore {
|
|||||||
fn as_core(&self) -> &ActivityCore { self }
|
fn as_core(&self) -> &ActivityCore { self }
|
||||||
fn as_core_mut(&mut self) -> &mut ActivityCore { self }
|
fn as_core_mut(&mut self) -> &mut ActivityCore { self }
|
||||||
fn min_len(&self) -> usize { self.min_len() }
|
fn min_len(&self) -> usize { self.min_len() }
|
||||||
fn truncate_push<R: RealizedOps>(&mut self, height: Height, state: &CohortState<R>) -> Result<()> {
|
fn truncate_push<R: RealizedOps>(&mut self, height: Height, state: &CohortState<R, impl CostBasisOps>) -> Result<()> {
|
||||||
self.truncate_push(height, state)
|
self.truncate_push(height, state)
|
||||||
}
|
}
|
||||||
fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> {
|
fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> {
|
||||||
@@ -56,7 +56,7 @@ impl ActivityLike for ActivityFull {
|
|||||||
fn as_core(&self) -> &ActivityCore { &self.inner }
|
fn as_core(&self) -> &ActivityCore { &self.inner }
|
||||||
fn as_core_mut(&mut self) -> &mut ActivityCore { &mut self.inner }
|
fn as_core_mut(&mut self) -> &mut ActivityCore { &mut self.inner }
|
||||||
fn min_len(&self) -> usize { self.full_min_len() }
|
fn min_len(&self) -> usize { self.full_min_len() }
|
||||||
fn truncate_push<R: RealizedOps>(&mut self, height: Height, state: &CohortState<R>) -> Result<()> {
|
fn truncate_push<R: RealizedOps>(&mut self, height: Height, state: &CohortState<R, impl CostBasisOps>) -> Result<()> {
|
||||||
self.full_truncate_push(height, state)
|
self.full_truncate_push(height, state)
|
||||||
}
|
}
|
||||||
fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> {
|
fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> {
|
||||||
|
|||||||
@@ -66,35 +66,43 @@ use brk_error::Result;
|
|||||||
use brk_types::{Cents, Height, Indexes, Version};
|
use brk_types::{Cents, Height, Indexes, Version};
|
||||||
use vecdb::{AnyStoredVec, Exit, StorageMode};
|
use vecdb::{AnyStoredVec, Exit, StorageMode};
|
||||||
|
|
||||||
use crate::{blocks, distribution::state::{CohortState, CoreRealizedState, MinimalRealizedState, RealizedOps, RealizedState}, prices};
|
use crate::{blocks, distribution::state::{CohortState, CostBasisData, CostBasisOps, CostBasisRaw, CoreRealizedState, MinimalRealizedState, RealizedOps, RealizedState}, prices};
|
||||||
|
|
||||||
pub trait CohortMetricsState {
|
pub trait CohortMetricsState {
|
||||||
type Realized: RealizedOps;
|
type Realized: RealizedOps;
|
||||||
|
type CostBasis: CostBasisOps;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<M: StorageMode> CohortMetricsState for TypeCohortMetrics<M> {
|
impl<M: StorageMode> CohortMetricsState for TypeCohortMetrics<M> {
|
||||||
type Realized = MinimalRealizedState;
|
type Realized = MinimalRealizedState;
|
||||||
|
type CostBasis = CostBasisData;
|
||||||
}
|
}
|
||||||
impl<M: StorageMode> CohortMetricsState for MinimalCohortMetrics<M> {
|
impl<M: StorageMode> CohortMetricsState for MinimalCohortMetrics<M> {
|
||||||
type Realized = MinimalRealizedState;
|
type Realized = MinimalRealizedState;
|
||||||
|
type CostBasis = CostBasisRaw;
|
||||||
}
|
}
|
||||||
impl<M: StorageMode> CohortMetricsState for CoreCohortMetrics<M> {
|
impl<M: StorageMode> CohortMetricsState for CoreCohortMetrics<M> {
|
||||||
type Realized = CoreRealizedState;
|
type Realized = CoreRealizedState;
|
||||||
|
type CostBasis = CostBasisData;
|
||||||
}
|
}
|
||||||
impl<M: StorageMode> CohortMetricsState for BasicCohortMetrics<M> {
|
impl<M: StorageMode> CohortMetricsState for BasicCohortMetrics<M> {
|
||||||
type Realized = RealizedState;
|
type Realized = RealizedState;
|
||||||
|
type CostBasis = CostBasisData;
|
||||||
}
|
}
|
||||||
impl<M: StorageMode> CohortMetricsState for ExtendedCohortMetrics<M> {
|
impl<M: StorageMode> CohortMetricsState for ExtendedCohortMetrics<M> {
|
||||||
type Realized = RealizedState;
|
type Realized = RealizedState;
|
||||||
|
type CostBasis = CostBasisData;
|
||||||
}
|
}
|
||||||
impl<M: StorageMode> CohortMetricsState for ExtendedAdjustedCohortMetrics<M> {
|
impl<M: StorageMode> CohortMetricsState for ExtendedAdjustedCohortMetrics<M> {
|
||||||
type Realized = RealizedState;
|
type Realized = RealizedState;
|
||||||
|
type CostBasis = CostBasisData;
|
||||||
}
|
}
|
||||||
impl<M: StorageMode> CohortMetricsState for AllCohortMetrics<M> {
|
impl<M: StorageMode> CohortMetricsState for AllCohortMetrics<M> {
|
||||||
type Realized = RealizedState;
|
type Realized = RealizedState;
|
||||||
|
type CostBasis = CostBasisData;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait CohortMetricsBase: CohortMetricsState<Realized = RealizedState> + Send + Sync {
|
pub trait CohortMetricsBase: CohortMetricsState<Realized = RealizedState, CostBasis = CostBasisData> + Send + Sync {
|
||||||
type ActivityVecs: ActivityLike;
|
type ActivityVecs: ActivityLike;
|
||||||
type RealizedVecs: RealizedLike;
|
type RealizedVecs: RealizedLike;
|
||||||
type UnrealizedVecs: UnrealizedLike;
|
type UnrealizedVecs: UnrealizedLike;
|
||||||
@@ -134,7 +142,7 @@ pub trait CohortMetricsBase: CohortMetricsState<Realized = RealizedState> + Send
|
|||||||
&mut self,
|
&mut self,
|
||||||
height: Height,
|
height: Height,
|
||||||
height_price: Cents,
|
height_price: Cents,
|
||||||
state: &mut CohortState<RealizedState>,
|
state: &mut CohortState<RealizedState, CostBasisData>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
state.apply_pending();
|
state.apply_pending();
|
||||||
let unrealized_state = state.compute_unrealized_state(height_price);
|
let unrealized_state = state.compute_unrealized_state(height_price);
|
||||||
@@ -154,7 +162,7 @@ pub trait CohortMetricsBase: CohortMetricsState<Realized = RealizedState> + Send
|
|||||||
.min(self.unrealized().min_stateful_height_len())
|
.min(self.unrealized().min_stateful_height_len())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn truncate_push(&mut self, height: Height, state: &CohortState<RealizedState>) -> Result<()> {
|
fn truncate_push(&mut self, height: Height, state: &CohortState<RealizedState, CostBasisData>) -> Result<()> {
|
||||||
self.supply_mut().truncate_push(height, state)?;
|
self.supply_mut().truncate_push(height, state)?;
|
||||||
self.outputs_mut().truncate_push(height, state)?;
|
self.outputs_mut().truncate_push(height, state)?;
|
||||||
self.activity_mut().truncate_push(height, state)?;
|
self.activity_mut().truncate_push(height, state)?;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use brk_traversable::Traversable;
|
|||||||
use brk_types::{Height, Indexes, StoredU64, Version};
|
use brk_types::{Height, Indexes, StoredU64, Version};
|
||||||
use vecdb::{AnyStoredVec, AnyVec, Exit, Rw, StorageMode, WritableVec};
|
use vecdb::{AnyStoredVec, AnyVec, Exit, Rw, StorageMode, WritableVec};
|
||||||
|
|
||||||
use crate::{distribution::state::{CohortState, RealizedOps}, internal::ComputedPerBlock};
|
use crate::{distribution::state::{CohortState, CostBasisOps, RealizedOps}, internal::ComputedPerBlock};
|
||||||
|
|
||||||
use crate::distribution::metrics::ImportConfig;
|
use crate::distribution::metrics::ImportConfig;
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ impl OutputsBase {
|
|||||||
self.utxo_count.height.len()
|
self.utxo_count.height.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn truncate_push(&mut self, height: Height, state: &CohortState<impl RealizedOps>) -> Result<()> {
|
pub(crate) fn truncate_push(&mut self, height: Height, state: &CohortState<impl RealizedOps, impl CostBasisOps>) -> Result<()> {
|
||||||
self.utxo_count
|
self.utxo_count
|
||||||
.height
|
.height
|
||||||
.truncate_push(height, StoredU64::from(state.supply.utxo_count))?;
|
.truncate_push(height, StoredU64::from(state.supply.utxo_count))?;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use vecdb::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
blocks,
|
blocks,
|
||||||
distribution::state::{CohortState, RealizedOps},
|
distribution::state::{CohortState, CostBasisOps, RealizedOps},
|
||||||
internal::{
|
internal::{
|
||||||
AmountPerBlockWithSum24h, ComputedPerBlock, FiatRollingDelta1m, LazyPerBlock,
|
AmountPerBlockWithSum24h, ComputedPerBlock, FiatRollingDelta1m, LazyPerBlock,
|
||||||
NegCentsUnsignedToDollars, PerBlockWithSum24h, RatioCents64,
|
NegCentsUnsignedToDollars, PerBlockWithSum24h, RatioCents64,
|
||||||
@@ -92,7 +92,7 @@ impl RealizedCore {
|
|||||||
.min(self.sent.in_loss.raw.sats.height.len())
|
.min(self.sent.in_loss.raw.sats.height.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn truncate_push(&mut self, height: Height, state: &CohortState<impl RealizedOps>) -> Result<()> {
|
pub(crate) fn truncate_push(&mut self, height: Height, state: &CohortState<impl RealizedOps, impl CostBasisOps>) -> Result<()> {
|
||||||
self.minimal.truncate_push(height, state)?;
|
self.minimal.truncate_push(height, state)?;
|
||||||
self.sent
|
self.sent
|
||||||
.in_profit
|
.in_profit
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use vecdb::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
blocks,
|
blocks,
|
||||||
distribution::state::{CohortState, RealizedState},
|
distribution::state::{CohortState, CostBasisData, RealizedState},
|
||||||
internal::{
|
internal::{
|
||||||
CentsUnsignedToDollars, ComputedPerBlock, ComputedPerBlockCumulative, FiatPerBlock,
|
CentsUnsignedToDollars, ComputedPerBlock, ComputedPerBlockCumulative, FiatPerBlock,
|
||||||
FiatRollingDelta1m, FiatRollingDeltaExcept1m, LazyPerBlock, PercentPerBlock,
|
FiatRollingDelta1m, FiatRollingDeltaExcept1m, LazyPerBlock, PercentPerBlock,
|
||||||
@@ -295,7 +295,7 @@ impl RealizedFull {
|
|||||||
pub(crate) fn truncate_push(
|
pub(crate) fn truncate_push(
|
||||||
&mut self,
|
&mut self,
|
||||||
height: Height,
|
height: Height,
|
||||||
state: &CohortState<RealizedState>,
|
state: &CohortState<RealizedState, CostBasisData>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
self.core.truncate_push(height, state)?;
|
self.core.truncate_push(height, state)?;
|
||||||
self.profit
|
self.profit
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use vecdb::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
blocks,
|
blocks,
|
||||||
distribution::state::{CohortState, RealizedOps},
|
distribution::state::{CohortState, CostBasisOps, RealizedOps},
|
||||||
internal::{
|
internal::{
|
||||||
ComputedPerBlock, FiatPerBlock, FiatPerBlockWithSum24h, Identity, LazyPerBlock,
|
ComputedPerBlock, FiatPerBlock, FiatPerBlockWithSum24h, Identity, LazyPerBlock,
|
||||||
PerBlockWithSum24h, Price, RatioPerBlock,
|
PerBlockWithSum24h, Price, RatioPerBlock,
|
||||||
@@ -83,7 +83,7 @@ impl RealizedMinimal {
|
|||||||
.min(self.sopr.value_destroyed.raw.height.len())
|
.min(self.sopr.value_destroyed.raw.height.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn truncate_push(&mut self, height: Height, state: &CohortState<impl RealizedOps>) -> Result<()> {
|
pub(crate) fn truncate_push(&mut self, height: Height, state: &CohortState<impl RealizedOps, impl CostBasisOps>) -> Result<()> {
|
||||||
self.cap.cents.height.truncate_push(height, state.realized.cap())?;
|
self.cap.cents.height.truncate_push(height, state.realized.cap())?;
|
||||||
self.profit.raw.cents.height.truncate_push(height, state.realized.profit())?;
|
self.profit.raw.cents.height.truncate_push(height, state.realized.profit())?;
|
||||||
self.loss.raw.cents.height.truncate_push(height, state.realized.loss())?;
|
self.loss.raw.cents.height.truncate_push(height, state.realized.loss())?;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use brk_error::Result;
|
|||||||
use brk_types::{Height, Indexes};
|
use brk_types::{Height, Indexes};
|
||||||
use vecdb::Exit;
|
use vecdb::Exit;
|
||||||
|
|
||||||
use crate::{blocks, distribution::state::{CohortState, RealizedState}};
|
use crate::{blocks, distribution::state::{CohortState, CostBasisData, RealizedState}};
|
||||||
|
|
||||||
/// Polymorphic dispatch for realized metric types.
|
/// Polymorphic dispatch for realized metric types.
|
||||||
///
|
///
|
||||||
@@ -23,7 +23,7 @@ pub trait RealizedLike: Send + Sync {
|
|||||||
fn as_core(&self) -> &RealizedCore;
|
fn as_core(&self) -> &RealizedCore;
|
||||||
fn as_core_mut(&mut self) -> &mut RealizedCore;
|
fn as_core_mut(&mut self) -> &mut RealizedCore;
|
||||||
fn min_stateful_height_len(&self) -> usize;
|
fn min_stateful_height_len(&self) -> usize;
|
||||||
fn truncate_push(&mut self, height: Height, state: &CohortState<RealizedState>) -> Result<()>;
|
fn truncate_push(&mut self, height: Height, state: &CohortState<RealizedState, CostBasisData>) -> Result<()>;
|
||||||
fn compute_rest_part1(&mut self, blocks: &blocks::Vecs, starting_indexes: &Indexes, exit: &Exit) -> Result<()>;
|
fn compute_rest_part1(&mut self, blocks: &blocks::Vecs, starting_indexes: &Indexes, exit: &Exit) -> Result<()>;
|
||||||
fn compute_from_stateful(
|
fn compute_from_stateful(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -37,7 +37,7 @@ impl RealizedLike for RealizedCore {
|
|||||||
fn as_core(&self) -> &RealizedCore { self }
|
fn as_core(&self) -> &RealizedCore { self }
|
||||||
fn as_core_mut(&mut self) -> &mut RealizedCore { self }
|
fn as_core_mut(&mut self) -> &mut RealizedCore { self }
|
||||||
fn min_stateful_height_len(&self) -> usize { self.min_stateful_height_len() }
|
fn min_stateful_height_len(&self) -> usize { self.min_stateful_height_len() }
|
||||||
fn truncate_push(&mut self, height: Height, state: &CohortState<RealizedState>) -> Result<()> {
|
fn truncate_push(&mut self, height: Height, state: &CohortState<RealizedState, CostBasisData>) -> Result<()> {
|
||||||
self.truncate_push(height, state)
|
self.truncate_push(height, state)
|
||||||
}
|
}
|
||||||
fn compute_rest_part1(&mut self, blocks: &blocks::Vecs, starting_indexes: &Indexes, exit: &Exit) -> Result<()> {
|
fn compute_rest_part1(&mut self, blocks: &blocks::Vecs, starting_indexes: &Indexes, exit: &Exit) -> Result<()> {
|
||||||
@@ -52,7 +52,7 @@ impl RealizedLike for RealizedFull {
|
|||||||
fn as_core(&self) -> &RealizedCore { &self.core }
|
fn as_core(&self) -> &RealizedCore { &self.core }
|
||||||
fn as_core_mut(&mut self) -> &mut RealizedCore { &mut self.core }
|
fn as_core_mut(&mut self) -> &mut RealizedCore { &mut self.core }
|
||||||
fn min_stateful_height_len(&self) -> usize { self.min_stateful_height_len() }
|
fn min_stateful_height_len(&self) -> usize { self.min_stateful_height_len() }
|
||||||
fn truncate_push(&mut self, height: Height, state: &CohortState<RealizedState>) -> Result<()> {
|
fn truncate_push(&mut self, height: Height, state: &CohortState<RealizedState, CostBasisData>) -> Result<()> {
|
||||||
self.truncate_push(height, state)
|
self.truncate_push(height, state)
|
||||||
}
|
}
|
||||||
fn compute_rest_part1(&mut self, blocks: &blocks::Vecs, starting_indexes: &Indexes, exit: &Exit) -> Result<()> {
|
fn compute_rest_part1(&mut self, blocks: &blocks::Vecs, starting_indexes: &Indexes, exit: &Exit) -> Result<()> {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use brk_traversable::Traversable;
|
|||||||
use brk_types::{Height, Indexes, Version};
|
use brk_types::{Height, Indexes, Version};
|
||||||
use vecdb::{AnyStoredVec, AnyVec, Exit, Rw, StorageMode, WritableVec};
|
use vecdb::{AnyStoredVec, AnyVec, Exit, Rw, StorageMode, WritableVec};
|
||||||
|
|
||||||
use crate::{distribution::state::{CohortState, RealizedOps}, prices};
|
use crate::{distribution::state::{CohortState, CostBasisOps, RealizedOps}, prices};
|
||||||
|
|
||||||
use crate::internal::{
|
use crate::internal::{
|
||||||
AmountPerBlock, HalveCents, HalveDollars, HalveSats, HalveSatsToBitcoin,
|
AmountPerBlock, HalveCents, HalveDollars, HalveSats, HalveSatsToBitcoin,
|
||||||
@@ -40,7 +40,7 @@ impl SupplyBase {
|
|||||||
self.total.sats.height.len()
|
self.total.sats.height.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn truncate_push(&mut self, height: Height, state: &CohortState<impl RealizedOps>) -> Result<()> {
|
pub(crate) fn truncate_push(&mut self, height: Height, state: &CohortState<impl RealizedOps, impl CostBasisOps>) -> Result<()> {
|
||||||
self.total.sats.height.truncate_push(height, state.supply.value)?;
|
self.total.sats.height.truncate_push(height, state.supply.value)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use brk_error::Result;
|
|||||||
use brk_types::{Age, Cents, FundedAddressData, Sats, SupplyState};
|
use brk_types::{Age, Cents, FundedAddressData, Sats, SupplyState};
|
||||||
use vecdb::unlikely;
|
use vecdb::unlikely;
|
||||||
|
|
||||||
use super::super::cost_basis::RealizedOps;
|
use super::super::cost_basis::{CostBasisRaw, RealizedOps};
|
||||||
use super::base::CohortState;
|
use super::base::CohortState;
|
||||||
|
|
||||||
/// Significant digits for address cost basis prices (after rounding to dollars).
|
/// Significant digits for address cost basis prices (after rounding to dollars).
|
||||||
@@ -12,7 +12,7 @@ const COST_BASIS_PRICE_DIGITS: i32 = 4;
|
|||||||
|
|
||||||
pub struct AddressCohortState<R: RealizedOps> {
|
pub struct AddressCohortState<R: RealizedOps> {
|
||||||
pub addr_count: u64,
|
pub addr_count: u64,
|
||||||
pub inner: CohortState<R>,
|
pub inner: CohortState<R, CostBasisRaw>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: RealizedOps> AddressCohortState<R> {
|
impl<R: RealizedOps> AddressCohortState<R> {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use std::{collections::BTreeMap, path::Path};
|
use std::path::Path;
|
||||||
|
|
||||||
use brk_error::Result;
|
use brk_error::Result;
|
||||||
use brk_types::{Age, Cents, CentsCompact, CentsSats, CentsSquaredSats, CostBasisSnapshot, Height, Sats, SupplyState};
|
use brk_types::{Age, Cents, CentsCompact, CentsSats, CentsSquaredSats, CostBasisSnapshot, Height, Sats, SupplyState};
|
||||||
|
|
||||||
use super::super::cost_basis::{CostBasisData, PendingDelta, RealizedOps, UnrealizedState};
|
use super::super::cost_basis::{CostBasisData, CostBasisOps, PendingDelta, RealizedOps, UnrealizedState};
|
||||||
|
|
||||||
pub struct SendPrecomputed {
|
pub struct SendPrecomputed {
|
||||||
pub sats: Sats,
|
pub sats: Sats,
|
||||||
@@ -49,54 +49,50 @@ impl SendPrecomputed {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CohortState<R: RealizedOps> {
|
pub struct CohortState<R: RealizedOps, C: CostBasisOps> {
|
||||||
pub supply: SupplyState,
|
pub supply: SupplyState,
|
||||||
pub realized: R,
|
pub realized: R,
|
||||||
pub sent: Sats,
|
pub sent: Sats,
|
||||||
pub satdays_destroyed: Sats,
|
pub satdays_destroyed: Sats,
|
||||||
cost_basis_data: CostBasisData,
|
cost_basis: C,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R: RealizedOps> CohortState<R> {
|
impl<R: RealizedOps, C: CostBasisOps> CohortState<R, C> {
|
||||||
pub(crate) fn new(path: &Path, name: &str) -> Self {
|
pub(crate) fn new(path: &Path, name: &str) -> Self {
|
||||||
Self {
|
Self {
|
||||||
supply: SupplyState::default(),
|
supply: SupplyState::default(),
|
||||||
realized: R::default(),
|
realized: R::default(),
|
||||||
sent: Sats::ZERO,
|
sent: Sats::ZERO,
|
||||||
satdays_destroyed: Sats::ZERO,
|
satdays_destroyed: Sats::ZERO,
|
||||||
cost_basis_data: CostBasisData::create(path, name),
|
cost_basis: C::create(path, name),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enable price rounding for cost basis data.
|
/// Enable price rounding for cost basis data.
|
||||||
pub(crate) fn with_price_rounding(mut self, digits: i32) -> Self {
|
pub(crate) fn with_price_rounding(mut self, digits: i32) -> Self {
|
||||||
self.cost_basis_data = self.cost_basis_data.with_price_rounding(digits);
|
self.cost_basis = self.cost_basis.with_price_rounding(digits);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn import_at_or_before(&mut self, height: Height) -> Result<Height> {
|
pub(crate) fn import_at_or_before(&mut self, height: Height) -> Result<Height> {
|
||||||
self.cost_basis_data.import_at_or_before(height)
|
self.cost_basis.import_at_or_before(height)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Restore realized cap from cost_basis_data after import.
|
/// Restore realized cap from cost_basis after import.
|
||||||
pub(crate) fn restore_realized_cap(&mut self) {
|
pub(crate) fn restore_realized_cap(&mut self) {
|
||||||
self.realized.set_cap_raw(self.cost_basis_data.cap_raw());
|
self.realized.set_cap_raw(self.cost_basis.cap_raw());
|
||||||
self.realized
|
self.realized
|
||||||
.set_investor_cap_raw(self.cost_basis_data.investor_cap_raw());
|
.set_investor_cap_raw(self.cost_basis.investor_cap_raw());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn reset_cost_basis_data_if_needed(&mut self) -> Result<()> {
|
pub(crate) fn reset_cost_basis_data_if_needed(&mut self) -> Result<()> {
|
||||||
self.cost_basis_data.clean()?;
|
self.cost_basis.clean()?;
|
||||||
self.cost_basis_data.init();
|
self.cost_basis.init();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn for_each_cost_basis_pending(&self, f: impl FnMut(&CentsCompact, &PendingDelta)) {
|
|
||||||
self.cost_basis_data.for_each_pending(f);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn apply_pending(&mut self) {
|
pub(crate) fn apply_pending(&mut self) {
|
||||||
self.cost_basis_data.apply_pending();
|
self.cost_basis.apply_pending();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn reset_single_iteration_values(&mut self) {
|
pub(crate) fn reset_single_iteration_values(&mut self) {
|
||||||
@@ -113,7 +109,7 @@ impl<R: RealizedOps> CohortState<R> {
|
|||||||
if s.supply_state.value > Sats::ZERO {
|
if s.supply_state.value > Sats::ZERO {
|
||||||
self.realized
|
self.realized
|
||||||
.increment_snapshot(s.price_sats, s.investor_cap);
|
.increment_snapshot(s.price_sats, s.investor_cap);
|
||||||
self.cost_basis_data.increment(
|
self.cost_basis.increment(
|
||||||
s.realized_price,
|
s.realized_price,
|
||||||
s.supply_state.value,
|
s.supply_state.value,
|
||||||
s.price_sats,
|
s.price_sats,
|
||||||
@@ -128,7 +124,7 @@ impl<R: RealizedOps> CohortState<R> {
|
|||||||
if s.supply_state.value > Sats::ZERO {
|
if s.supply_state.value > Sats::ZERO {
|
||||||
self.realized
|
self.realized
|
||||||
.decrement_snapshot(s.price_sats, s.investor_cap);
|
.decrement_snapshot(s.price_sats, s.investor_cap);
|
||||||
self.cost_basis_data.decrement(
|
self.cost_basis.decrement(
|
||||||
s.realized_price,
|
s.realized_price,
|
||||||
s.supply_state.value,
|
s.supply_state.value,
|
||||||
s.price_sats,
|
s.price_sats,
|
||||||
@@ -153,7 +149,7 @@ impl<R: RealizedOps> CohortState<R> {
|
|||||||
if supply.value > Sats::ZERO {
|
if supply.value > Sats::ZERO {
|
||||||
self.realized.receive(snapshot.realized_price, supply.value);
|
self.realized.receive(snapshot.realized_price, supply.value);
|
||||||
|
|
||||||
self.cost_basis_data.increment(
|
self.cost_basis.increment(
|
||||||
snapshot.realized_price,
|
snapshot.realized_price,
|
||||||
supply.value,
|
supply.value,
|
||||||
snapshot.price_sats,
|
snapshot.price_sats,
|
||||||
@@ -175,7 +171,7 @@ impl<R: RealizedOps> CohortState<R> {
|
|||||||
self.realized.receive(price, supply.value);
|
self.realized.receive(price, supply.value);
|
||||||
|
|
||||||
if current.supply_state.value.is_not_zero() {
|
if current.supply_state.value.is_not_zero() {
|
||||||
self.cost_basis_data.increment(
|
self.cost_basis.increment(
|
||||||
current.realized_price,
|
current.realized_price,
|
||||||
current.supply_state.value,
|
current.supply_state.value,
|
||||||
current.price_sats,
|
current.price_sats,
|
||||||
@@ -184,7 +180,7 @@ impl<R: RealizedOps> CohortState<R> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if prev.supply_state.value.is_not_zero() {
|
if prev.supply_state.value.is_not_zero() {
|
||||||
self.cost_basis_data.decrement(
|
self.cost_basis.decrement(
|
||||||
prev.realized_price,
|
prev.realized_price,
|
||||||
prev.supply_state.value,
|
prev.supply_state.value,
|
||||||
prev.price_sats,
|
prev.price_sats,
|
||||||
@@ -208,7 +204,7 @@ impl<R: RealizedOps> CohortState<R> {
|
|||||||
self.realized
|
self.realized
|
||||||
.send(pre.sats, pre.current_ps, pre.prev_ps, pre.ath_ps, pre.prev_investor_cap);
|
.send(pre.sats, pre.current_ps, pre.prev_ps, pre.ath_ps, pre.prev_investor_cap);
|
||||||
|
|
||||||
self.cost_basis_data
|
self.cost_basis
|
||||||
.decrement(pre.prev_price, pre.sats, pre.prev_ps, pre.prev_investor_cap);
|
.decrement(pre.prev_price, pre.sats, pre.prev_ps, pre.prev_investor_cap);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,7 +258,7 @@ impl<R: RealizedOps> CohortState<R> {
|
|||||||
.send(sats, current_ps, prev_ps, ath_ps, prev_investor_cap);
|
.send(sats, current_ps, prev_ps, ath_ps, prev_investor_cap);
|
||||||
|
|
||||||
if current.supply_state.value.is_not_zero() {
|
if current.supply_state.value.is_not_zero() {
|
||||||
self.cost_basis_data.increment(
|
self.cost_basis.increment(
|
||||||
current.realized_price,
|
current.realized_price,
|
||||||
current.supply_state.value,
|
current.supply_state.value,
|
||||||
current.price_sats,
|
current.price_sats,
|
||||||
@@ -271,7 +267,7 @@ impl<R: RealizedOps> CohortState<R> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if prev.supply_state.value.is_not_zero() {
|
if prev.supply_state.value.is_not_zero() {
|
||||||
self.cost_basis_data.decrement(
|
self.cost_basis.decrement(
|
||||||
prev.realized_price,
|
prev.realized_price,
|
||||||
prev.supply_state.value,
|
prev.supply_state.value,
|
||||||
prev.price_sats,
|
prev.price_sats,
|
||||||
@@ -281,15 +277,22 @@ impl<R: RealizedOps> CohortState<R> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn compute_unrealized_state(&mut self, height_price: Cents) -> UnrealizedState {
|
|
||||||
self.cost_basis_data.compute_unrealized_state(height_price)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn write(&mut self, height: Height, cleanup: bool) -> Result<()> {
|
pub(crate) fn write(&mut self, height: Height, cleanup: bool) -> Result<()> {
|
||||||
self.cost_basis_data.write(height, cleanup)
|
self.cost_basis.write(height, cleanup)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
pub(crate) fn cost_basis_map(&self) -> &BTreeMap<CentsCompact, Sats> {
|
|
||||||
self.cost_basis_data.map()
|
/// Methods only available with full CostBasisData (map + unrealized).
|
||||||
|
impl<R: RealizedOps> CohortState<R, CostBasisData> {
|
||||||
|
pub(crate) fn compute_unrealized_state(&mut self, height_price: Cents) -> UnrealizedState {
|
||||||
|
self.cost_basis.compute_unrealized_state(height_price)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn for_each_cost_basis_pending(&self, f: impl FnMut(&CentsCompact, &PendingDelta)) {
|
||||||
|
self.cost_basis.for_each_pending(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn cost_basis_map(&self) -> &std::collections::BTreeMap<CentsCompact, Sats> {
|
||||||
|
self.cost_basis.map()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ use brk_error::Result;
|
|||||||
use brk_types::{Sats, SupplyState};
|
use brk_types::{Sats, SupplyState};
|
||||||
use derive_more::{Deref, DerefMut};
|
use derive_more::{Deref, DerefMut};
|
||||||
|
|
||||||
use super::super::cost_basis::RealizedOps;
|
use super::super::cost_basis::{CostBasisOps, RealizedOps};
|
||||||
use super::base::CohortState;
|
use super::base::CohortState;
|
||||||
|
|
||||||
#[derive(Deref, DerefMut)]
|
#[derive(Deref, DerefMut)]
|
||||||
pub struct UTXOCohortState<R: RealizedOps>(pub(crate) CohortState<R>);
|
pub struct UTXOCohortState<R: RealizedOps, C: CostBasisOps>(pub(crate) CohortState<R, C>);
|
||||||
|
|
||||||
impl<R: RealizedOps> UTXOCohortState<R> {
|
impl<R: RealizedOps, C: CostBasisOps> UTXOCohortState<R, C> {
|
||||||
pub(crate) fn new(path: &Path, name: &str) -> Self {
|
pub(crate) fn new(path: &Path, name: &str) -> Self {
|
||||||
Self(CohortState::new(path, name))
|
Self(CohortState::new(path, name))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,15 @@ struct PendingRaw {
|
|||||||
investor_cap_dec: CentsSquaredSats,
|
investor_cap_dec: CentsSquaredSats,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PendingRaw {
|
||||||
|
fn is_zero(&self) -> bool {
|
||||||
|
self.cap_inc == CentsSats::ZERO
|
||||||
|
&& self.cap_dec == CentsSats::ZERO
|
||||||
|
&& self.investor_cap_inc == CentsSquaredSats::ZERO
|
||||||
|
&& self.investor_cap_dec == CentsSquaredSats::ZERO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Pending increments and decrements for a single price bucket.
|
/// Pending increments and decrements for a single price bucket.
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
pub struct PendingDelta {
|
pub struct PendingDelta {
|
||||||
@@ -31,261 +40,79 @@ pub struct PendingDelta {
|
|||||||
pub dec: Sats,
|
pub dec: Sats,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct CostBasisData {
|
|
||||||
pathbuf: PathBuf,
|
|
||||||
state: Option<State>,
|
|
||||||
pending: FxHashMap<CentsCompact, PendingDelta>,
|
|
||||||
pending_raw: PendingRaw,
|
|
||||||
cache: Option<CachedUnrealizedState>,
|
|
||||||
rounding_digits: Option<i32>,
|
|
||||||
/// Monotonically increasing counter, bumped on each apply_pending with actual changes.
|
|
||||||
generation: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
const STATE_TO_KEEP: usize = 10;
|
const STATE_TO_KEEP: usize = 10;
|
||||||
|
|
||||||
impl CostBasisData {
|
/// Common interface for cost basis tracking.
|
||||||
pub(crate) fn create(path: &Path, name: &str) -> Self {
|
///
|
||||||
Self {
|
/// Implemented by `CostBasisRaw` (scalars only) and `CostBasisData` (full map + scalars).
|
||||||
pathbuf: path.join(format!("{name}_cost_basis")),
|
pub trait CostBasisOps: Send + Sync + 'static {
|
||||||
state: None,
|
fn create(path: &Path, name: &str) -> Self;
|
||||||
pending: FxHashMap::default(),
|
fn with_price_rounding(self, digits: i32) -> Self;
|
||||||
pending_raw: PendingRaw::default(),
|
fn import_at_or_before(&mut self, height: Height) -> Result<Height>;
|
||||||
cache: None,
|
fn cap_raw(&self) -> CentsSats;
|
||||||
rounding_digits: None,
|
fn investor_cap_raw(&self) -> CentsSquaredSats;
|
||||||
generation: 0,
|
fn increment(
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn with_price_rounding(mut self, digits: i32) -> Self {
|
|
||||||
self.rounding_digits = Some(digits);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn round_price(&self, price: Cents) -> Cents {
|
|
||||||
match self.rounding_digits {
|
|
||||||
Some(digits) => price.round_to_dollar(digits),
|
|
||||||
None => price,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn import_at_or_before(&mut self, height: Height) -> Result<Height> {
|
|
||||||
let files = self.read_dir(None)?;
|
|
||||||
let (&height, path) = files.range(..=height).next_back().ok_or(Error::NotFound(
|
|
||||||
"No cost basis state found at or before height".into(),
|
|
||||||
))?;
|
|
||||||
self.state = Some(State::deserialize(&fs::read(path)?)?);
|
|
||||||
self.pending.clear();
|
|
||||||
self.pending_raw = PendingRaw::default();
|
|
||||||
self.cache = None;
|
|
||||||
Ok(height)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_pending_empty(&self) {
|
|
||||||
debug_assert!(
|
|
||||||
self.pending.is_empty() && self.pending_raw_is_zero(),
|
|
||||||
"CostBasisData: pending not empty, call apply_pending first"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pending_raw_is_zero(&self) -> bool {
|
|
||||||
self.pending_raw.cap_inc == CentsSats::ZERO
|
|
||||||
&& self.pending_raw.cap_dec == CentsSats::ZERO
|
|
||||||
&& self.pending_raw.investor_cap_inc == CentsSquaredSats::ZERO
|
|
||||||
&& self.pending_raw.investor_cap_dec == CentsSquaredSats::ZERO
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn map(&self) -> &CostBasisMap {
|
|
||||||
self.assert_pending_empty();
|
|
||||||
&self.state.as_ref().unwrap().base.map
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn is_empty(&self) -> bool {
|
|
||||||
self.pending.is_empty() && self.state.as_ref().unwrap().base.map.is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the exact cap_raw value (not recomputed from map).
|
|
||||||
pub(crate) fn cap_raw(&self) -> CentsSats {
|
|
||||||
self.assert_pending_empty();
|
|
||||||
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.as_ref().unwrap().investor_cap_raw
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Increment with pre-computed typed values.
|
|
||||||
/// Handles rounding and cache update.
|
|
||||||
pub(crate) fn increment(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
price: Cents,
|
price: Cents,
|
||||||
sats: Sats,
|
sats: Sats,
|
||||||
price_sats: CentsSats,
|
price_sats: CentsSats,
|
||||||
investor_cap: CentsSquaredSats,
|
investor_cap: CentsSquaredSats,
|
||||||
) {
|
);
|
||||||
let price = self.round_price(price);
|
fn decrement(
|
||||||
self.pending.entry(price.into()).or_default().inc += sats;
|
|
||||||
self.pending_raw.cap_inc += price_sats;
|
|
||||||
if investor_cap != CentsSquaredSats::ZERO {
|
|
||||||
self.pending_raw.investor_cap_inc += investor_cap;
|
|
||||||
}
|
|
||||||
if let Some(cache) = self.cache.as_mut() {
|
|
||||||
cache.on_receive(price, sats);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Decrement with pre-computed typed values.
|
|
||||||
/// Handles rounding and cache update.
|
|
||||||
pub(crate) fn decrement(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
price: Cents,
|
price: Cents,
|
||||||
sats: Sats,
|
sats: Sats,
|
||||||
price_sats: CentsSats,
|
price_sats: CentsSats,
|
||||||
investor_cap: CentsSquaredSats,
|
investor_cap: CentsSquaredSats,
|
||||||
) {
|
);
|
||||||
let price = self.round_price(price);
|
fn apply_pending(&mut self);
|
||||||
self.pending.entry(price.into()).or_default().dec += sats;
|
fn init(&mut self);
|
||||||
self.pending_raw.cap_dec += price_sats;
|
fn clean(&mut self) -> Result<()>;
|
||||||
if investor_cap != CentsSquaredSats::ZERO {
|
fn write(&mut self, height: Height, cleanup: bool) -> Result<()>;
|
||||||
self.pending_raw.investor_cap_dec += investor_cap;
|
}
|
||||||
}
|
|
||||||
if let Some(cache) = self.cache.as_mut() {
|
// ─── CostBasisRaw ───────────────────────────────────────────────────────────
|
||||||
cache.on_send(price, sats);
|
|
||||||
}
|
#[derive(Clone, Default, Debug)]
|
||||||
|
struct RawState {
|
||||||
|
cap_raw: CentsSats,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawState {
|
||||||
|
fn serialize(&self) -> Vec<u8> {
|
||||||
|
self.cap_raw.to_bytes().to_vec()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn for_each_pending(&self, mut f: impl FnMut(&CentsCompact, &PendingDelta)) {
|
fn deserialize(data: &[u8]) -> Result<Self> {
|
||||||
self.pending.iter().for_each(|(k, v)| f(k, v));
|
Ok(Self {
|
||||||
|
cap_raw: CentsSats::from_bytes(&data[0..16])?,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn apply_pending(&mut self) {
|
/// Lightweight cost basis tracking: only cap_raw and investor_cap_raw scalars.
|
||||||
if self.pending.is_empty() {
|
/// No BTreeMap, no unrealized computation, no pending map.
|
||||||
return;
|
/// Used by cohorts that only need realized cap on restart (amount_range, address).
|
||||||
}
|
#[derive(Clone, Debug)]
|
||||||
self.generation = self.generation.wrapping_add(1);
|
pub struct CostBasisRaw {
|
||||||
let map = &mut self.state.as_mut().unwrap().base.map;
|
pathbuf: PathBuf,
|
||||||
for (cents, PendingDelta { inc, dec }) in self.pending.drain() {
|
state: Option<RawState>,
|
||||||
match map.entry(cents) {
|
pending_raw: PendingRaw,
|
||||||
Entry::Occupied(mut e) => {
|
}
|
||||||
*e.get_mut() += inc;
|
|
||||||
if unlikely(*e.get() < dec) {
|
|
||||||
panic!(
|
|
||||||
"CostBasisData::apply_pending underflow!\n\
|
|
||||||
Path: {:?}\n\
|
|
||||||
Price: {}\n\
|
|
||||||
Current + increments: {}\n\
|
|
||||||
Trying to decrement by: {}",
|
|
||||||
self.pathbuf,
|
|
||||||
cents.to_dollars(),
|
|
||||||
e.get(),
|
|
||||||
dec
|
|
||||||
);
|
|
||||||
}
|
|
||||||
*e.get_mut() -= dec;
|
|
||||||
if *e.get() == Sats::ZERO {
|
|
||||||
e.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Entry::Vacant(e) => {
|
|
||||||
if unlikely(inc < dec) {
|
|
||||||
panic!(
|
|
||||||
"CostBasisData::apply_pending underflow (new entry)!\n\
|
|
||||||
Path: {:?}\n\
|
|
||||||
Price: {}\n\
|
|
||||||
Increment: {}\n\
|
|
||||||
Trying to decrement by: {}",
|
|
||||||
self.pathbuf,
|
|
||||||
cents.to_dollars(),
|
|
||||||
inc,
|
|
||||||
dec
|
|
||||||
);
|
|
||||||
}
|
|
||||||
let val = inc - dec;
|
|
||||||
if val != Sats::ZERO {
|
|
||||||
e.insert(val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply raw values
|
impl CostBasisRaw {
|
||||||
let state = self.state.as_mut().unwrap();
|
pub(super) fn path_by_height(&self) -> PathBuf {
|
||||||
state.cap_raw += self.pending_raw.cap_inc;
|
|
||||||
|
|
||||||
// Check for underflow before subtracting
|
|
||||||
if unlikely(state.cap_raw.inner() < self.pending_raw.cap_dec.inner()) {
|
|
||||||
panic!(
|
|
||||||
"CostBasisData::apply_pending cap_raw underflow!\n\
|
|
||||||
Path: {:?}\n\
|
|
||||||
Current cap_raw (after increments): {}\n\
|
|
||||||
Trying to decrement by: {}",
|
|
||||||
self.pathbuf, state.cap_raw, self.pending_raw.cap_dec
|
|
||||||
);
|
|
||||||
}
|
|
||||||
state.cap_raw -= self.pending_raw.cap_dec;
|
|
||||||
|
|
||||||
// Only process investor_cap if there are non-zero values
|
|
||||||
let has_investor_cap = self.pending_raw.investor_cap_inc != CentsSquaredSats::ZERO
|
|
||||||
|| self.pending_raw.investor_cap_dec != CentsSquaredSats::ZERO;
|
|
||||||
|
|
||||||
if has_investor_cap {
|
|
||||||
state.investor_cap_raw += self.pending_raw.investor_cap_inc;
|
|
||||||
|
|
||||||
if unlikely(state.investor_cap_raw.inner() < self.pending_raw.investor_cap_dec.inner()) {
|
|
||||||
panic!(
|
|
||||||
"CostBasisData::apply_pending investor_cap_raw underflow!\n\
|
|
||||||
Path: {:?}\n\
|
|
||||||
Current investor_cap_raw (after increments): {}\n\
|
|
||||||
Trying to decrement by: {}",
|
|
||||||
self.pathbuf, state.investor_cap_raw, self.pending_raw.investor_cap_dec
|
|
||||||
);
|
|
||||||
}
|
|
||||||
state.investor_cap_raw -= self.pending_raw.investor_cap_dec;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.pending_raw = PendingRaw::default();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn init(&mut self) {
|
|
||||||
self.state.replace(State::default());
|
|
||||||
self.pending.clear();
|
|
||||||
self.pending_raw = PendingRaw::default();
|
|
||||||
self.cache = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn compute_unrealized_state(&mut self, height_price: Cents) -> UnrealizedState {
|
|
||||||
if self.is_empty() {
|
|
||||||
return UnrealizedState::ZERO;
|
|
||||||
}
|
|
||||||
|
|
||||||
let map = &self.state.as_ref().unwrap().base.map;
|
|
||||||
|
|
||||||
if let Some(cache) = self.cache.as_mut() {
|
|
||||||
cache.get_at_price(height_price, map)
|
|
||||||
} else {
|
|
||||||
let cache = CachedUnrealizedState::compute_fresh(height_price, map);
|
|
||||||
let state = cache.current_state();
|
|
||||||
self.cache = Some(cache);
|
|
||||||
state
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn clean(&mut self) -> Result<()> {
|
|
||||||
let _ = fs::remove_dir_all(&self.pathbuf);
|
|
||||||
fs::create_dir_all(self.path_by_height())?;
|
|
||||||
self.cache = None;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn path_by_height(&self) -> PathBuf {
|
|
||||||
self.pathbuf.join("by_height")
|
self.pathbuf.join("by_height")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_dir(&self, keep_only_before: Option<Height>) -> Result<BTreeMap<Height, PathBuf>> {
|
pub(super) fn path_state(&self, height: Height) -> PathBuf {
|
||||||
|
self.path_by_height().join(height.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn read_dir(
|
||||||
|
&self,
|
||||||
|
keep_only_before: Option<Height>,
|
||||||
|
) -> Result<BTreeMap<Height, PathBuf>> {
|
||||||
let by_height = self.path_by_height();
|
let by_height = self.path_by_height();
|
||||||
if !by_height.exists() {
|
if !by_height.exists() {
|
||||||
return Ok(BTreeMap::new());
|
return Ok(BTreeMap::new());
|
||||||
@@ -305,15 +132,33 @@ impl CostBasisData {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect::<BTreeMap<Height, PathBuf>>())
|
.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn write(&mut self, height: Height, cleanup: bool) -> Result<()> {
|
fn apply_pending_raw(&mut self) {
|
||||||
self.apply_pending();
|
if self.pending_raw.is_zero() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let state = self.state.as_mut().unwrap();
|
||||||
|
|
||||||
|
state.cap_raw += self.pending_raw.cap_inc;
|
||||||
|
if unlikely(state.cap_raw.inner() < self.pending_raw.cap_dec.inner()) {
|
||||||
|
panic!(
|
||||||
|
"CostBasis cap_raw underflow!\n\
|
||||||
|
Path: {:?}\n\
|
||||||
|
Current cap_raw (after increments): {}\n\
|
||||||
|
Trying to decrement by: {}",
|
||||||
|
self.pathbuf, state.cap_raw, self.pending_raw.cap_dec
|
||||||
|
);
|
||||||
|
}
|
||||||
|
state.cap_raw -= self.pending_raw.cap_dec;
|
||||||
|
|
||||||
|
self.pending_raw = PendingRaw::default();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_and_cleanup(&mut self, height: Height, cleanup: bool) -> Result<()> {
|
||||||
if cleanup {
|
if cleanup {
|
||||||
let files = self.read_dir(Some(height))?;
|
let files = self.read_dir(Some(height))?;
|
||||||
|
|
||||||
for (_, path) in files
|
for (_, path) in files
|
||||||
.iter()
|
.iter()
|
||||||
.take(files.len().saturating_sub(STATE_TO_KEEP - 1))
|
.take(files.len().saturating_sub(STATE_TO_KEEP - 1))
|
||||||
@@ -321,46 +166,309 @@ impl CostBasisData {
|
|||||||
fs::remove_file(path)?;
|
fs::remove_file(path)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fs::write(
|
impl CostBasisOps for CostBasisRaw {
|
||||||
self.path_state(height),
|
fn create(path: &Path, name: &str) -> Self {
|
||||||
self.state.as_ref().unwrap().serialize()?,
|
Self {
|
||||||
)?;
|
pathbuf: path.join(format!("{name}_cost_basis")),
|
||||||
|
state: None,
|
||||||
|
pending_raw: PendingRaw::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_price_rounding(self, _digits: i32) -> Self {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn import_at_or_before(&mut self, height: Height) -> Result<Height> {
|
||||||
|
let files = self.read_dir(None)?;
|
||||||
|
let (&height, path) = files.range(..=height).next_back().ok_or(Error::NotFound(
|
||||||
|
"No cost basis state found at or before height".into(),
|
||||||
|
))?;
|
||||||
|
let data = fs::read(path)?;
|
||||||
|
// Handle both formats: full (map + raw at end) and raw-only (16 bytes).
|
||||||
|
self.state = Some(if data.len() == 16 {
|
||||||
|
RawState::deserialize(&data)?
|
||||||
|
} else {
|
||||||
|
let (_, rest) = CostBasisDistribution::deserialize_with_rest(&data)?;
|
||||||
|
RawState::deserialize(rest)?
|
||||||
|
});
|
||||||
|
self.pending_raw = PendingRaw::default();
|
||||||
|
Ok(height)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cap_raw(&self) -> CentsSats {
|
||||||
|
debug_assert!(self.pending_raw.is_zero());
|
||||||
|
self.state.as_ref().unwrap().cap_raw
|
||||||
|
}
|
||||||
|
|
||||||
|
fn investor_cap_raw(&self) -> CentsSquaredSats {
|
||||||
|
CentsSquaredSats::ZERO
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn increment(
|
||||||
|
&mut self,
|
||||||
|
_price: Cents,
|
||||||
|
_sats: Sats,
|
||||||
|
price_sats: CentsSats,
|
||||||
|
_investor_cap: CentsSquaredSats,
|
||||||
|
) {
|
||||||
|
self.pending_raw.cap_inc += price_sats;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn decrement(
|
||||||
|
&mut self,
|
||||||
|
_price: Cents,
|
||||||
|
_sats: Sats,
|
||||||
|
price_sats: CentsSats,
|
||||||
|
_investor_cap: CentsSquaredSats,
|
||||||
|
) {
|
||||||
|
self.pending_raw.cap_dec += price_sats;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_pending(&mut self) {
|
||||||
|
self.apply_pending_raw();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(&mut self) {
|
||||||
|
self.state.replace(RawState::default());
|
||||||
|
self.pending_raw = PendingRaw::default();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clean(&mut self) -> Result<()> {
|
||||||
|
let _ = fs::remove_dir_all(&self.pathbuf);
|
||||||
|
fs::create_dir_all(self.path_by_height())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn path_state(&self, height: Height) -> PathBuf {
|
fn write(&mut self, height: Height, cleanup: bool) -> Result<()> {
|
||||||
self.path_by_height().join(height.to_string())
|
self.apply_pending_raw();
|
||||||
|
self.write_and_cleanup(height, cleanup)?;
|
||||||
|
fs::write(
|
||||||
|
self.path_state(height),
|
||||||
|
self.state.as_ref().unwrap().serialize(),
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default, Debug)]
|
// ─── CostBasisData ──────────────────────────────────────────────────────────
|
||||||
struct State {
|
|
||||||
base: CostBasisDistribution,
|
/// Full cost basis tracking: BTreeMap distribution + raw scalars.
|
||||||
/// Exact realized cap: Σ(price × sats)
|
/// Composes `CostBasisRaw` for scalar tracking, adds map, pending, and cache.
|
||||||
cap_raw: CentsSats,
|
/// Used by cohorts that need unrealized computation or Fenwick tree.
|
||||||
/// Exact investor cap: Σ(price² × sats)
|
#[derive(Clone, Debug)]
|
||||||
investor_cap_raw: CentsSquaredSats,
|
pub struct CostBasisData {
|
||||||
|
raw: CostBasisRaw,
|
||||||
|
map: Option<CostBasisDistribution>,
|
||||||
|
pending: FxHashMap<CentsCompact, PendingDelta>,
|
||||||
|
cache: Option<CachedUnrealizedState>,
|
||||||
|
rounding_digits: Option<i32>,
|
||||||
|
/// Monotonically increasing counter, bumped on each apply_pending with actual changes.
|
||||||
|
generation: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl CostBasisData {
|
||||||
fn serialize(&self) -> Result<Vec<u8>> {
|
#[inline]
|
||||||
let mut buffer = self.base.serialize()?;
|
fn round_price(&self, price: Cents) -> Cents {
|
||||||
buffer.extend(self.cap_raw.to_bytes());
|
match self.rounding_digits {
|
||||||
buffer.extend(self.investor_cap_raw.to_bytes());
|
Some(digits) => price.round_to_dollar(digits),
|
||||||
Ok(buffer)
|
None => price,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deserialize(data: &[u8]) -> Result<Self> {
|
pub(crate) fn map(&self) -> &CostBasisMap {
|
||||||
let (base, rest) = CostBasisDistribution::deserialize_with_rest(data)?;
|
debug_assert!(self.pending.is_empty() && self.raw.pending_raw.is_zero());
|
||||||
let cap_raw = CentsSats::from_bytes(&rest[0..16])?;
|
&self.map.as_ref().unwrap().map
|
||||||
let investor_cap_raw = CentsSquaredSats::from_bytes(&rest[16..32])?;
|
}
|
||||||
|
|
||||||
Ok(Self {
|
pub(crate) fn is_empty(&self) -> bool {
|
||||||
base,
|
self.pending.is_empty() && self.map.as_ref().unwrap().map.is_empty()
|
||||||
cap_raw,
|
}
|
||||||
investor_cap_raw,
|
|
||||||
})
|
pub(crate) fn for_each_pending(&self, mut f: impl FnMut(&CentsCompact, &PendingDelta)) {
|
||||||
|
self.pending.iter().for_each(|(k, v)| f(k, v));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn compute_unrealized_state(&mut self, height_price: Cents) -> UnrealizedState {
|
||||||
|
if self.is_empty() {
|
||||||
|
return UnrealizedState::ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
|
let map = &self.map.as_ref().unwrap().map;
|
||||||
|
|
||||||
|
if let Some(cache) = self.cache.as_mut() {
|
||||||
|
cache.get_at_price(height_price, map)
|
||||||
|
} else {
|
||||||
|
let cache = CachedUnrealizedState::compute_fresh(height_price, map);
|
||||||
|
let state = cache.current_state();
|
||||||
|
self.cache = Some(cache);
|
||||||
|
state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_map_pending(&mut self) {
|
||||||
|
if self.pending.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.generation = self.generation.wrapping_add(1);
|
||||||
|
let map = &mut self.map.as_mut().unwrap().map;
|
||||||
|
for (cents, PendingDelta { inc, dec }) in self.pending.drain() {
|
||||||
|
match map.entry(cents) {
|
||||||
|
Entry::Occupied(mut e) => {
|
||||||
|
*e.get_mut() += inc;
|
||||||
|
if unlikely(*e.get() < dec) {
|
||||||
|
panic!(
|
||||||
|
"CostBasisData::apply_pending underflow!\n\
|
||||||
|
Path: {:?}\n\
|
||||||
|
Price: {}\n\
|
||||||
|
Current + increments: {}\n\
|
||||||
|
Trying to decrement by: {}",
|
||||||
|
self.raw.pathbuf,
|
||||||
|
cents.to_dollars(),
|
||||||
|
e.get(),
|
||||||
|
dec
|
||||||
|
);
|
||||||
|
}
|
||||||
|
*e.get_mut() -= dec;
|
||||||
|
if *e.get() == Sats::ZERO {
|
||||||
|
e.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Entry::Vacant(e) => {
|
||||||
|
if unlikely(inc < dec) {
|
||||||
|
panic!(
|
||||||
|
"CostBasisData::apply_pending underflow (new entry)!\n\
|
||||||
|
Path: {:?}\n\
|
||||||
|
Price: {}\n\
|
||||||
|
Increment: {}\n\
|
||||||
|
Trying to decrement by: {}",
|
||||||
|
self.raw.pathbuf,
|
||||||
|
cents.to_dollars(),
|
||||||
|
inc,
|
||||||
|
dec
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let val = inc - dec;
|
||||||
|
if val != Sats::ZERO {
|
||||||
|
e.insert(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CostBasisOps for CostBasisData {
|
||||||
|
fn create(path: &Path, name: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
raw: CostBasisRaw::create(path, name),
|
||||||
|
map: None,
|
||||||
|
pending: FxHashMap::default(),
|
||||||
|
cache: None,
|
||||||
|
rounding_digits: None,
|
||||||
|
generation: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_price_rounding(mut self, digits: i32) -> Self {
|
||||||
|
self.rounding_digits = Some(digits);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn import_at_or_before(&mut self, height: Height) -> Result<Height> {
|
||||||
|
let files = self.raw.read_dir(None)?;
|
||||||
|
let (&height, path) = files.range(..=height).next_back().ok_or(Error::NotFound(
|
||||||
|
"No cost basis state found at or before height".into(),
|
||||||
|
))?;
|
||||||
|
let data = fs::read(path)?;
|
||||||
|
let (base, rest) = CostBasisDistribution::deserialize_with_rest(&data)?;
|
||||||
|
self.map = Some(base);
|
||||||
|
self.raw.state = Some(RawState::deserialize(rest)?);
|
||||||
|
self.pending.clear();
|
||||||
|
self.raw.pending_raw = PendingRaw::default();
|
||||||
|
self.cache = None;
|
||||||
|
Ok(height)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cap_raw(&self) -> CentsSats {
|
||||||
|
self.raw.cap_raw()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn investor_cap_raw(&self) -> CentsSquaredSats {
|
||||||
|
self.raw.investor_cap_raw()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn increment(
|
||||||
|
&mut self,
|
||||||
|
price: Cents,
|
||||||
|
sats: Sats,
|
||||||
|
price_sats: CentsSats,
|
||||||
|
investor_cap: CentsSquaredSats,
|
||||||
|
) {
|
||||||
|
let price = self.round_price(price);
|
||||||
|
self.pending.entry(price.into()).or_default().inc += sats;
|
||||||
|
self.raw.pending_raw.cap_inc += price_sats;
|
||||||
|
if investor_cap != CentsSquaredSats::ZERO {
|
||||||
|
self.raw.pending_raw.investor_cap_inc += investor_cap;
|
||||||
|
}
|
||||||
|
if let Some(cache) = self.cache.as_mut() {
|
||||||
|
cache.on_receive(price, sats);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn decrement(
|
||||||
|
&mut self,
|
||||||
|
price: Cents,
|
||||||
|
sats: Sats,
|
||||||
|
price_sats: CentsSats,
|
||||||
|
investor_cap: CentsSquaredSats,
|
||||||
|
) {
|
||||||
|
let price = self.round_price(price);
|
||||||
|
self.pending.entry(price.into()).or_default().dec += sats;
|
||||||
|
self.raw.pending_raw.cap_dec += price_sats;
|
||||||
|
if investor_cap != CentsSquaredSats::ZERO {
|
||||||
|
self.raw.pending_raw.investor_cap_dec += investor_cap;
|
||||||
|
}
|
||||||
|
if let Some(cache) = self.cache.as_mut() {
|
||||||
|
cache.on_send(price, sats);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_pending(&mut self) {
|
||||||
|
self.apply_map_pending();
|
||||||
|
self.raw.apply_pending_raw();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(&mut self) {
|
||||||
|
self.raw.init();
|
||||||
|
self.map.replace(CostBasisDistribution::default());
|
||||||
|
self.pending.clear();
|
||||||
|
self.cache = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clean(&mut self) -> Result<()> {
|
||||||
|
self.raw.clean()?;
|
||||||
|
self.cache = None;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(&mut self, height: Height, cleanup: bool) -> Result<()> {
|
||||||
|
self.apply_pending();
|
||||||
|
self.raw.write_and_cleanup(height, cleanup)?;
|
||||||
|
|
||||||
|
let raw_state = self.raw.state.as_ref().unwrap();
|
||||||
|
let mut buffer = self.map.as_ref().unwrap().serialize()?;
|
||||||
|
buffer.extend(raw_state.cap_raw.to_bytes());
|
||||||
|
fs::write(self.raw.path_state(height), buffer)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2307,6 +2307,35 @@ function create_1m1w1y24hBpsPercentRatioPattern(client, acc) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} CoindaysCoinyearsDormancySentVelocityPattern
|
||||||
|
* @property {CumulativeRawSumPattern<StoredF64>} coindaysDestroyed
|
||||||
|
* @property {MetricPattern1<StoredF32>} coindaysDestroyedSupplyAdjusted
|
||||||
|
* @property {MetricPattern1<StoredF64>} coinyearsDestroyed
|
||||||
|
* @property {MetricPattern1<StoredF32>} coinyearsDestroyedSupplyAdjusted
|
||||||
|
* @property {MetricPattern1<StoredF32>} dormancy
|
||||||
|
* @property {RawSumPattern3<Sats>} sent
|
||||||
|
* @property {MetricPattern1<StoredF32>} velocity
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a CoindaysCoinyearsDormancySentVelocityPattern pattern node
|
||||||
|
* @param {BrkClientBase} client
|
||||||
|
* @param {string} acc - Accumulated metric name
|
||||||
|
* @returns {CoindaysCoinyearsDormancySentVelocityPattern}
|
||||||
|
*/
|
||||||
|
function createCoindaysCoinyearsDormancySentVelocityPattern(client, acc) {
|
||||||
|
return {
|
||||||
|
coindaysDestroyed: createCumulativeRawSumPattern(client, _m(acc, 'coindays_destroyed')),
|
||||||
|
coindaysDestroyedSupplyAdjusted: createMetricPattern1(client, _m(acc, 'coindays_destroyed_supply_adjusted')),
|
||||||
|
coinyearsDestroyed: createMetricPattern1(client, _m(acc, 'coinyears_destroyed')),
|
||||||
|
coinyearsDestroyedSupplyAdjusted: createMetricPattern1(client, _m(acc, 'coinyears_destroyed_supply_adjusted')),
|
||||||
|
dormancy: createMetricPattern1(client, _m(acc, 'dormancy')),
|
||||||
|
sent: createRawSumPattern3(client, _m(acc, 'sent')),
|
||||||
|
velocity: createMetricPattern1(client, _m(acc, 'velocity')),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} CumulativeDistributionRawRelSumValuePattern
|
* @typedef {Object} CumulativeDistributionRawRelSumValuePattern
|
||||||
* @property {MetricPattern1<Cents>} cumulative
|
* @property {MetricPattern1<Cents>} cumulative
|
||||||
@@ -3030,29 +3059,6 @@ function createCentsRelUsdPattern2(client, acc) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {Object} CoindaysDormancySentVelocityPattern
|
|
||||||
* @property {CumulativeRawSumPattern<StoredF64>} coindaysDestroyed
|
|
||||||
* @property {MetricPattern1<StoredF32>} dormancy
|
|
||||||
* @property {RawSumPattern3<Sats>} sent
|
|
||||||
* @property {MetricPattern1<StoredF32>} velocity
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a CoindaysDormancySentVelocityPattern pattern node
|
|
||||||
* @param {BrkClientBase} client
|
|
||||||
* @param {string} acc - Accumulated metric name
|
|
||||||
* @returns {CoindaysDormancySentVelocityPattern}
|
|
||||||
*/
|
|
||||||
function createCoindaysDormancySentVelocityPattern(client, acc) {
|
|
||||||
return {
|
|
||||||
coindaysDestroyed: createCumulativeRawSumPattern(client, _m(acc, 'coindays_destroyed')),
|
|
||||||
dormancy: createMetricPattern1(client, _m(acc, 'dormancy')),
|
|
||||||
sent: createRawSumPattern3(client, _m(acc, 'sent')),
|
|
||||||
velocity: createMetricPattern1(client, _m(acc, 'velocity')),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} CumulativeNegativeRawSumPattern
|
* @typedef {Object} CumulativeNegativeRawSumPattern
|
||||||
* @property {MetricPattern1<Cents>} cumulative
|
* @property {MetricPattern1<Cents>} cumulative
|
||||||
@@ -4750,6 +4756,7 @@ function createRawPattern(client, acc) {
|
|||||||
* @property {CentsUsdPattern} vaultedCap
|
* @property {CentsUsdPattern} vaultedCap
|
||||||
* @property {CentsUsdPattern} activeCap
|
* @property {CentsUsdPattern} activeCap
|
||||||
* @property {CentsUsdPattern} cointimeCap
|
* @property {CentsUsdPattern} cointimeCap
|
||||||
|
* @property {BpsRatioPattern} aviv
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -5859,7 +5866,7 @@ function createRawPattern(client, acc) {
|
|||||||
* @typedef {Object} MetricsTree_Distribution_UtxoCohorts_All
|
* @typedef {Object} MetricsTree_Distribution_UtxoCohorts_All
|
||||||
* @property {MetricsTree_Distribution_UtxoCohorts_All_Supply} supply
|
* @property {MetricsTree_Distribution_UtxoCohorts_All_Supply} supply
|
||||||
* @property {UtxoPattern3} outputs
|
* @property {UtxoPattern3} outputs
|
||||||
* @property {CoindaysDormancySentVelocityPattern} activity
|
* @property {CoindaysCoinyearsDormancySentVelocityPattern} activity
|
||||||
* @property {CapGrossInvestorLossMvrvNetNuplPeakPriceProfitSentSoprPattern} realized
|
* @property {CapGrossInvestorLossMvrvNetNuplPeakPriceProfitSentSoprPattern} realized
|
||||||
* @property {InvestedMaxMinPercentilesPattern} costBasis
|
* @property {InvestedMaxMinPercentilesPattern} costBasis
|
||||||
* @property {MetricsTree_Distribution_UtxoCohorts_All_Unrealized} unrealized
|
* @property {MetricsTree_Distribution_UtxoCohorts_All_Unrealized} unrealized
|
||||||
@@ -5914,7 +5921,7 @@ function createRawPattern(client, acc) {
|
|||||||
* @property {CapGrossInvestorLossMvrvNetNuplPeakPriceProfitSentSoprPattern} realized
|
* @property {CapGrossInvestorLossMvrvNetNuplPeakPriceProfitSentSoprPattern} realized
|
||||||
* @property {DeltaHalvedRelTotalPattern2} supply
|
* @property {DeltaHalvedRelTotalPattern2} supply
|
||||||
* @property {UtxoPattern3} outputs
|
* @property {UtxoPattern3} outputs
|
||||||
* @property {CoindaysDormancySentVelocityPattern} activity
|
* @property {CoindaysCoinyearsDormancySentVelocityPattern} activity
|
||||||
* @property {InvestedMaxMinPercentilesPattern} costBasis
|
* @property {InvestedMaxMinPercentilesPattern} costBasis
|
||||||
* @property {GrossInvestedInvestorLossNetProfitSentimentPattern2} unrealized
|
* @property {GrossInvestedInvestorLossNetProfitSentimentPattern2} unrealized
|
||||||
*/
|
*/
|
||||||
@@ -5923,7 +5930,7 @@ function createRawPattern(client, acc) {
|
|||||||
* @typedef {Object} MetricsTree_Distribution_UtxoCohorts_Lth
|
* @typedef {Object} MetricsTree_Distribution_UtxoCohorts_Lth
|
||||||
* @property {DeltaHalvedRelTotalPattern2} supply
|
* @property {DeltaHalvedRelTotalPattern2} supply
|
||||||
* @property {UtxoPattern3} outputs
|
* @property {UtxoPattern3} outputs
|
||||||
* @property {CoindaysDormancySentVelocityPattern} activity
|
* @property {CoindaysCoinyearsDormancySentVelocityPattern} activity
|
||||||
* @property {MetricsTree_Distribution_UtxoCohorts_Lth_Realized} realized
|
* @property {MetricsTree_Distribution_UtxoCohorts_Lth_Realized} realized
|
||||||
* @property {InvestedMaxMinPercentilesPattern} costBasis
|
* @property {InvestedMaxMinPercentilesPattern} costBasis
|
||||||
* @property {GrossInvestedInvestorLossNetProfitSentimentPattern2} unrealized
|
* @property {GrossInvestedInvestorLossNetProfitSentimentPattern2} unrealized
|
||||||
@@ -7580,6 +7587,7 @@ class BrkClient extends BrkClientBase {
|
|||||||
vaultedCap: createCentsUsdPattern(this, 'vaulted_cap'),
|
vaultedCap: createCentsUsdPattern(this, 'vaulted_cap'),
|
||||||
activeCap: createCentsUsdPattern(this, 'active_cap'),
|
activeCap: createCentsUsdPattern(this, 'active_cap'),
|
||||||
cointimeCap: createCentsUsdPattern(this, 'cointime_cap'),
|
cointimeCap: createCentsUsdPattern(this, 'cointime_cap'),
|
||||||
|
aviv: createBpsRatioPattern(this, 'aviv_ratio'),
|
||||||
},
|
},
|
||||||
pricing: {
|
pricing: {
|
||||||
vaultedPrice: createCentsSatsUsdPattern(this, 'vaulted_price'),
|
vaultedPrice: createCentsSatsUsdPattern(this, 'vaulted_price'),
|
||||||
@@ -8349,7 +8357,7 @@ class BrkClient extends BrkClientBase {
|
|||||||
halved: createBtcCentsSatsUsdPattern(this, 'supply_halved'),
|
halved: createBtcCentsSatsUsdPattern(this, 'supply_halved'),
|
||||||
},
|
},
|
||||||
outputs: createUtxoPattern3(this, 'utxo_count'),
|
outputs: createUtxoPattern3(this, 'utxo_count'),
|
||||||
activity: createCoindaysDormancySentVelocityPattern(this, ''),
|
activity: createCoindaysCoinyearsDormancySentVelocityPattern(this, ''),
|
||||||
realized: createCapGrossInvestorLossMvrvNetNuplPeakPriceProfitSentSoprPattern(this, ''),
|
realized: createCapGrossInvestorLossMvrvNetNuplPeakPriceProfitSentSoprPattern(this, ''),
|
||||||
costBasis: createInvestedMaxMinPercentilesPattern(this, ''),
|
costBasis: createInvestedMaxMinPercentilesPattern(this, ''),
|
||||||
unrealized: {
|
unrealized: {
|
||||||
@@ -8383,14 +8391,14 @@ class BrkClient extends BrkClientBase {
|
|||||||
realized: createCapGrossInvestorLossMvrvNetNuplPeakPriceProfitSentSoprPattern(this, 'sth'),
|
realized: createCapGrossInvestorLossMvrvNetNuplPeakPriceProfitSentSoprPattern(this, 'sth'),
|
||||||
supply: createDeltaHalvedRelTotalPattern2(this, 'sth_supply'),
|
supply: createDeltaHalvedRelTotalPattern2(this, 'sth_supply'),
|
||||||
outputs: createUtxoPattern3(this, 'sth_utxo_count'),
|
outputs: createUtxoPattern3(this, 'sth_utxo_count'),
|
||||||
activity: createCoindaysDormancySentVelocityPattern(this, 'sth'),
|
activity: createCoindaysCoinyearsDormancySentVelocityPattern(this, 'sth'),
|
||||||
costBasis: createInvestedMaxMinPercentilesPattern(this, 'sth'),
|
costBasis: createInvestedMaxMinPercentilesPattern(this, 'sth'),
|
||||||
unrealized: createGrossInvestedInvestorLossNetProfitSentimentPattern2(this, 'sth'),
|
unrealized: createGrossInvestedInvestorLossNetProfitSentimentPattern2(this, 'sth'),
|
||||||
},
|
},
|
||||||
lth: {
|
lth: {
|
||||||
supply: createDeltaHalvedRelTotalPattern2(this, 'lth_supply'),
|
supply: createDeltaHalvedRelTotalPattern2(this, 'lth_supply'),
|
||||||
outputs: createUtxoPattern3(this, 'lth_utxo_count'),
|
outputs: createUtxoPattern3(this, 'lth_utxo_count'),
|
||||||
activity: createCoindaysDormancySentVelocityPattern(this, 'lth'),
|
activity: createCoindaysCoinyearsDormancySentVelocityPattern(this, 'lth'),
|
||||||
realized: {
|
realized: {
|
||||||
profit: createCumulativeDistributionRawRelSumValuePattern(this, 'lth'),
|
profit: createCumulativeDistributionRawRelSumValuePattern(this, 'lth'),
|
||||||
loss: createCapitulationCumulativeNegativeRawRelSumValuePattern(this, 'lth'),
|
loss: createCapitulationCumulativeNegativeRawRelSumValuePattern(this, 'lth'),
|
||||||
|
|||||||
@@ -2423,6 +2423,19 @@ class _1m1w1y24hBpsPercentRatioPattern:
|
|||||||
self.percent: MetricPattern1[StoredF32] = MetricPattern1(client, acc)
|
self.percent: MetricPattern1[StoredF32] = MetricPattern1(client, acc)
|
||||||
self.ratio: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'ratio'))
|
self.ratio: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'ratio'))
|
||||||
|
|
||||||
|
class CoindaysCoinyearsDormancySentVelocityPattern:
|
||||||
|
"""Pattern struct for repeated tree structure."""
|
||||||
|
|
||||||
|
def __init__(self, client: BrkClientBase, acc: str):
|
||||||
|
"""Create pattern node with accumulated metric name."""
|
||||||
|
self.coindays_destroyed: CumulativeRawSumPattern[StoredF64] = CumulativeRawSumPattern(client, _m(acc, 'coindays_destroyed'))
|
||||||
|
self.coindays_destroyed_supply_adjusted: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'coindays_destroyed_supply_adjusted'))
|
||||||
|
self.coinyears_destroyed: MetricPattern1[StoredF64] = MetricPattern1(client, _m(acc, 'coinyears_destroyed'))
|
||||||
|
self.coinyears_destroyed_supply_adjusted: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'coinyears_destroyed_supply_adjusted'))
|
||||||
|
self.dormancy: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'dormancy'))
|
||||||
|
self.sent: RawSumPattern3[Sats] = RawSumPattern3(client, _m(acc, 'sent'))
|
||||||
|
self.velocity: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'velocity'))
|
||||||
|
|
||||||
class CumulativeDistributionRawRelSumValuePattern:
|
class CumulativeDistributionRawRelSumValuePattern:
|
||||||
"""Pattern struct for repeated tree structure."""
|
"""Pattern struct for repeated tree structure."""
|
||||||
|
|
||||||
@@ -2740,16 +2753,6 @@ class CentsRelUsdPattern2:
|
|||||||
self.rel_to_own_market_cap: BpsPercentRatioPattern = BpsPercentRatioPattern(client, _m(acc, 'rel_to_own_market_cap'))
|
self.rel_to_own_market_cap: BpsPercentRatioPattern = BpsPercentRatioPattern(client, _m(acc, 'rel_to_own_market_cap'))
|
||||||
self.usd: MetricPattern1[Dollars] = MetricPattern1(client, acc)
|
self.usd: MetricPattern1[Dollars] = MetricPattern1(client, acc)
|
||||||
|
|
||||||
class CoindaysDormancySentVelocityPattern:
|
|
||||||
"""Pattern struct for repeated tree structure."""
|
|
||||||
|
|
||||||
def __init__(self, client: BrkClientBase, acc: str):
|
|
||||||
"""Create pattern node with accumulated metric name."""
|
|
||||||
self.coindays_destroyed: CumulativeRawSumPattern[StoredF64] = CumulativeRawSumPattern(client, _m(acc, 'coindays_destroyed'))
|
|
||||||
self.dormancy: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'dormancy'))
|
|
||||||
self.sent: RawSumPattern3[Sats] = RawSumPattern3(client, _m(acc, 'sent'))
|
|
||||||
self.velocity: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'velocity'))
|
|
||||||
|
|
||||||
class CumulativeNegativeRawSumPattern:
|
class CumulativeNegativeRawSumPattern:
|
||||||
"""Pattern struct for repeated tree structure."""
|
"""Pattern struct for repeated tree structure."""
|
||||||
|
|
||||||
@@ -3704,6 +3707,7 @@ class MetricsTree_Cointime_Cap:
|
|||||||
self.vaulted_cap: CentsUsdPattern = CentsUsdPattern(client, 'vaulted_cap')
|
self.vaulted_cap: CentsUsdPattern = CentsUsdPattern(client, 'vaulted_cap')
|
||||||
self.active_cap: CentsUsdPattern = CentsUsdPattern(client, 'active_cap')
|
self.active_cap: CentsUsdPattern = CentsUsdPattern(client, 'active_cap')
|
||||||
self.cointime_cap: CentsUsdPattern = CentsUsdPattern(client, 'cointime_cap')
|
self.cointime_cap: CentsUsdPattern = CentsUsdPattern(client, 'cointime_cap')
|
||||||
|
self.aviv: BpsRatioPattern = BpsRatioPattern(client, 'aviv_ratio')
|
||||||
|
|
||||||
class MetricsTree_Cointime_Pricing:
|
class MetricsTree_Cointime_Pricing:
|
||||||
"""Metrics tree node."""
|
"""Metrics tree node."""
|
||||||
@@ -4946,7 +4950,7 @@ class MetricsTree_Distribution_UtxoCohorts_All:
|
|||||||
def __init__(self, client: BrkClientBase, base_path: str = ''):
|
def __init__(self, client: BrkClientBase, base_path: str = ''):
|
||||||
self.supply: MetricsTree_Distribution_UtxoCohorts_All_Supply = MetricsTree_Distribution_UtxoCohorts_All_Supply(client)
|
self.supply: MetricsTree_Distribution_UtxoCohorts_All_Supply = MetricsTree_Distribution_UtxoCohorts_All_Supply(client)
|
||||||
self.outputs: UtxoPattern3 = UtxoPattern3(client, 'utxo_count')
|
self.outputs: UtxoPattern3 = UtxoPattern3(client, 'utxo_count')
|
||||||
self.activity: CoindaysDormancySentVelocityPattern = CoindaysDormancySentVelocityPattern(client, '')
|
self.activity: CoindaysCoinyearsDormancySentVelocityPattern = CoindaysCoinyearsDormancySentVelocityPattern(client, '')
|
||||||
self.realized: CapGrossInvestorLossMvrvNetNuplPeakPriceProfitSentSoprPattern = CapGrossInvestorLossMvrvNetNuplPeakPriceProfitSentSoprPattern(client, '')
|
self.realized: CapGrossInvestorLossMvrvNetNuplPeakPriceProfitSentSoprPattern = CapGrossInvestorLossMvrvNetNuplPeakPriceProfitSentSoprPattern(client, '')
|
||||||
self.cost_basis: InvestedMaxMinPercentilesPattern = InvestedMaxMinPercentilesPattern(client, '')
|
self.cost_basis: InvestedMaxMinPercentilesPattern = InvestedMaxMinPercentilesPattern(client, '')
|
||||||
self.unrealized: MetricsTree_Distribution_UtxoCohorts_All_Unrealized = MetricsTree_Distribution_UtxoCohorts_All_Unrealized(client)
|
self.unrealized: MetricsTree_Distribution_UtxoCohorts_All_Unrealized = MetricsTree_Distribution_UtxoCohorts_All_Unrealized(client)
|
||||||
@@ -4958,7 +4962,7 @@ class MetricsTree_Distribution_UtxoCohorts_Sth:
|
|||||||
self.realized: CapGrossInvestorLossMvrvNetNuplPeakPriceProfitSentSoprPattern = CapGrossInvestorLossMvrvNetNuplPeakPriceProfitSentSoprPattern(client, 'sth')
|
self.realized: CapGrossInvestorLossMvrvNetNuplPeakPriceProfitSentSoprPattern = CapGrossInvestorLossMvrvNetNuplPeakPriceProfitSentSoprPattern(client, 'sth')
|
||||||
self.supply: DeltaHalvedRelTotalPattern2 = DeltaHalvedRelTotalPattern2(client, 'sth_supply')
|
self.supply: DeltaHalvedRelTotalPattern2 = DeltaHalvedRelTotalPattern2(client, 'sth_supply')
|
||||||
self.outputs: UtxoPattern3 = UtxoPattern3(client, 'sth_utxo_count')
|
self.outputs: UtxoPattern3 = UtxoPattern3(client, 'sth_utxo_count')
|
||||||
self.activity: CoindaysDormancySentVelocityPattern = CoindaysDormancySentVelocityPattern(client, 'sth')
|
self.activity: CoindaysCoinyearsDormancySentVelocityPattern = CoindaysCoinyearsDormancySentVelocityPattern(client, 'sth')
|
||||||
self.cost_basis: InvestedMaxMinPercentilesPattern = InvestedMaxMinPercentilesPattern(client, 'sth')
|
self.cost_basis: InvestedMaxMinPercentilesPattern = InvestedMaxMinPercentilesPattern(client, 'sth')
|
||||||
self.unrealized: GrossInvestedInvestorLossNetProfitSentimentPattern2 = GrossInvestedInvestorLossNetProfitSentimentPattern2(client, 'sth')
|
self.unrealized: GrossInvestedInvestorLossNetProfitSentimentPattern2 = GrossInvestedInvestorLossNetProfitSentimentPattern2(client, 'sth')
|
||||||
|
|
||||||
@@ -5009,7 +5013,7 @@ class MetricsTree_Distribution_UtxoCohorts_Lth:
|
|||||||
def __init__(self, client: BrkClientBase, base_path: str = ''):
|
def __init__(self, client: BrkClientBase, base_path: str = ''):
|
||||||
self.supply: DeltaHalvedRelTotalPattern2 = DeltaHalvedRelTotalPattern2(client, 'lth_supply')
|
self.supply: DeltaHalvedRelTotalPattern2 = DeltaHalvedRelTotalPattern2(client, 'lth_supply')
|
||||||
self.outputs: UtxoPattern3 = UtxoPattern3(client, 'lth_utxo_count')
|
self.outputs: UtxoPattern3 = UtxoPattern3(client, 'lth_utxo_count')
|
||||||
self.activity: CoindaysDormancySentVelocityPattern = CoindaysDormancySentVelocityPattern(client, 'lth')
|
self.activity: CoindaysCoinyearsDormancySentVelocityPattern = CoindaysCoinyearsDormancySentVelocityPattern(client, 'lth')
|
||||||
self.realized: MetricsTree_Distribution_UtxoCohorts_Lth_Realized = MetricsTree_Distribution_UtxoCohorts_Lth_Realized(client)
|
self.realized: MetricsTree_Distribution_UtxoCohorts_Lth_Realized = MetricsTree_Distribution_UtxoCohorts_Lth_Realized(client)
|
||||||
self.cost_basis: InvestedMaxMinPercentilesPattern = InvestedMaxMinPercentilesPattern(client, 'lth')
|
self.cost_basis: InvestedMaxMinPercentilesPattern = InvestedMaxMinPercentilesPattern(client, 'lth')
|
||||||
self.unrealized: GrossInvestedInvestorLossNetProfitSentimentPattern2 = GrossInvestedInvestorLossNetProfitSentimentPattern2(client, 'lth')
|
self.unrealized: GrossInvestedInvestorLossNetProfitSentimentPattern2 = GrossInvestedInvestorLossNetProfitSentimentPattern2(client, 'lth')
|
||||||
|
|||||||
Reference in New Issue
Block a user