global: add cohorts by entry

This commit is contained in:
nym21
2026-06-14 00:40:18 +02:00
parent 297fc3b855
commit c85da92cbc
44 changed files with 1927 additions and 19567 deletions
+4 -3
View File
@@ -6,9 +6,9 @@
use std::collections::BTreeMap;
use brk_cohort::{
AGE_RANGE_NAMES, AMOUNT_RANGE_NAMES, CLASS_NAMES, EPOCH_NAMES, LOSS_NAMES, OVER_AGE_NAMES,
OVER_AMOUNT_NAMES, PROFIT_NAMES, PROFITABILITY_RANGE_NAMES, SPENDABLE_TYPE_NAMES, TERM_NAMES,
UNDER_AGE_NAMES, UNDER_AMOUNT_NAMES,
AGE_RANGE_NAMES, AMOUNT_RANGE_NAMES, CLASS_NAMES, ENTRY_NAMES, EPOCH_NAMES, LOSS_NAMES,
OVER_AGE_NAMES, OVER_AMOUNT_NAMES, PROFIT_NAMES, PROFITABILITY_RANGE_NAMES,
SPENDABLE_TYPE_NAMES, TERM_NAMES, UNDER_AGE_NAMES, UNDER_AMOUNT_NAMES,
};
use brk_types::{Index, pools};
use serde::Serialize;
@@ -59,6 +59,7 @@ impl CohortConstants {
("TERM_NAMES", to_value(&TERM_NAMES)),
("EPOCH_NAMES", to_value(&EPOCH_NAMES)),
("CLASS_NAMES", to_value(&CLASS_NAMES)),
("ENTRY_NAMES", to_value(&ENTRY_NAMES)),
("SPENDABLE_TYPE_NAMES", to_value(&SPENDABLE_TYPE_NAMES)),
("AGE_RANGE_NAMES", to_value(&AGE_RANGE_NAMES)),
("UNDER_AGE_NAMES", to_value(&UNDER_AGE_NAMES)),
+603 -18
View File
@@ -1191,6 +1191,22 @@ pub struct CapCapitalizedGrossLossMvrvNetPeakPriceProfitSellSoprPattern {
pub sopr: AdjustedRatioValuePattern,
}
/// Pattern struct for repeated tree structure.
pub struct CapCapitalizedGrossLossMvrvNetPeakPriceProfitSellSoprPattern2 {
pub cap: CentsDeltaToUsdPattern,
pub capitalized: PricePattern,
pub gross_pnl: BlockCumulativeSumPattern,
pub loss: BlockCumulativeNegativeSumPattern,
pub mvrv: SeriesPattern1<StoredF32>,
pub net_pnl: BlockChangeCumulativeDeltaSumPattern,
pub peak_regret: BlockCumulativeSumPattern,
pub price: BpsCentsPercentilesRatioSatsSmaStdUsdPattern,
pub profit: BlockCumulativeSumPattern,
pub profit_to_loss_ratio: _1m1w1y24hPattern<StoredF64>,
pub sell_side_risk_ratio: _1m1w1y24hPattern8,
pub sopr: RatioValuePattern2,
}
/// Pattern struct for repeated tree structure.
pub struct EmptyOpP2aP2msP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshUnknownPattern2 {
pub empty: _1m1w1y24hBpsPercentRatioPattern,
@@ -1658,6 +1674,17 @@ pub struct ActiveInputOutputSpendablePattern {
pub spendable_output_to_reused_addr_share: _1m1w1y24hBpsPercentRatioPattern,
}
/// Pattern struct for repeated tree structure.
pub struct ActivityCostInvestedOutputsRealizedSupplyUnrealizedPattern2 {
pub activity: CoindaysCoinyearsDormancyTransferPattern,
pub cost_basis: InMaxMinPerSupplyPattern,
pub invested_capital: InPattern,
pub outputs: SpendingSpentUnspentPattern,
pub realized: CapCapitalizedGrossLossMvrvNetPeakPriceProfitSellSoprPattern2,
pub supply: DeltaDominanceHalfInTotalPattern2,
pub unrealized: CapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2,
}
/// Pattern struct for repeated tree structure.
pub struct CapLossMvrvNetPriceProfitSoprPattern {
pub cap: CentsDeltaUsdPattern,
@@ -3408,6 +3435,22 @@ impl PriceRatioPattern {
}
}
/// Pattern struct for repeated tree structure.
pub struct RatioValuePattern2 {
pub ratio: _1m1w1y24hPattern<StoredF64>,
pub value_destroyed: AverageBlockCumulativeSumPattern<Cents>,
}
impl RatioValuePattern2 {
/// Create a new pattern node with accumulated series name.
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
Self {
ratio: _1m1w1y24hPattern::new(client.clone(), _m(&acc, "sopr")),
value_destroyed: AverageBlockCumulativeSumPattern::new(client.clone(), _m(&acc, "value_destroyed")),
}
}
}
/// Pattern struct for repeated tree structure.
pub struct RatioValuePattern {
pub ratio: _24hPattern,
@@ -7199,6 +7242,7 @@ pub struct SeriesTree_Cohorts_Utxo {
pub over_age: SeriesTree_Cohorts_Utxo_OverAge,
pub epoch: SeriesTree_Cohorts_Utxo_Epoch,
pub class: SeriesTree_Cohorts_Utxo_Class,
pub entry: SeriesTree_Cohorts_Utxo_Entry,
pub over_amount: SeriesTree_Cohorts_Utxo_OverAmount,
pub amount_range: SeriesTree_Cohorts_Utxo_AmountRange,
pub under_amount: SeriesTree_Cohorts_Utxo_UnderAmount,
@@ -7218,6 +7262,7 @@ impl SeriesTree_Cohorts_Utxo {
over_age: SeriesTree_Cohorts_Utxo_OverAge::new(client.clone(), format!("{base_path}_over_age")),
epoch: SeriesTree_Cohorts_Utxo_Epoch::new(client.clone(), format!("{base_path}_epoch")),
class: SeriesTree_Cohorts_Utxo_Class::new(client.clone(), format!("{base_path}_class")),
entry: SeriesTree_Cohorts_Utxo_Entry::new(client.clone(), format!("{base_path}_entry")),
over_amount: SeriesTree_Cohorts_Utxo_OverAmount::new(client.clone(), format!("{base_path}_over_amount")),
amount_range: SeriesTree_Cohorts_Utxo_AmountRange::new(client.clone(), format!("{base_path}_amount_range")),
under_amount: SeriesTree_Cohorts_Utxo_UnderAmount::new(client.clone(), format!("{base_path}_under_amount")),
@@ -7999,7 +8044,7 @@ pub struct SeriesTree_Cohorts_Utxo_Lth_Realized {
pub price: SeriesTree_Cohorts_Utxo_Lth_Realized_Price,
pub mvrv: SeriesPattern1<StoredF32>,
pub net_pnl: BlockChangeCumulativeDeltaSumPattern,
pub sopr: SeriesTree_Cohorts_Utxo_Lth_Realized_Sopr,
pub sopr: RatioValuePattern2,
pub gross_pnl: BlockCumulativeSumPattern,
pub sell_side_risk_ratio: _1m1w1y24hPattern8,
pub peak_regret: BlockCumulativeSumPattern,
@@ -8016,7 +8061,7 @@ impl SeriesTree_Cohorts_Utxo_Lth_Realized {
price: SeriesTree_Cohorts_Utxo_Lth_Realized_Price::new(client.clone(), format!("{base_path}_price")),
mvrv: SeriesPattern1::new(client.clone(), "lth_mvrv".to_string()),
net_pnl: BlockChangeCumulativeDeltaSumPattern::new(client.clone(), "lth_net".to_string()),
sopr: SeriesTree_Cohorts_Utxo_Lth_Realized_Sopr::new(client.clone(), format!("{base_path}_sopr")),
sopr: RatioValuePattern2::new(client.clone(), "lth".to_string()),
gross_pnl: BlockCumulativeSumPattern::new(client.clone(), "lth_realized_gross_pnl".to_string()),
sell_side_risk_ratio: _1m1w1y24hPattern8::new(client.clone(), "lth_sell_side_risk_ratio".to_string()),
peak_regret: BlockCumulativeSumPattern::new(client.clone(), "lth_realized_peak_regret".to_string()),
@@ -8236,21 +8281,6 @@ impl SeriesTree_Cohorts_Utxo_Lth_Realized_Price_StdDev_1y {
}
}
/// Series tree node.
pub struct SeriesTree_Cohorts_Utxo_Lth_Realized_Sopr {
pub value_destroyed: AverageBlockCumulativeSumPattern<Cents>,
pub ratio: _1m1w1y24hPattern<StoredF64>,
}
impl SeriesTree_Cohorts_Utxo_Lth_Realized_Sopr {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
value_destroyed: AverageBlockCumulativeSumPattern::new(client.clone(), "lth_value_destroyed".to_string()),
ratio: _1m1w1y24hPattern::new(client.clone(), "lth_sopr".to_string()),
}
}
}
/// Series tree node.
pub struct SeriesTree_Cohorts_Utxo_AgeRange {
pub under_1h: ActivityOutputsRealizedSupplyUnrealizedPattern,
@@ -8466,6 +8496,561 @@ impl SeriesTree_Cohorts_Utxo_Class {
}
}
/// Series tree node.
pub struct SeriesTree_Cohorts_Utxo_Entry {
pub discount: SeriesTree_Cohorts_Utxo_Entry_Discount,
pub premium: SeriesTree_Cohorts_Utxo_Entry_Premium,
}
impl SeriesTree_Cohorts_Utxo_Entry {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
discount: SeriesTree_Cohorts_Utxo_Entry_Discount::new(client.clone(), format!("{base_path}_discount")),
premium: SeriesTree_Cohorts_Utxo_Entry_Premium::new(client.clone(), format!("{base_path}_premium")),
}
}
}
/// Series tree node.
pub struct SeriesTree_Cohorts_Utxo_Entry_Discount {
pub supply: DeltaDominanceHalfInTotalPattern2,
pub outputs: SpendingSpentUnspentPattern,
pub activity: CoindaysCoinyearsDormancyTransferPattern,
pub realized: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized,
pub cost_basis: InMaxMinPerSupplyPattern,
pub unrealized: CapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2,
pub invested_capital: InPattern,
}
impl SeriesTree_Cohorts_Utxo_Entry_Discount {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
supply: DeltaDominanceHalfInTotalPattern2::new(client.clone(), "veteran_supply".to_string()),
outputs: SpendingSpentUnspentPattern::new(client.clone(), "veteran".to_string()),
activity: CoindaysCoinyearsDormancyTransferPattern::new(client.clone(), "veteran".to_string()),
realized: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized::new(client.clone(), format!("{base_path}_realized")),
cost_basis: InMaxMinPerSupplyPattern::new(client.clone(), "veteran".to_string()),
unrealized: CapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2::new(client.clone(), "veteran".to_string()),
invested_capital: InPattern::new(client.clone(), "veteran_invested_capital_in".to_string()),
}
}
}
/// Series tree node.
pub struct SeriesTree_Cohorts_Utxo_Entry_Discount_Realized {
pub cap: CentsDeltaToUsdPattern,
pub profit: BlockCumulativeSumPattern,
pub loss: BlockCumulativeNegativeSumPattern,
pub price: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price,
pub mvrv: SeriesPattern1<StoredF32>,
pub net_pnl: BlockChangeCumulativeDeltaSumPattern,
pub sopr: RatioValuePattern2,
pub gross_pnl: BlockCumulativeSumPattern,
pub sell_side_risk_ratio: _1m1w1y24hPattern8,
pub peak_regret: BlockCumulativeSumPattern,
pub capitalized: PricePattern,
pub profit_to_loss_ratio: _1m1w1y24hPattern<StoredF64>,
}
impl SeriesTree_Cohorts_Utxo_Entry_Discount_Realized {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
cap: CentsDeltaToUsdPattern::new(client.clone(), "veteran_realized_cap".to_string()),
profit: BlockCumulativeSumPattern::new(client.clone(), "veteran_realized_profit".to_string()),
loss: BlockCumulativeNegativeSumPattern::new(client.clone(), "veteran_realized_loss".to_string()),
price: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price::new(client.clone(), format!("{base_path}_price")),
mvrv: SeriesPattern1::new(client.clone(), "veteran_mvrv".to_string()),
net_pnl: BlockChangeCumulativeDeltaSumPattern::new(client.clone(), "veteran_net".to_string()),
sopr: RatioValuePattern2::new(client.clone(), "veteran".to_string()),
gross_pnl: BlockCumulativeSumPattern::new(client.clone(), "veteran_realized_gross_pnl".to_string()),
sell_side_risk_ratio: _1m1w1y24hPattern8::new(client.clone(), "veteran_sell_side_risk_ratio".to_string()),
peak_regret: BlockCumulativeSumPattern::new(client.clone(), "veteran_realized_peak_regret".to_string()),
capitalized: PricePattern::new(client.clone(), "veteran_capitalized_price".to_string()),
profit_to_loss_ratio: _1m1w1y24hPattern::new(client.clone(), "veteran_realized_profit_to_loss_ratio".to_string()),
}
}
}
/// Series tree node.
pub struct SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price {
pub usd: SeriesPattern1<Dollars>,
pub cents: SeriesPattern1<Cents>,
pub sats: SeriesPattern1<SatsFract>,
pub bps: SeriesPattern1<BasisPoints32>,
pub ratio: SeriesPattern1<StoredF32>,
pub percentiles: Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern,
pub sma: _1m1w1y2y4yAllPattern,
pub std_dev: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev,
}
impl SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
usd: SeriesPattern1::new(client.clone(), "veteran_realized_price".to_string()),
cents: SeriesPattern1::new(client.clone(), "veteran_realized_price_cents".to_string()),
sats: SeriesPattern1::new(client.clone(), "veteran_realized_price_sats".to_string()),
bps: SeriesPattern1::new(client.clone(), "veteran_realized_price_ratio_bps".to_string()),
ratio: SeriesPattern1::new(client.clone(), "veteran_realized_price_ratio".to_string()),
percentiles: Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern::new(client.clone(), "veteran_realized_price".to_string()),
sma: _1m1w1y2y4yAllPattern::new(client.clone(), "veteran_realized_price_ratio_sma".to_string()),
std_dev: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev::new(client.clone(), format!("{base_path}_std_dev")),
}
}
}
/// Series tree node.
pub struct SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev {
pub all: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_All,
pub _4y: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_4y,
pub _2y: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_2y,
pub _1y: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_1y,
}
impl SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
all: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_All::new(client.clone(), format!("{base_path}_all")),
_4y: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_4y::new(client.clone(), format!("{base_path}_4y")),
_2y: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_2y::new(client.clone(), format!("{base_path}_2y")),
_1y: SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_1y::new(client.clone(), format!("{base_path}_1y")),
}
}
}
/// Series tree node.
pub struct SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_All {
pub sd: SeriesPattern1<StoredF32>,
pub zscore: SeriesPattern1<StoredF32>,
pub _0sd: CentsSatsUsdPattern,
pub p0_5sd: PriceRatioPattern,
pub p1sd: PriceRatioPattern,
pub p1_5sd: PriceRatioPattern,
pub p2sd: PriceRatioPattern,
pub p2_5sd: PriceRatioPattern,
pub p3sd: PriceRatioPattern,
pub m0_5sd: PriceRatioPattern,
pub m1sd: PriceRatioPattern,
pub m1_5sd: PriceRatioPattern,
pub m2sd: PriceRatioPattern,
pub m2_5sd: PriceRatioPattern,
pub m3sd: PriceRatioPattern,
}
impl SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_All {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
sd: SeriesPattern1::new(client.clone(), "veteran_realized_price_ratio_sd".to_string()),
zscore: SeriesPattern1::new(client.clone(), "veteran_realized_price_ratio_zscore".to_string()),
_0sd: CentsSatsUsdPattern::new(client.clone(), "veteran_realized_price_0sd".to_string()),
p0_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p0_5sd".to_string()),
p1sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p1sd".to_string()),
p1_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p1_5sd".to_string()),
p2sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p2sd".to_string()),
p2_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p2_5sd".to_string()),
p3sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p3sd".to_string()),
m0_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m0_5sd".to_string()),
m1sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m1sd".to_string()),
m1_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m1_5sd".to_string()),
m2sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m2sd".to_string()),
m2_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m2_5sd".to_string()),
m3sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m3sd".to_string()),
}
}
}
/// Series tree node.
pub struct SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_4y {
pub sd: SeriesPattern1<StoredF32>,
pub zscore: SeriesPattern1<StoredF32>,
pub _0sd: CentsSatsUsdPattern,
pub p0_5sd: PriceRatioPattern,
pub p1sd: PriceRatioPattern,
pub p1_5sd: PriceRatioPattern,
pub p2sd: PriceRatioPattern,
pub p2_5sd: PriceRatioPattern,
pub p3sd: PriceRatioPattern,
pub m0_5sd: PriceRatioPattern,
pub m1sd: PriceRatioPattern,
pub m1_5sd: PriceRatioPattern,
pub m2sd: PriceRatioPattern,
pub m2_5sd: PriceRatioPattern,
pub m3sd: PriceRatioPattern,
}
impl SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_4y {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
sd: SeriesPattern1::new(client.clone(), "veteran_realized_price_ratio_sd_4y".to_string()),
zscore: SeriesPattern1::new(client.clone(), "veteran_realized_price_ratio_zscore_4y".to_string()),
_0sd: CentsSatsUsdPattern::new(client.clone(), "veteran_realized_price_0sd_4y".to_string()),
p0_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p0_5sd_4y".to_string()),
p1sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p1sd_4y".to_string()),
p1_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p1_5sd_4y".to_string()),
p2sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p2sd_4y".to_string()),
p2_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p2_5sd_4y".to_string()),
p3sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p3sd_4y".to_string()),
m0_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m0_5sd_4y".to_string()),
m1sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m1sd_4y".to_string()),
m1_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m1_5sd_4y".to_string()),
m2sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m2sd_4y".to_string()),
m2_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m2_5sd_4y".to_string()),
m3sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m3sd_4y".to_string()),
}
}
}
/// Series tree node.
pub struct SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_2y {
pub sd: SeriesPattern1<StoredF32>,
pub zscore: SeriesPattern1<StoredF32>,
pub _0sd: CentsSatsUsdPattern,
pub p0_5sd: PriceRatioPattern,
pub p1sd: PriceRatioPattern,
pub p1_5sd: PriceRatioPattern,
pub p2sd: PriceRatioPattern,
pub p2_5sd: PriceRatioPattern,
pub p3sd: PriceRatioPattern,
pub m0_5sd: PriceRatioPattern,
pub m1sd: PriceRatioPattern,
pub m1_5sd: PriceRatioPattern,
pub m2sd: PriceRatioPattern,
pub m2_5sd: PriceRatioPattern,
pub m3sd: PriceRatioPattern,
}
impl SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_2y {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
sd: SeriesPattern1::new(client.clone(), "veteran_realized_price_ratio_sd_2y".to_string()),
zscore: SeriesPattern1::new(client.clone(), "veteran_realized_price_ratio_zscore_2y".to_string()),
_0sd: CentsSatsUsdPattern::new(client.clone(), "veteran_realized_price_0sd_2y".to_string()),
p0_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p0_5sd_2y".to_string()),
p1sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p1sd_2y".to_string()),
p1_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p1_5sd_2y".to_string()),
p2sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p2sd_2y".to_string()),
p2_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p2_5sd_2y".to_string()),
p3sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p3sd_2y".to_string()),
m0_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m0_5sd_2y".to_string()),
m1sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m1sd_2y".to_string()),
m1_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m1_5sd_2y".to_string()),
m2sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m2sd_2y".to_string()),
m2_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m2_5sd_2y".to_string()),
m3sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m3sd_2y".to_string()),
}
}
}
/// Series tree node.
pub struct SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_1y {
pub sd: SeriesPattern1<StoredF32>,
pub zscore: SeriesPattern1<StoredF32>,
pub _0sd: CentsSatsUsdPattern,
pub p0_5sd: PriceRatioPattern,
pub p1sd: PriceRatioPattern,
pub p1_5sd: PriceRatioPattern,
pub p2sd: PriceRatioPattern,
pub p2_5sd: PriceRatioPattern,
pub p3sd: PriceRatioPattern,
pub m0_5sd: PriceRatioPattern,
pub m1sd: PriceRatioPattern,
pub m1_5sd: PriceRatioPattern,
pub m2sd: PriceRatioPattern,
pub m2_5sd: PriceRatioPattern,
pub m3sd: PriceRatioPattern,
}
impl SeriesTree_Cohorts_Utxo_Entry_Discount_Realized_Price_StdDev_1y {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
sd: SeriesPattern1::new(client.clone(), "veteran_realized_price_ratio_sd_1y".to_string()),
zscore: SeriesPattern1::new(client.clone(), "veteran_realized_price_ratio_zscore_1y".to_string()),
_0sd: CentsSatsUsdPattern::new(client.clone(), "veteran_realized_price_0sd_1y".to_string()),
p0_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p0_5sd_1y".to_string()),
p1sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p1sd_1y".to_string()),
p1_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p1_5sd_1y".to_string()),
p2sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p2sd_1y".to_string()),
p2_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p2_5sd_1y".to_string()),
p3sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "p3sd_1y".to_string()),
m0_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m0_5sd_1y".to_string()),
m1sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m1sd_1y".to_string()),
m1_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m1_5sd_1y".to_string()),
m2sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m2sd_1y".to_string()),
m2_5sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m2_5sd_1y".to_string()),
m3sd: PriceRatioPattern::new(client.clone(), "veteran_realized_price".to_string(), "m3sd_1y".to_string()),
}
}
}
/// Series tree node.
pub struct SeriesTree_Cohorts_Utxo_Entry_Premium {
pub supply: DeltaDominanceHalfInTotalPattern2,
pub outputs: SpendingSpentUnspentPattern,
pub activity: CoindaysCoinyearsDormancyTransferPattern,
pub realized: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized,
pub cost_basis: InMaxMinPerSupplyPattern,
pub unrealized: CapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2,
pub invested_capital: InPattern,
}
impl SeriesTree_Cohorts_Utxo_Entry_Premium {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
supply: DeltaDominanceHalfInTotalPattern2::new(client.clone(), "rookie_supply".to_string()),
outputs: SpendingSpentUnspentPattern::new(client.clone(), "rookie".to_string()),
activity: CoindaysCoinyearsDormancyTransferPattern::new(client.clone(), "rookie".to_string()),
realized: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized::new(client.clone(), format!("{base_path}_realized")),
cost_basis: InMaxMinPerSupplyPattern::new(client.clone(), "rookie".to_string()),
unrealized: CapitalizedGrossInvestedLossNetNuplProfitSentimentPattern2::new(client.clone(), "rookie".to_string()),
invested_capital: InPattern::new(client.clone(), "rookie_invested_capital_in".to_string()),
}
}
}
/// Series tree node.
pub struct SeriesTree_Cohorts_Utxo_Entry_Premium_Realized {
pub cap: CentsDeltaToUsdPattern,
pub profit: BlockCumulativeSumPattern,
pub loss: BlockCumulativeNegativeSumPattern,
pub price: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price,
pub mvrv: SeriesPattern1<StoredF32>,
pub net_pnl: BlockChangeCumulativeDeltaSumPattern,
pub sopr: RatioValuePattern2,
pub gross_pnl: BlockCumulativeSumPattern,
pub sell_side_risk_ratio: _1m1w1y24hPattern8,
pub peak_regret: BlockCumulativeSumPattern,
pub capitalized: PricePattern,
pub profit_to_loss_ratio: _1m1w1y24hPattern<StoredF64>,
}
impl SeriesTree_Cohorts_Utxo_Entry_Premium_Realized {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
cap: CentsDeltaToUsdPattern::new(client.clone(), "rookie_realized_cap".to_string()),
profit: BlockCumulativeSumPattern::new(client.clone(), "rookie_realized_profit".to_string()),
loss: BlockCumulativeNegativeSumPattern::new(client.clone(), "rookie_realized_loss".to_string()),
price: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price::new(client.clone(), format!("{base_path}_price")),
mvrv: SeriesPattern1::new(client.clone(), "rookie_mvrv".to_string()),
net_pnl: BlockChangeCumulativeDeltaSumPattern::new(client.clone(), "rookie_net".to_string()),
sopr: RatioValuePattern2::new(client.clone(), "rookie".to_string()),
gross_pnl: BlockCumulativeSumPattern::new(client.clone(), "rookie_realized_gross_pnl".to_string()),
sell_side_risk_ratio: _1m1w1y24hPattern8::new(client.clone(), "rookie_sell_side_risk_ratio".to_string()),
peak_regret: BlockCumulativeSumPattern::new(client.clone(), "rookie_realized_peak_regret".to_string()),
capitalized: PricePattern::new(client.clone(), "rookie_capitalized_price".to_string()),
profit_to_loss_ratio: _1m1w1y24hPattern::new(client.clone(), "rookie_realized_profit_to_loss_ratio".to_string()),
}
}
}
/// Series tree node.
pub struct SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price {
pub usd: SeriesPattern1<Dollars>,
pub cents: SeriesPattern1<Cents>,
pub sats: SeriesPattern1<SatsFract>,
pub bps: SeriesPattern1<BasisPoints32>,
pub ratio: SeriesPattern1<StoredF32>,
pub percentiles: Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern,
pub sma: _1m1w1y2y4yAllPattern,
pub std_dev: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev,
}
impl SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
usd: SeriesPattern1::new(client.clone(), "rookie_realized_price".to_string()),
cents: SeriesPattern1::new(client.clone(), "rookie_realized_price_cents".to_string()),
sats: SeriesPattern1::new(client.clone(), "rookie_realized_price_sats".to_string()),
bps: SeriesPattern1::new(client.clone(), "rookie_realized_price_ratio_bps".to_string()),
ratio: SeriesPattern1::new(client.clone(), "rookie_realized_price_ratio".to_string()),
percentiles: Pct0Pct1Pct2Pct5Pct95Pct98Pct99Pattern::new(client.clone(), "rookie_realized_price".to_string()),
sma: _1m1w1y2y4yAllPattern::new(client.clone(), "rookie_realized_price_ratio_sma".to_string()),
std_dev: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev::new(client.clone(), format!("{base_path}_std_dev")),
}
}
}
/// Series tree node.
pub struct SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev {
pub all: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_All,
pub _4y: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_4y,
pub _2y: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_2y,
pub _1y: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_1y,
}
impl SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
all: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_All::new(client.clone(), format!("{base_path}_all")),
_4y: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_4y::new(client.clone(), format!("{base_path}_4y")),
_2y: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_2y::new(client.clone(), format!("{base_path}_2y")),
_1y: SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_1y::new(client.clone(), format!("{base_path}_1y")),
}
}
}
/// Series tree node.
pub struct SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_All {
pub sd: SeriesPattern1<StoredF32>,
pub zscore: SeriesPattern1<StoredF32>,
pub _0sd: CentsSatsUsdPattern,
pub p0_5sd: PriceRatioPattern,
pub p1sd: PriceRatioPattern,
pub p1_5sd: PriceRatioPattern,
pub p2sd: PriceRatioPattern,
pub p2_5sd: PriceRatioPattern,
pub p3sd: PriceRatioPattern,
pub m0_5sd: PriceRatioPattern,
pub m1sd: PriceRatioPattern,
pub m1_5sd: PriceRatioPattern,
pub m2sd: PriceRatioPattern,
pub m2_5sd: PriceRatioPattern,
pub m3sd: PriceRatioPattern,
}
impl SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_All {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
sd: SeriesPattern1::new(client.clone(), "rookie_realized_price_ratio_sd".to_string()),
zscore: SeriesPattern1::new(client.clone(), "rookie_realized_price_ratio_zscore".to_string()),
_0sd: CentsSatsUsdPattern::new(client.clone(), "rookie_realized_price_0sd".to_string()),
p0_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p0_5sd".to_string()),
p1sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p1sd".to_string()),
p1_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p1_5sd".to_string()),
p2sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p2sd".to_string()),
p2_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p2_5sd".to_string()),
p3sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p3sd".to_string()),
m0_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m0_5sd".to_string()),
m1sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m1sd".to_string()),
m1_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m1_5sd".to_string()),
m2sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m2sd".to_string()),
m2_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m2_5sd".to_string()),
m3sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m3sd".to_string()),
}
}
}
/// Series tree node.
pub struct SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_4y {
pub sd: SeriesPattern1<StoredF32>,
pub zscore: SeriesPattern1<StoredF32>,
pub _0sd: CentsSatsUsdPattern,
pub p0_5sd: PriceRatioPattern,
pub p1sd: PriceRatioPattern,
pub p1_5sd: PriceRatioPattern,
pub p2sd: PriceRatioPattern,
pub p2_5sd: PriceRatioPattern,
pub p3sd: PriceRatioPattern,
pub m0_5sd: PriceRatioPattern,
pub m1sd: PriceRatioPattern,
pub m1_5sd: PriceRatioPattern,
pub m2sd: PriceRatioPattern,
pub m2_5sd: PriceRatioPattern,
pub m3sd: PriceRatioPattern,
}
impl SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_4y {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
sd: SeriesPattern1::new(client.clone(), "rookie_realized_price_ratio_sd_4y".to_string()),
zscore: SeriesPattern1::new(client.clone(), "rookie_realized_price_ratio_zscore_4y".to_string()),
_0sd: CentsSatsUsdPattern::new(client.clone(), "rookie_realized_price_0sd_4y".to_string()),
p0_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p0_5sd_4y".to_string()),
p1sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p1sd_4y".to_string()),
p1_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p1_5sd_4y".to_string()),
p2sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p2sd_4y".to_string()),
p2_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p2_5sd_4y".to_string()),
p3sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p3sd_4y".to_string()),
m0_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m0_5sd_4y".to_string()),
m1sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m1sd_4y".to_string()),
m1_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m1_5sd_4y".to_string()),
m2sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m2sd_4y".to_string()),
m2_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m2_5sd_4y".to_string()),
m3sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m3sd_4y".to_string()),
}
}
}
/// Series tree node.
pub struct SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_2y {
pub sd: SeriesPattern1<StoredF32>,
pub zscore: SeriesPattern1<StoredF32>,
pub _0sd: CentsSatsUsdPattern,
pub p0_5sd: PriceRatioPattern,
pub p1sd: PriceRatioPattern,
pub p1_5sd: PriceRatioPattern,
pub p2sd: PriceRatioPattern,
pub p2_5sd: PriceRatioPattern,
pub p3sd: PriceRatioPattern,
pub m0_5sd: PriceRatioPattern,
pub m1sd: PriceRatioPattern,
pub m1_5sd: PriceRatioPattern,
pub m2sd: PriceRatioPattern,
pub m2_5sd: PriceRatioPattern,
pub m3sd: PriceRatioPattern,
}
impl SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_2y {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
sd: SeriesPattern1::new(client.clone(), "rookie_realized_price_ratio_sd_2y".to_string()),
zscore: SeriesPattern1::new(client.clone(), "rookie_realized_price_ratio_zscore_2y".to_string()),
_0sd: CentsSatsUsdPattern::new(client.clone(), "rookie_realized_price_0sd_2y".to_string()),
p0_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p0_5sd_2y".to_string()),
p1sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p1sd_2y".to_string()),
p1_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p1_5sd_2y".to_string()),
p2sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p2sd_2y".to_string()),
p2_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p2_5sd_2y".to_string()),
p3sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p3sd_2y".to_string()),
m0_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m0_5sd_2y".to_string()),
m1sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m1sd_2y".to_string()),
m1_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m1_5sd_2y".to_string()),
m2sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m2sd_2y".to_string()),
m2_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m2_5sd_2y".to_string()),
m3sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m3sd_2y".to_string()),
}
}
}
/// Series tree node.
pub struct SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_1y {
pub sd: SeriesPattern1<StoredF32>,
pub zscore: SeriesPattern1<StoredF32>,
pub _0sd: CentsSatsUsdPattern,
pub p0_5sd: PriceRatioPattern,
pub p1sd: PriceRatioPattern,
pub p1_5sd: PriceRatioPattern,
pub p2sd: PriceRatioPattern,
pub p2_5sd: PriceRatioPattern,
pub p3sd: PriceRatioPattern,
pub m0_5sd: PriceRatioPattern,
pub m1sd: PriceRatioPattern,
pub m1_5sd: PriceRatioPattern,
pub m2sd: PriceRatioPattern,
pub m2_5sd: PriceRatioPattern,
pub m3sd: PriceRatioPattern,
}
impl SeriesTree_Cohorts_Utxo_Entry_Premium_Realized_Price_StdDev_1y {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
sd: SeriesPattern1::new(client.clone(), "rookie_realized_price_ratio_sd_1y".to_string()),
zscore: SeriesPattern1::new(client.clone(), "rookie_realized_price_ratio_zscore_1y".to_string()),
_0sd: CentsSatsUsdPattern::new(client.clone(), "rookie_realized_price_0sd_1y".to_string()),
p0_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p0_5sd_1y".to_string()),
p1sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p1sd_1y".to_string()),
p1_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p1_5sd_1y".to_string()),
p2sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p2sd_1y".to_string()),
p2_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p2_5sd_1y".to_string()),
p3sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "p3sd_1y".to_string()),
m0_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m0_5sd_1y".to_string()),
m1sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m1sd_1y".to_string()),
m1_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m1_5sd_1y".to_string()),
m2sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m2sd_1y".to_string()),
m2_5sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m2_5sd_1y".to_string()),
m3sd: PriceRatioPattern::new(client.clone(), "rookie_realized_price".to_string(), "m3sd_1y".to_string()),
}
}
}
/// Series tree node.
pub struct SeriesTree_Cohorts_Utxo_OverAmount {
pub _1sat: ActivityOutputsRealizedSupplyUnrealizedPattern2,
@@ -8953,7 +9538,7 @@ pub struct BrkClient {
impl BrkClient {
/// Client version.
pub const VERSION: &'static str = "v0.3.2";
pub const VERSION: &'static str = "v0.3.3";
/// Create a new client with the given base URL.
pub fn new(base_url: impl Into<String>) -> Self {
+104
View File
@@ -0,0 +1,104 @@
use brk_traversable::Traversable;
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use serde::Serialize;
use super::{CohortName, Filter};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EntryPrice {
Discount,
Premium,
}
impl EntryPrice {
#[inline]
pub const fn from_is_discount(is_discount: bool) -> Self {
if is_discount {
Self::Discount
} else {
Self::Premium
}
}
#[inline]
pub const fn is_discount(self) -> bool {
matches!(self, Self::Discount)
}
}
pub const ENTRY_FILTERS: ByEntry<Filter> = ByEntry {
discount: Filter::Entry(EntryPrice::Discount),
premium: Filter::Entry(EntryPrice::Premium),
};
pub const ENTRY_NAMES: ByEntry<CohortName> = ByEntry {
discount: CohortName::new("veteran", "Veteran", "Veteran Coins"),
premium: CohortName::new("rookie", "Rookie", "Rookie Coins"),
};
#[derive(Default, Clone, Traversable, Serialize)]
pub struct ByEntry<T> {
pub discount: T,
pub premium: T,
}
impl ByEntry<CohortName> {
pub const fn names() -> &'static Self {
&ENTRY_NAMES
}
}
impl<T> ByEntry<T> {
pub fn new<F>(mut create: F) -> Self
where
F: FnMut(Filter, &'static str) -> T,
{
let f = ENTRY_FILTERS;
let n = ENTRY_NAMES;
Self {
discount: create(f.discount, n.discount.id),
premium: create(f.premium, n.premium.id),
}
}
pub fn try_new<F, E>(mut create: F) -> Result<Self, E>
where
F: FnMut(Filter, &'static str) -> Result<T, E>,
{
let f = ENTRY_FILTERS;
let n = ENTRY_NAMES;
Ok(Self {
discount: create(f.discount, n.discount.id)?,
premium: create(f.premium, n.premium.id)?,
})
}
pub fn get(&self, entry: EntryPrice) -> &T {
match entry {
EntryPrice::Discount => &self.discount,
EntryPrice::Premium => &self.premium,
}
}
pub fn get_mut(&mut self, entry: EntryPrice) -> &mut T {
match entry {
EntryPrice::Discount => &mut self.discount,
EntryPrice::Premium => &mut self.premium,
}
}
pub fn iter(&self) -> impl Iterator<Item = &T> {
[&self.discount, &self.premium].into_iter()
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> {
[&mut self.discount, &mut self.premium].into_iter()
}
pub fn par_iter_mut(&mut self) -> impl ParallelIterator<Item = &mut T>
where
T: Send + Sync,
{
[&mut self.discount, &mut self.premium].into_par_iter()
}
}
+2 -1
View File
@@ -24,7 +24,7 @@ impl CohortContext {
/// Build full name for a filter, adding prefix only for Time/Amount filters.
///
/// Prefix rules:
/// - No prefix: `All`, `Term`, `Epoch`, `Class`, `Type`
/// - No prefix: `All`, `Term`, `Epoch`, `Class`, `Entry`, `Type`
/// - Context prefix: `Time`, `Amount`
pub fn full_name(&self, filter: &Filter, name: &str) -> String {
match filter {
@@ -32,6 +32,7 @@ impl CohortContext {
| Filter::Term(_)
| Filter::Epoch(_)
| Filter::Class(_)
| Filter::Entry(_)
| Filter::Type(_) => name.to_string(),
Filter::Time(_) | Filter::Amount(_) => self.prefixed(name),
}
+8 -3
View File
@@ -1,6 +1,6 @@
use brk_types::{Halving, OutputType, Sats, Year};
use super::{AmountFilter, CohortContext, Term, TimeFilter};
use super::{AmountFilter, CohortContext, EntryPrice, Term, TimeFilter};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Filter {
@@ -10,6 +10,7 @@ pub enum Filter {
Amount(AmountFilter),
Epoch(Halving),
Class(Year),
Entry(EntryPrice),
Type(OutputType),
}
@@ -68,7 +69,8 @@ impl Filter {
}
/// Whether to compute extended metrics (realized cap ratios, profit/loss ratios, percentiles)
/// For UTXO context: true only for age range cohorts (Range) and aggregate cohorts (All, Term)
/// For UTXO context: true for age range cohorts (Range), aggregate cohorts (All, Term),
/// and immutable entry valuation cohorts.
/// For address context: always false
pub fn is_extended(&self, context: CohortContext) -> bool {
match context {
@@ -76,7 +78,10 @@ impl Filter {
CohortContext::Utxo => {
matches!(
self,
Filter::All | Filter::Term(_) | Filter::Time(TimeFilter::Range(_))
Filter::All
| Filter::Term(_)
| Filter::Time(TimeFilter::Range(_))
| Filter::Entry(_)
)
}
}
+2
View File
@@ -7,6 +7,7 @@ mod amount_range;
mod by_addr_type;
mod by_any_addr;
mod by_epoch;
mod by_entry;
mod by_term;
mod by_type;
mod class;
@@ -36,6 +37,7 @@ pub use amount_range::*;
pub use by_addr_type::*;
pub use by_any_addr::*;
pub use by_epoch::*;
pub use by_entry::*;
pub use by_term::*;
pub use by_type::*;
pub use class::*;
+10 -2
View File
@@ -2,8 +2,8 @@ use brk_traversable::Traversable;
use rayon::prelude::*;
use crate::{
AgeRange, AmountRange, ByEpoch, ByTerm, Class, Filter, OverAge, OverAmount, SpendableType,
UnderAge, UnderAmount,
AgeRange, AmountRange, ByEntry, ByEpoch, ByTerm, Class, Filter, OverAge, OverAmount,
SpendableType, UnderAge, UnderAmount,
};
#[derive(Default, Clone, Traversable)]
@@ -12,6 +12,7 @@ pub struct UTXOGroups<T> {
pub age_range: AgeRange<T>,
pub epoch: ByEpoch<T>,
pub class: Class<T>,
pub entry: ByEntry<T>,
pub over_age: OverAge<T>,
pub over_amount: OverAmount<T>,
pub amount_range: AmountRange<T>,
@@ -31,6 +32,7 @@ impl<T> UTXOGroups<T> {
age_range: AgeRange::new(&mut create),
epoch: ByEpoch::new(&mut create),
class: Class::new(&mut create),
entry: ByEntry::new(&mut create),
over_age: OverAge::new(&mut create),
over_amount: OverAmount::new(&mut create),
amount_range: AmountRange::new(&mut create),
@@ -51,6 +53,7 @@ impl<T> UTXOGroups<T> {
.chain(self.age_range.iter())
.chain(self.epoch.iter())
.chain(self.class.iter())
.chain(self.entry.iter())
.chain(self.amount_range.iter())
.chain(self.under_amount.iter())
.chain(self.type_.iter())
@@ -66,6 +69,7 @@ impl<T> UTXOGroups<T> {
.chain(self.age_range.iter_mut())
.chain(self.epoch.iter_mut())
.chain(self.class.iter_mut())
.chain(self.entry.iter_mut())
.chain(self.amount_range.iter_mut())
.chain(self.under_amount.iter_mut())
.chain(self.type_.iter_mut())
@@ -84,6 +88,7 @@ impl<T> UTXOGroups<T> {
.chain(self.age_range.par_iter_mut())
.chain(self.epoch.par_iter_mut())
.chain(self.class.par_iter_mut())
.chain(self.entry.par_iter_mut())
.chain(self.amount_range.par_iter_mut())
.chain(self.under_amount.par_iter_mut())
.chain(self.type_.par_iter_mut())
@@ -94,6 +99,7 @@ impl<T> UTXOGroups<T> {
.iter()
.chain(self.epoch.iter())
.chain(self.class.iter())
.chain(self.entry.iter())
.chain(self.amount_range.iter())
.chain(self.type_.iter())
}
@@ -103,6 +109,7 @@ impl<T> UTXOGroups<T> {
.iter_mut()
.chain(self.epoch.iter_mut())
.chain(self.class.iter_mut())
.chain(self.entry.iter_mut())
.chain(self.amount_range.iter_mut())
.chain(self.type_.iter_mut())
}
@@ -115,6 +122,7 @@ impl<T> UTXOGroups<T> {
.par_iter_mut()
.chain(self.epoch.par_iter_mut())
.chain(self.class.par_iter_mut())
.chain(self.entry.par_iter_mut())
.chain(self.amount_range.par_iter_mut())
.chain(self.type_.par_iter_mut())
}
@@ -30,18 +30,34 @@ const TREE_SIZE: usize = TIER0_COUNT + TIER1_COUNT + OVERFLOW; // 190,001
pub(super) struct CostBasisNode {
all_sats: i64,
sth_sats: i64,
discount_sats: i64,
all_usd: i128,
sth_usd: i128,
discount_usd: i128,
}
impl CostBasisNode {
#[inline(always)]
fn new(sats: i64, usd: i128, is_sth: bool) -> Self {
fn new_supply(sats: i64, usd: i128, is_sth: bool) -> Self {
Self {
all_sats: sats,
sth_sats: if is_sth { sats } else { 0 },
discount_sats: 0,
all_usd: usd,
sth_usd: if is_sth { usd } else { 0 },
discount_usd: 0,
}
}
#[inline(always)]
fn new_discount(sats: i64, usd: i128) -> Self {
Self {
all_sats: 0,
sth_sats: 0,
discount_sats: sats,
all_usd: 0,
sth_usd: 0,
discount_usd: usd,
}
}
}
@@ -51,8 +67,10 @@ impl FenwickNode for CostBasisNode {
fn add_assign(&mut self, other: &Self) {
self.all_sats += other.all_sats;
self.sth_sats += other.sth_sats;
self.discount_sats += other.discount_sats;
self.all_usd += other.all_usd;
self.sth_usd += other.sth_usd;
self.discount_usd += other.discount_usd;
}
}
@@ -151,16 +169,34 @@ impl CostBasisFenwick {
}
let bucket = price_to_bucket(price);
let delta =
CostBasisNode::new(net_sats, price.as_u128() as i128 * net_sats as i128, is_sth);
CostBasisNode::new_supply(net_sats, price.as_u128() as i128 * net_sats as i128, is_sth);
self.tree.add(bucket, &delta);
self.totals.add_assign(&delta);
}
/// Bulk-initialize from BTreeMaps (one per age-range cohort).
/// Call after state import when all pending maps have been drained.
pub(super) fn bulk_init<'a>(
/// Apply a net delta from the discount-entry cohort.
///
/// Supply totals are maintained from the age-range cohorts; this updates
/// only the discount-entry partition so premium can be derived as all - discount.
pub(super) fn apply_discount_delta(&mut self, price: CentsCompact, pending: &PendingDelta) {
let net_sats = u64::from(pending.inc) as i64 - u64::from(pending.dec) as i64;
if net_sats == 0 {
return;
}
let bucket = price_to_bucket(price);
let delta =
CostBasisNode::new_discount(net_sats, price.as_u128() as i128 * net_sats as i128);
self.tree.add(bucket, &delta);
self.totals.add_assign(&delta);
}
/// Bulk-initialize from age-range maps plus the discount-entry map.
/// Age-range maps maintain all/STH/LTH totals; the discount-entry map
/// maintains only the discount partition used to derive premium.
pub(super) fn bulk_init_with_discount<'a>(
&mut self,
maps: impl Iterator<Item = (&'a std::collections::BTreeMap<CentsCompact, Sats>, bool)>,
discount_maps: impl Iterator<Item = &'a std::collections::BTreeMap<CentsCompact, Sats>>,
) {
self.tree.reset();
self.totals = CostBasisNode::default();
@@ -169,7 +205,18 @@ impl CostBasisFenwick {
for (&price, &sats) in map.iter() {
let bucket = price_to_bucket(price);
let s = u64::from(sats) as i64;
let node = CostBasisNode::new(s, price.as_u128() as i128 * s as i128, is_sth);
let node =
CostBasisNode::new_supply(s, price.as_u128() as i128 * s as i128, is_sth);
self.tree.add_raw(bucket, &node);
self.totals.add_assign(&node);
}
}
for map in discount_maps {
for (&price, &sats) in map.iter() {
let bucket = price_to_bucket(price);
let s = u64::from(sats) as i64;
let node = CostBasisNode::new_discount(s, price.as_u128() as i128 * s as i128);
self.tree.add_raw(bucket, &node);
self.totals.add_assign(&node);
}
@@ -212,6 +259,26 @@ impl CostBasisFenwick {
)
}
/// Compute percentile prices for discount-entry cohort.
pub(super) fn percentiles_discount_entry(&self) -> PercentileResult {
self.compute_percentiles(
self.totals.discount_sats,
self.totals.discount_usd,
|n| n.discount_sats,
|n| n.discount_usd,
)
}
/// Compute percentile prices for premium-entry cohort (all - discount).
pub(super) fn percentiles_premium_entry(&self) -> PercentileResult {
self.compute_percentiles(
self.totals.all_sats - self.totals.discount_sats,
self.totals.all_usd - self.totals.discount_usd,
|n| n.all_sats - n.discount_sats,
|n| n.all_usd - n.discount_usd,
)
}
fn compute_percentiles(
&self,
total_sats: i64,
@@ -271,6 +338,37 @@ impl CostBasisFenwick {
return (0, 0, 0);
}
let range = self.density_range(spot_price);
let all_range = range.all_sats.max(0);
let sth_range = range.sth_sats.max(0);
let lth_range = all_range - sth_range;
let lth_total = self.totals.all_sats - self.totals.sth_sats;
(
Self::to_bps(all_range, self.totals.all_sats),
Self::to_bps(sth_range, self.totals.sth_sats),
Self::to_bps(lth_range, lth_total),
)
}
/// Compute supply density for entry cohorts: (discount_bps, premium_bps).
pub(super) fn entry_density(&self, spot_price: Cents) -> (u16, u16) {
if self.totals.all_sats <= 0 {
return (0, 0);
}
let range = self.density_range(spot_price);
let discount_range = range.discount_sats.max(0);
let premium_range = range.all_sats.max(0) - discount_range;
let premium_total = self.totals.all_sats - self.totals.discount_sats;
(
Self::to_bps(discount_range, self.totals.discount_sats),
Self::to_bps(premium_range, premium_total),
)
}
fn density_range(&self, spot_price: Cents) -> CostBasisNode {
let spot_f64 = u64::from(spot_price) as f64;
let low = Cents::from((spot_f64 * 0.95) as u64);
let high = Cents::from((spot_f64 * 1.05) as u64);
@@ -285,24 +383,23 @@ impl CostBasisFenwick {
CostBasisNode::default()
};
let all_range = (cum_high.all_sats - cum_low.all_sats).max(0);
let sth_range = (cum_high.sth_sats - cum_low.sth_sats).max(0);
let lth_range = all_range - sth_range;
CostBasisNode {
all_sats: cum_high.all_sats - cum_low.all_sats,
sth_sats: cum_high.sth_sats - cum_low.sth_sats,
discount_sats: cum_high.discount_sats - cum_low.discount_sats,
all_usd: cum_high.all_usd - cum_low.all_usd,
sth_usd: cum_high.sth_usd - cum_low.sth_usd,
discount_usd: cum_high.discount_usd - cum_low.discount_usd,
}
}
let to_bps = |range: i64, total: i64| -> u16 {
if total <= 0 {
0
} else {
(range as f64 / total as f64 * 10000.0).round() as u16
}
};
let lth_total = self.totals.all_sats - self.totals.sth_sats;
(
to_bps(all_range, self.totals.all_sats),
to_bps(sth_range, self.totals.sth_sats),
to_bps(lth_range, lth_total),
)
#[inline(always)]
fn to_bps(range: i64, total: i64) -> u16 {
if total <= 0 {
0
} else {
(range as f64 / total as f64 * 10000.0).round() as u16
}
}
// -----------------------------------------------------------------------
@@ -1,8 +1,8 @@
use std::path::Path;
use brk_cohort::{
AgeRange, AmountRange, ByEpoch, Class, CohortContext, Filter, Filtered, OverAge, OverAmount,
SpendableType, Term, UnderAge, UnderAmount,
AgeRange, AmountRange, ByEntry, ByEpoch, Class, CohortContext, Filter, Filtered, OverAge,
OverAmount, SpendableType, Term, UnderAge, UnderAmount,
};
use brk_error::Result;
use brk_indexer::Lengths;
@@ -16,7 +16,6 @@ use vecdb::{
use crate::{
blocks,
distribution::{
DynCohortVecs,
metrics::{
AllCohortMetrics, BasicCohortMetrics, CohortMetricsBase, CoreCohortMetrics,
ExtendedAdjustedCohortMetrics, ExtendedCohortMetrics, ImportConfig,
@@ -24,6 +23,7 @@ use crate::{
TypeCohortMetrics,
},
state::UTXOCohortState,
DynCohortVecs,
},
indexes,
internal::{ValuePerBlockCumulativeRolling, WindowStartVec, Windows},
@@ -45,6 +45,7 @@ pub struct UTXOCohorts<M: StorageMode = Rw> {
pub over_age: OverAge<UTXOCohortVecs<CoreCohortMetrics<M>>>,
pub epoch: ByEpoch<UTXOCohortVecs<CoreCohortMetrics<M>>>,
pub class: Class<UTXOCohortVecs<CoreCohortMetrics<M>>>,
pub entry: ByEntry<UTXOCohortVecs<ExtendedCohortMetrics<M>>>,
pub over_amount: OverAmount<UTXOCohortVecs<MinimalCohortMetrics<M>>>,
pub amount_range: AmountRange<UTXOCohortVecs<MinimalCohortMetrics<M>>>,
pub under_amount: UnderAmount<UTXOCohortVecs<MinimalCohortMetrics<M>>>,
@@ -67,8 +68,10 @@ pub(crate) struct UTXOCohortsTransientState {
}
impl UTXOCohorts<Rw> {
/// ~71 separate cohorts (21 age + 5 epoch + 18 class + 15 amount + 12 type)
const SEPARATE_COHORT_CAPACITY: usize = 80;
/// Separate cohorts currently total 72:
/// 21 age + 5 epoch + 18 class + 2 entry + 15 amount + 11 spendable type.
/// Keep small headroom because this is only Vec allocation capacity.
const SEPARATE_COHORT_CAPACITY: usize = 82;
/// Import all UTXO cohorts from database.
pub(crate) fn forced_import(
@@ -136,6 +139,26 @@ impl UTXOCohorts<Rw> {
let epoch = ByEpoch::try_new(&core_separate)?;
let class = Class::try_new(&core_separate)?;
let extended_separate =
|f: Filter, name: &'static str| -> Result<UTXOCohortVecs<ExtendedCohortMetrics>> {
let full_name = CohortContext::Utxo.full_name(&f, name);
let cfg = ImportConfig {
db,
filter: &f,
full_name: &full_name,
version: v,
indexes,
cached_starts,
};
let state = Some(Box::new(UTXOCohortState::new(states_path, &full_name)));
Ok(UTXOCohortVecs::new(
state,
ExtendedCohortMetrics::forced_import(&cfg)?,
))
};
let entry = ByEntry::try_new(&extended_separate)?;
// Helper for separate cohorts with MinimalCohortMetrics + MinimalRealizedState
let minimal_separate =
|f: Filter, name: &'static str| -> Result<UTXOCohortVecs<MinimalCohortMetrics>> {
@@ -281,6 +304,7 @@ impl UTXOCohorts<Rw> {
lth,
epoch,
class,
entry,
type_,
under_age,
over_age,
@@ -309,6 +333,7 @@ impl UTXOCohorts<Rw> {
sth,
caches,
age_range,
entry,
..
} = self;
caches
@@ -327,7 +352,15 @@ impl UTXOCohorts<Rw> {
Some((map, caches.fenwick.is_sth_at(i)))
})
.collect();
caches.fenwick.bulk_init(maps.into_iter());
let discount_maps = entry
.discount
.state
.as_ref()
.map(|state| state.cost_basis_map())
.into_iter();
caches
.fenwick
.bulk_init_with_discount(maps.into_iter(), discount_maps);
}
/// Apply pending deltas from all age-range cohorts to the Fenwick tree.
@@ -338,7 +371,10 @@ impl UTXOCohorts<Rw> {
}
// Destructure to get separate borrows on caches and age_range
let Self {
caches, age_range, ..
caches,
age_range,
entry,
..
} = self;
for (i, sub) in age_range.iter().enumerate() {
if let Some(state) = sub.state.as_ref() {
@@ -348,6 +384,11 @@ impl UTXOCohorts<Rw> {
});
}
}
if let Some(state) = entry.discount.state.as_ref() {
state.for_each_cost_basis_pending(|&price, delta| {
caches.fenwick.apply_discount_delta(price, delta);
});
}
}
/// Push maturation sats to the matured vecs for the given height.
@@ -365,6 +406,7 @@ impl UTXOCohorts<Rw> {
age_range,
epoch,
class,
entry,
amount_range,
type_,
..
@@ -374,6 +416,7 @@ impl UTXOCohorts<Rw> {
.map(|x| x as &mut dyn DynCohortVecs)
.chain(epoch.par_iter_mut().map(|x| x as &mut dyn DynCohortVecs))
.chain(class.par_iter_mut().map(|x| x as &mut dyn DynCohortVecs))
.chain(entry.par_iter_mut().map(|x| x as &mut dyn DynCohortVecs))
.chain(
amount_range
.par_iter_mut()
@@ -389,6 +432,7 @@ impl UTXOCohorts<Rw> {
age_range,
epoch,
class,
entry,
amount_range,
type_,
..
@@ -398,6 +442,7 @@ impl UTXOCohorts<Rw> {
.map(|x| x as &mut dyn DynCohortVecs)
.chain(epoch.iter_mut().map(|x| x as &mut dyn DynCohortVecs))
.chain(class.iter_mut().map(|x| x as &mut dyn DynCohortVecs))
.chain(entry.iter_mut().map(|x| x as &mut dyn DynCohortVecs))
.chain(amount_range.iter_mut().map(|x| x as &mut dyn DynCohortVecs))
.chain(type_.iter_mut().map(|x| x as &mut dyn DynCohortVecs))
}
@@ -409,6 +454,7 @@ impl UTXOCohorts<Rw> {
.map(|x| x as &dyn DynCohortVecs)
.chain(self.epoch.iter().map(|x| x as &dyn DynCohortVecs))
.chain(self.class.iter().map(|x| x as &dyn DynCohortVecs))
.chain(self.entry.iter().map(|x| x as &dyn DynCohortVecs))
.chain(self.amount_range.iter().map(|x| x as &dyn DynCohortVecs))
.chain(self.type_.iter().map(|x| x as &dyn DynCohortVecs))
}
@@ -516,6 +562,7 @@ impl UTXOCohorts<Rw> {
);
all.extend(self.epoch.iter_mut().map(|x| x as &mut dyn DynCohortVecs));
all.extend(self.class.iter_mut().map(|x| x as &mut dyn DynCohortVecs));
all.extend(self.entry.iter_mut().map(|x| x as &mut dyn DynCohortVecs));
all.extend(
self.amount_range
.iter_mut()
@@ -604,6 +651,7 @@ impl UTXOCohorts<Rw> {
under_amount,
epoch,
class,
entry,
type_,
..
} = self;
@@ -676,6 +724,19 @@ impl UTXOCohorts<Rw> {
.compute_rest_part2(prices, starting_lengths, ss, au, exit)
})
}),
Box::new(|| {
entry.par_iter_mut().try_for_each(|v| {
v.metrics.compute_rest_part2(
blocks,
prices,
starting_lengths,
height_to_market_cap,
ss,
au,
exit,
)
})
}),
Box::new(|| {
amount_range.par_iter_mut().try_for_each(|v| {
v.metrics
@@ -730,6 +791,9 @@ impl UTXOCohorts<Rw> {
for v in self.class.iter_mut() {
vecs.extend(v.metrics.collect_all_vecs_mut());
}
for v in self.entry.iter_mut() {
vecs.extend(v.metrics.collect_all_vecs_mut());
}
for v in self.amount_range.iter_mut() {
vecs.extend(v.metrics.collect_all_vecs_mut());
}
@@ -813,7 +877,7 @@ impl UTXOCohorts<Rw> {
/// Aggregate RealizedFull fields from age_range states and push to all/sth/lth.
/// Called during the block loop after separate cohorts' push_state but before reset.
pub(crate) fn push_overlapping(&mut self, height_price: Cents) {
pub(crate) fn push_overlapping(&mut self, height_price: Cents) -> Cents {
let Self {
all,
sth,
@@ -852,7 +916,7 @@ impl UTXOCohorts<Rw> {
}
}
all.metrics.realized.push_accum(&all_acc);
let all_capitalized_price = all.metrics.realized.push_accum(&all_acc);
sth.metrics.realized.push_accum(&sth_acc);
lth.metrics.realized.push_accum(&lth_acc);
@@ -880,6 +944,8 @@ impl UTXOCohorts<Rw> {
.unrealized
.capitalized_cap_in_loss_raw
.push(CentsSquaredSats::new(lth_ccap.1));
all_capitalized_price
}
}
@@ -50,6 +50,22 @@ impl UTXOCohorts {
let lth = self.caches.fenwick.percentiles_lth();
push_cost_basis(&lth, lth_d, &mut self.lth.metrics.cost_basis);
let (discount_d, premium_d) = self.caches.fenwick.entry_density(spot_price);
let discount = self.caches.fenwick.percentiles_discount_entry();
push_cost_basis(
&discount,
discount_d,
&mut self.entry.discount.metrics.cost_basis,
);
let premium = self.caches.fenwick.percentiles_premium_entry();
push_cost_basis(
&premium,
premium_d,
&mut self.entry.premium.metrics.cost_basis,
);
let prof = self.caches.fenwick.profitability(spot_price);
push_profitability(&prof, &mut self.profitability);
}
@@ -1,3 +1,4 @@
use brk_cohort::EntryPrice;
use brk_types::{Cents, CostBasisSnapshot, Height, Timestamp};
use vecdb::Rw;
@@ -12,6 +13,7 @@ impl UTXOCohorts<Rw> {
/// - The "under_1h" age cohort (all new UTXOs start at 0 hours old)
/// - The appropriate epoch cohort based on block height
/// - The appropriate class cohort based on block timestamp
/// - The immutable entry valuation cohort based on creation price versus anchor
/// - The appropriate output type cohort (P2PKH, P2SH, etc.)
/// - The appropriate amount range cohort based on value
pub(crate) fn receive(
@@ -20,13 +22,14 @@ impl UTXOCohorts<Rw> {
height: Height,
timestamp: Timestamp,
price: Cents,
entry: EntryPrice,
) {
let supply_state = received.spendable_supply;
// Pre-compute snapshot once for the 3 cohorts sharing the same supply_state
// Pre-compute snapshot once for cohorts sharing the block-level supply_state
let snapshot = CostBasisSnapshot::from_utxo(price, &supply_state);
// New UTXOs go into under_1h, current epoch, and current class
// New UTXOs go into under_1h plus immutable creation cohorts
self.age_range
.under_1h
.state
@@ -45,6 +48,12 @@ impl UTXOCohorts<Rw> {
.unwrap()
.receive_utxo_snapshot(&supply_state, &snapshot);
}
self.entry
.get_mut(entry)
.state
.as_mut()
.unwrap()
.receive_utxo_snapshot(&supply_state, &snapshot);
// Update output type cohorts (skip types with no outputs this block)
self.type_.iter_typed_mut().for_each(|(output_type, vecs)| {
@@ -49,7 +49,7 @@ impl UTXOCohorts<Rw> {
// This is the max price between receive and send heights
let peak_price = price_range_max.max_between(receive_height, send_height);
// Pre-compute once for age_range, epoch, year (all share sent.spendable_supply)
// Pre-compute once for cohorts sharing the sent supply.
if let Some(pre) = SendPrecomputed::new(
&sent.spendable_supply,
current_price,
@@ -75,6 +75,12 @@ impl UTXOCohorts<Rw> {
.unwrap()
.send_utxo_precomputed(&sent.spendable_supply, &pre);
}
self.entry
.get_mut(block_state.entry)
.state
.as_mut()
.unwrap()
.send_utxo_precomputed(&sent.spendable_supply, &pre);
} else if sent.spendable_supply.utxo_count > 0 {
// Zero-value UTXOs: just subtract supply
self.age_range.get_mut(age).state.as_mut().unwrap().supply -=
@@ -85,6 +91,12 @@ impl UTXOCohorts<Rw> {
if let Some(v) = self.class.mut_vec_from_timestamp(block_state.timestamp) {
v.state.as_mut().unwrap().supply -= &sent.spendable_supply;
}
self.entry
.get_mut(block_state.entry)
.state
.as_mut()
.unwrap()
.supply -= &sent.spendable_supply;
}
// Update output type cohorts (skip zero-supply entries)
@@ -1,4 +1,4 @@
use brk_cohort::ByAddrType;
use brk_cohort::{ByAddrType, EntryPrice};
use brk_error::Result;
use brk_indexer::Indexer;
use brk_types::{
@@ -46,6 +46,7 @@ pub(crate) fn process_blocks(
last_height: Height,
chain_state: &mut Vec<BlockState>,
tx_index_to_height: &mut RangeMap<TxIndex, Height>,
mut entry_anchor: Cents,
cached_prices: &[Cents],
cached_timestamps: &[Timestamp],
cached_price_range_max: &PriceRangeMax,
@@ -370,9 +371,14 @@ pub(crate) fn process_blocks(
.iterate(Sats::FIFTY_BTC, OutputType::P2PK65);
}
let entry = EntryPrice::from_is_discount(
entry_anchor == Cents::ZERO || block_price <= entry_anchor,
);
// Push current block state before processing cohort updates
chain_state.push(BlockState {
supply: transacted.spendable_supply,
entry,
price: block_price,
timestamp,
});
@@ -411,7 +417,7 @@ pub(crate) fn process_blocks(
|| {
// UTXO cohorts receive/send
vecs.utxo_cohorts
.receive(transacted, height, timestamp, block_price);
.receive(transacted, height, timestamp, block_price, entry);
if let Some(min_h) =
vecs.utxo_cohorts
.send(height_to_sent, chain_state, ctx.price_range_max)
@@ -460,7 +466,7 @@ pub(crate) fn process_blocks(
let is_last_of_day = is_last_of_day[offset];
let date_opt = is_last_of_day.then(|| Date::from(timestamp));
push_cohort_states(
entry_anchor = push_cohort_states(
&mut vecs.utxo_cohorts,
&mut vecs.addr_cohorts,
height,
@@ -527,7 +533,7 @@ fn push_cohort_states(
addr_cohorts: &mut AddrCohorts,
height: Height,
height_price: Cents,
) {
) -> Cents {
// Phase 1: push + unrealized (no reset yet, states still needed for aggregation)
rayon::join(
|| {
@@ -545,7 +551,7 @@ fn push_cohort_states(
);
// Phase 2: aggregate age_range states → push to overlapping cohorts
utxo_cohorts.push_overlapping(height_price);
let all_capitalized_price = utxo_cohorts.push_overlapping(height_price);
// Phase 3: reset per-block values
utxo_cohorts
@@ -554,4 +560,6 @@ fn push_cohort_states(
addr_cohorts
.iter_separate_mut()
.for_each(|v| v.reset_single_iteration_values());
all_capitalized_price
}
@@ -206,7 +206,7 @@ impl RealizedFull {
}
#[inline(always)]
pub(crate) fn push_accum(&mut self, accum: &RealizedFullAccum) {
pub(crate) fn push_accum(&mut self, accum: &RealizedFullAccum) -> Cents {
self.cap_raw.push(accum.cap_raw);
self.capitalized.cap_raw.push(accum.capitalized_cap_raw);
@@ -221,6 +221,8 @@ impl RealizedFull {
self.capitalized.price.cents.height.push(capitalized_price);
self.peak_regret.value.block.cents.push(accum.peak_regret());
capitalized_price
}
pub(crate) fn compute_rest_part1(
@@ -1,5 +1,6 @@
use std::ops::{Add, AddAssign, SubAssign};
use brk_cohort::EntryPrice;
use brk_types::{Cents, SupplyState, Timestamp};
use serde::Serialize;
@@ -8,6 +9,8 @@ pub struct BlockState {
#[serde(flatten)]
pub supply: SupplyState,
#[serde(skip)]
pub entry: EntryPrice,
#[serde(skip)]
pub price: Cents,
#[serde(skip)]
pub timestamp: Timestamp,
+41 -5
View File
@@ -1,6 +1,6 @@
use std::path::{Path, PathBuf};
use brk_cohort::{ByAddrType, Filter};
use brk_cohort::{ByAddrType, EntryPrice, Filter};
use brk_error::Result;
use brk_indexer::Indexer;
use brk_traversable::Traversable;
@@ -436,13 +436,34 @@ impl Vecs {
let end = usize::from(recovered_height);
debug!("building supply_state vec for {} heights", recovered_height);
let supply_state_data: Vec<_> = self.supply_state.collect_range_at(0, end);
let capitalized_price_data: Vec<_> = self
.utxo_cohorts
.all
.metrics
.realized
.capitalized
.price
.cents
.height
.collect_range_at(0, end);
let mut entry_anchor = Cents::ZERO;
chain_state = supply_state_data
.into_iter()
.enumerate()
.map(|(h, supply)| BlockState {
supply,
price: self.caches.prices[h],
timestamp: self.caches.timestamps[h],
.map(|(h, supply)| {
let price = self.caches.prices[h];
let entry = EntryPrice::from_is_discount(
entry_anchor == Cents::ZERO || price <= entry_anchor,
);
entry_anchor = capitalized_price_data[h];
BlockState {
supply,
entry,
price,
timestamp: self.caches.timestamps[h],
}
})
.collect();
debug!("chain_state rebuilt");
@@ -474,6 +495,20 @@ impl Vecs {
let prices = std::mem::take(&mut self.caches.prices);
let timestamps = std::mem::take(&mut self.caches.timestamps);
let price_range_max = std::mem::take(&mut self.caches.price_range_max);
let entry_anchor = starting_height
.decremented()
.and_then(|height| {
self.utxo_cohorts
.all
.metrics
.realized
.capitalized
.price
.cents
.height
.collect_one(height)
})
.unwrap_or(Cents::ZERO);
process_blocks(
self,
@@ -486,6 +521,7 @@ impl Vecs {
last_height,
&mut chain_state,
&mut tx_index_to_height,
entry_anchor,
&prices,
&timestamps,
&price_range_max,