global: snapshot

This commit is contained in:
nym21
2026-03-15 00:57:53 +01:00
parent 0d177494d9
commit 9e36a4188a
50 changed files with 2765 additions and 1239 deletions

View File

@@ -6,8 +6,9 @@
use std::collections::BTreeMap;
use brk_cohort::{
AGE_RANGE_NAMES, AMOUNT_RANGE_NAMES, EPOCH_NAMES, OVER_AMOUNT_NAMES, UNDER_AMOUNT_NAMES,
UNDER_AGE_NAMES, OVER_AGE_NAMES, SPENDABLE_TYPE_NAMES, TERM_NAMES, CLASS_NAMES,
AGE_RANGE_NAMES, AMOUNT_RANGE_NAMES, CLASS_NAMES, EPOCH_NAMES, LOSS_NAMES,
OVER_AGE_NAMES, OVER_AMOUNT_NAMES, PROFITABILITY_RANGE_NAMES, PROFIT_NAMES,
SPENDABLE_TYPE_NAMES, TERM_NAMES, UNDER_AGE_NAMES, UNDER_AMOUNT_NAMES,
};
use brk_types::{Index, PoolSlug, pools};
use serde::Serialize;
@@ -63,6 +64,9 @@ impl CohortConstants {
("AMOUNT_RANGE_NAMES", to_value(&AMOUNT_RANGE_NAMES)),
("OVER_AMOUNT_NAMES", to_value(&OVER_AMOUNT_NAMES)),
("UNDER_AMOUNT_NAMES", to_value(&UNDER_AMOUNT_NAMES)),
("PROFITABILITY_RANGE_NAMES", to_value(&PROFITABILITY_RANGE_NAMES)),
("PROFIT_NAMES", to_value(&PROFIT_NAMES)),
("LOSS_NAMES", to_value(&LOSS_NAMES)),
]
}
}

View File

@@ -1871,6 +1871,28 @@ impl BpsCentsRatioSatsUsdPattern {
}
}
/// Pattern struct for repeated tree structure.
pub struct BtcCentsDeltaSatsUsdPattern {
pub btc: MetricPattern1<Bitcoin>,
pub cents: MetricPattern1<Cents>,
pub delta: AbsoluteRatePattern,
pub sats: MetricPattern1<Sats>,
pub usd: MetricPattern1<Dollars>,
}
impl BtcCentsDeltaSatsUsdPattern {
/// Create a new pattern node with accumulated metric name.
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
Self {
btc: MetricPattern1::new(client.clone(), acc.clone()),
cents: MetricPattern1::new(client.clone(), _m(&acc, "cents")),
delta: AbsoluteRatePattern::new(client.clone(), _m(&acc, "delta")),
sats: MetricPattern1::new(client.clone(), _m(&acc, "sats")),
usd: MetricPattern1::new(client.clone(), _m(&acc, "usd")),
}
}
}
/// Pattern struct for repeated tree structure.
pub struct BtcCentsRelSatsUsdPattern {
pub btc: MetricPattern1<Bitcoin>,
@@ -1981,6 +2003,28 @@ impl InvestedMaxMinPercentilesSupplyPattern {
}
}
/// Pattern struct for repeated tree structure.
pub struct MvrvNuplRealizedSupplyPattern {
pub mvrv: MetricPattern1<StoredF32>,
pub nupl: BpsRatioPattern,
pub realized_cap: AllSthPattern,
pub realized_price: BpsCentsRatioSatsUsdPattern,
pub supply: AllSthPattern2,
}
impl MvrvNuplRealizedSupplyPattern {
/// Create a new pattern node with accumulated metric name.
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
Self {
mvrv: MetricPattern1::new(client.clone(), _m(&acc, "mvrv")),
nupl: BpsRatioPattern::new(client.clone(), _m(&acc, "nupl")),
realized_cap: AllSthPattern::new(client.clone(), acc.clone()),
realized_price: BpsCentsRatioSatsUsdPattern::new(client.clone(), _m(&acc, "realized_price")),
supply: AllSthPattern2::new(client.clone(), acc.clone()),
}
}
}
/// Pattern struct for repeated tree structure.
pub struct PhsReboundThsPattern {
pub phs: MetricPattern1<StoredF32>,
@@ -2677,7 +2721,7 @@ impl GreedNetPainPattern {
/// Pattern struct for repeated tree structure.
pub struct LossNuplProfitPattern {
pub loss: BaseCumulativeSumPattern3,
pub loss: BaseCumulativeNegativeSumPattern,
pub nupl: BpsRatioPattern,
pub profit: BaseCumulativeSumPattern3,
}
@@ -2686,7 +2730,7 @@ impl LossNuplProfitPattern {
/// Create a new pattern node with accumulated metric name.
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
Self {
loss: BaseCumulativeSumPattern3::new(client.clone(), _m(&acc, "unrealized_loss")),
loss: BaseCumulativeNegativeSumPattern::new(client.clone(), acc.clone()),
nupl: BpsRatioPattern::new(client.clone(), _m(&acc, "nupl")),
profit: BaseCumulativeSumPattern3::new(client.clone(), _m(&acc, "unrealized_profit")),
}
@@ -2815,6 +2859,38 @@ impl AbsoluteRatePattern2 {
}
}
/// Pattern struct for repeated tree structure.
pub struct AllSthPattern2 {
pub all: BtcCentsDeltaSatsUsdPattern,
pub sth: BtcCentsSatsUsdPattern,
}
impl AllSthPattern2 {
/// Create a new pattern node with accumulated metric name.
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
Self {
all: BtcCentsDeltaSatsUsdPattern::new(client.clone(), _m(&acc, "supply")),
sth: BtcCentsSatsUsdPattern::new(client.clone(), _m(&acc, "sth_supply")),
}
}
}
/// Pattern struct for repeated tree structure.
pub struct AllSthPattern {
pub all: MetricPattern1<Dollars>,
pub sth: MetricPattern1<Dollars>,
}
impl AllSthPattern {
/// Create a new pattern node with accumulated metric name.
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
Self {
all: MetricPattern1::new(client.clone(), _m(&acc, "realized_cap")),
sth: MetricPattern1::new(client.clone(), _m(&acc, "sth_realized_cap")),
}
}
}
/// Pattern struct for repeated tree structure.
pub struct BlocksDominancePattern {
pub blocks_mined: BaseCumulativeSumPattern2,
@@ -2959,22 +3035,6 @@ impl PriceValuePattern {
}
}
/// Pattern struct for repeated tree structure.
pub struct RealizedSupplyPattern {
pub realized_cap: MetricPattern1<Dollars>,
pub supply: MetricPattern1<Sats>,
}
impl RealizedSupplyPattern {
/// Create a new pattern node with accumulated metric name.
pub fn new(client: Arc<BrkClientBase>, acc: String) -> Self {
Self {
realized_cap: MetricPattern1::new(client.clone(), _m(&acc, "realized_cap")),
supply: MetricPattern1::new(client.clone(), _m(&acc, "supply")),
}
}
}
/// Pattern struct for repeated tree structure.
pub struct RelPattern {
pub rel_to_mcap: BpsPercentRatioPattern,
@@ -4101,28 +4161,13 @@ impl MetricsTree_Scripts_Count {
/// Metrics tree node.
pub struct MetricsTree_Scripts_Value {
pub op_return: MetricsTree_Scripts_Value_OpReturn,
pub op_return: BaseCumulativeSumPattern4,
}
impl MetricsTree_Scripts_Value {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
op_return: MetricsTree_Scripts_Value_OpReturn::new(client.clone(), format!("{base_path}_op_return")),
}
}
}
/// Metrics tree node.
pub struct MetricsTree_Scripts_Value_OpReturn {
pub base: BtcCentsSatsUsdPattern,
pub cumulative: BtcCentsSatsUsdPattern,
}
impl MetricsTree_Scripts_Value_OpReturn {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
base: BtcCentsSatsUsdPattern::new(client.clone(), "op_return_value".to_string()),
cumulative: BtcCentsSatsUsdPattern::new(client.clone(), "op_return_value_cumulative".to_string()),
op_return: BaseCumulativeSumPattern4::new(client.clone(), "op_return_value".to_string()),
}
}
}
@@ -6448,14 +6493,12 @@ impl MetricsTree_Supply {
/// Metrics tree node.
pub struct MetricsTree_Supply_Burned {
pub op_return: BaseCumulativeSumPattern4,
pub unspendable: BaseCumulativeSumPattern4,
}
impl MetricsTree_Supply_Burned {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
op_return: BaseCumulativeSumPattern4::new(client.clone(), "op_return_supply".to_string()),
unspendable: BaseCumulativeSumPattern4::new(client.clone(), "unspendable_supply".to_string()),
}
}
@@ -7140,182 +7183,182 @@ impl MetricsTree_Cohorts_Utxo_Profitability {
/// Metrics tree node.
pub struct MetricsTree_Cohorts_Utxo_Profitability_Range {
pub over_1000pct_in_profit: RealizedSupplyPattern,
pub _500pct_to_1000pct_in_profit: RealizedSupplyPattern,
pub _300pct_to_500pct_in_profit: RealizedSupplyPattern,
pub _200pct_to_300pct_in_profit: RealizedSupplyPattern,
pub _100pct_to_200pct_in_profit: RealizedSupplyPattern,
pub _90pct_to_100pct_in_profit: RealizedSupplyPattern,
pub _80pct_to_90pct_in_profit: RealizedSupplyPattern,
pub _70pct_to_80pct_in_profit: RealizedSupplyPattern,
pub _60pct_to_70pct_in_profit: RealizedSupplyPattern,
pub _50pct_to_60pct_in_profit: RealizedSupplyPattern,
pub _40pct_to_50pct_in_profit: RealizedSupplyPattern,
pub _30pct_to_40pct_in_profit: RealizedSupplyPattern,
pub _20pct_to_30pct_in_profit: RealizedSupplyPattern,
pub _10pct_to_20pct_in_profit: RealizedSupplyPattern,
pub _0pct_to_10pct_in_profit: RealizedSupplyPattern,
pub _0pct_to_10pct_in_loss: RealizedSupplyPattern,
pub _10pct_to_20pct_in_loss: RealizedSupplyPattern,
pub _20pct_to_30pct_in_loss: RealizedSupplyPattern,
pub _30pct_to_40pct_in_loss: RealizedSupplyPattern,
pub _40pct_to_50pct_in_loss: RealizedSupplyPattern,
pub _50pct_to_60pct_in_loss: RealizedSupplyPattern,
pub _60pct_to_70pct_in_loss: RealizedSupplyPattern,
pub _70pct_to_80pct_in_loss: RealizedSupplyPattern,
pub _80pct_to_90pct_in_loss: RealizedSupplyPattern,
pub _90pct_to_100pct_in_loss: RealizedSupplyPattern,
pub over_1000pct_in_profit: MvrvNuplRealizedSupplyPattern,
pub _500pct_to_1000pct_in_profit: MvrvNuplRealizedSupplyPattern,
pub _300pct_to_500pct_in_profit: MvrvNuplRealizedSupplyPattern,
pub _200pct_to_300pct_in_profit: MvrvNuplRealizedSupplyPattern,
pub _100pct_to_200pct_in_profit: MvrvNuplRealizedSupplyPattern,
pub _90pct_to_100pct_in_profit: MvrvNuplRealizedSupplyPattern,
pub _80pct_to_90pct_in_profit: MvrvNuplRealizedSupplyPattern,
pub _70pct_to_80pct_in_profit: MvrvNuplRealizedSupplyPattern,
pub _60pct_to_70pct_in_profit: MvrvNuplRealizedSupplyPattern,
pub _50pct_to_60pct_in_profit: MvrvNuplRealizedSupplyPattern,
pub _40pct_to_50pct_in_profit: MvrvNuplRealizedSupplyPattern,
pub _30pct_to_40pct_in_profit: MvrvNuplRealizedSupplyPattern,
pub _20pct_to_30pct_in_profit: MvrvNuplRealizedSupplyPattern,
pub _10pct_to_20pct_in_profit: MvrvNuplRealizedSupplyPattern,
pub _0pct_to_10pct_in_profit: MvrvNuplRealizedSupplyPattern,
pub _0pct_to_10pct_in_loss: MvrvNuplRealizedSupplyPattern,
pub _10pct_to_20pct_in_loss: MvrvNuplRealizedSupplyPattern,
pub _20pct_to_30pct_in_loss: MvrvNuplRealizedSupplyPattern,
pub _30pct_to_40pct_in_loss: MvrvNuplRealizedSupplyPattern,
pub _40pct_to_50pct_in_loss: MvrvNuplRealizedSupplyPattern,
pub _50pct_to_60pct_in_loss: MvrvNuplRealizedSupplyPattern,
pub _60pct_to_70pct_in_loss: MvrvNuplRealizedSupplyPattern,
pub _70pct_to_80pct_in_loss: MvrvNuplRealizedSupplyPattern,
pub _80pct_to_90pct_in_loss: MvrvNuplRealizedSupplyPattern,
pub _90pct_to_100pct_in_loss: MvrvNuplRealizedSupplyPattern,
}
impl MetricsTree_Cohorts_Utxo_Profitability_Range {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
over_1000pct_in_profit: RealizedSupplyPattern::new(client.clone(), "utxos_over_1000pct_in_profit".to_string()),
_500pct_to_1000pct_in_profit: RealizedSupplyPattern::new(client.clone(), "utxos_500pct_to_1000pct_in_profit".to_string()),
_300pct_to_500pct_in_profit: RealizedSupplyPattern::new(client.clone(), "utxos_300pct_to_500pct_in_profit".to_string()),
_200pct_to_300pct_in_profit: RealizedSupplyPattern::new(client.clone(), "utxos_200pct_to_300pct_in_profit".to_string()),
_100pct_to_200pct_in_profit: RealizedSupplyPattern::new(client.clone(), "utxos_100pct_to_200pct_in_profit".to_string()),
_90pct_to_100pct_in_profit: RealizedSupplyPattern::new(client.clone(), "utxos_90pct_to_100pct_in_profit".to_string()),
_80pct_to_90pct_in_profit: RealizedSupplyPattern::new(client.clone(), "utxos_80pct_to_90pct_in_profit".to_string()),
_70pct_to_80pct_in_profit: RealizedSupplyPattern::new(client.clone(), "utxos_70pct_to_80pct_in_profit".to_string()),
_60pct_to_70pct_in_profit: RealizedSupplyPattern::new(client.clone(), "utxos_60pct_to_70pct_in_profit".to_string()),
_50pct_to_60pct_in_profit: RealizedSupplyPattern::new(client.clone(), "utxos_50pct_to_60pct_in_profit".to_string()),
_40pct_to_50pct_in_profit: RealizedSupplyPattern::new(client.clone(), "utxos_40pct_to_50pct_in_profit".to_string()),
_30pct_to_40pct_in_profit: RealizedSupplyPattern::new(client.clone(), "utxos_30pct_to_40pct_in_profit".to_string()),
_20pct_to_30pct_in_profit: RealizedSupplyPattern::new(client.clone(), "utxos_20pct_to_30pct_in_profit".to_string()),
_10pct_to_20pct_in_profit: RealizedSupplyPattern::new(client.clone(), "utxos_10pct_to_20pct_in_profit".to_string()),
_0pct_to_10pct_in_profit: RealizedSupplyPattern::new(client.clone(), "utxos_0pct_to_10pct_in_profit".to_string()),
_0pct_to_10pct_in_loss: RealizedSupplyPattern::new(client.clone(), "utxos_0pct_to_10pct_in_loss".to_string()),
_10pct_to_20pct_in_loss: RealizedSupplyPattern::new(client.clone(), "utxos_10pct_to_20pct_in_loss".to_string()),
_20pct_to_30pct_in_loss: RealizedSupplyPattern::new(client.clone(), "utxos_20pct_to_30pct_in_loss".to_string()),
_30pct_to_40pct_in_loss: RealizedSupplyPattern::new(client.clone(), "utxos_30pct_to_40pct_in_loss".to_string()),
_40pct_to_50pct_in_loss: RealizedSupplyPattern::new(client.clone(), "utxos_40pct_to_50pct_in_loss".to_string()),
_50pct_to_60pct_in_loss: RealizedSupplyPattern::new(client.clone(), "utxos_50pct_to_60pct_in_loss".to_string()),
_60pct_to_70pct_in_loss: RealizedSupplyPattern::new(client.clone(), "utxos_60pct_to_70pct_in_loss".to_string()),
_70pct_to_80pct_in_loss: RealizedSupplyPattern::new(client.clone(), "utxos_70pct_to_80pct_in_loss".to_string()),
_80pct_to_90pct_in_loss: RealizedSupplyPattern::new(client.clone(), "utxos_80pct_to_90pct_in_loss".to_string()),
_90pct_to_100pct_in_loss: RealizedSupplyPattern::new(client.clone(), "utxos_90pct_to_100pct_in_loss".to_string()),
over_1000pct_in_profit: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_over_1000pct_in_profit".to_string()),
_500pct_to_1000pct_in_profit: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_500pct_to_1000pct_in_profit".to_string()),
_300pct_to_500pct_in_profit: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_300pct_to_500pct_in_profit".to_string()),
_200pct_to_300pct_in_profit: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_200pct_to_300pct_in_profit".to_string()),
_100pct_to_200pct_in_profit: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_100pct_to_200pct_in_profit".to_string()),
_90pct_to_100pct_in_profit: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_90pct_to_100pct_in_profit".to_string()),
_80pct_to_90pct_in_profit: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_80pct_to_90pct_in_profit".to_string()),
_70pct_to_80pct_in_profit: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_70pct_to_80pct_in_profit".to_string()),
_60pct_to_70pct_in_profit: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_60pct_to_70pct_in_profit".to_string()),
_50pct_to_60pct_in_profit: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_50pct_to_60pct_in_profit".to_string()),
_40pct_to_50pct_in_profit: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_40pct_to_50pct_in_profit".to_string()),
_30pct_to_40pct_in_profit: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_30pct_to_40pct_in_profit".to_string()),
_20pct_to_30pct_in_profit: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_20pct_to_30pct_in_profit".to_string()),
_10pct_to_20pct_in_profit: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_10pct_to_20pct_in_profit".to_string()),
_0pct_to_10pct_in_profit: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_0pct_to_10pct_in_profit".to_string()),
_0pct_to_10pct_in_loss: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_0pct_to_10pct_in_loss".to_string()),
_10pct_to_20pct_in_loss: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_10pct_to_20pct_in_loss".to_string()),
_20pct_to_30pct_in_loss: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_20pct_to_30pct_in_loss".to_string()),
_30pct_to_40pct_in_loss: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_30pct_to_40pct_in_loss".to_string()),
_40pct_to_50pct_in_loss: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_40pct_to_50pct_in_loss".to_string()),
_50pct_to_60pct_in_loss: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_50pct_to_60pct_in_loss".to_string()),
_60pct_to_70pct_in_loss: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_60pct_to_70pct_in_loss".to_string()),
_70pct_to_80pct_in_loss: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_70pct_to_80pct_in_loss".to_string()),
_80pct_to_90pct_in_loss: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_80pct_to_90pct_in_loss".to_string()),
_90pct_to_100pct_in_loss: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_90pct_to_100pct_in_loss".to_string()),
}
}
}
/// Metrics tree node.
pub struct MetricsTree_Cohorts_Utxo_Profitability_Profit {
pub breakeven: RealizedSupplyPattern,
pub _10pct: RealizedSupplyPattern,
pub _20pct: RealizedSupplyPattern,
pub _30pct: RealizedSupplyPattern,
pub _40pct: RealizedSupplyPattern,
pub _50pct: RealizedSupplyPattern,
pub _60pct: RealizedSupplyPattern,
pub _70pct: RealizedSupplyPattern,
pub _80pct: RealizedSupplyPattern,
pub _90pct: RealizedSupplyPattern,
pub _100pct: RealizedSupplyPattern,
pub _200pct: RealizedSupplyPattern,
pub _300pct: RealizedSupplyPattern,
pub _500pct: RealizedSupplyPattern,
pub breakeven: MvrvNuplRealizedSupplyPattern,
pub _10pct: MvrvNuplRealizedSupplyPattern,
pub _20pct: MvrvNuplRealizedSupplyPattern,
pub _30pct: MvrvNuplRealizedSupplyPattern,
pub _40pct: MvrvNuplRealizedSupplyPattern,
pub _50pct: MvrvNuplRealizedSupplyPattern,
pub _60pct: MvrvNuplRealizedSupplyPattern,
pub _70pct: MvrvNuplRealizedSupplyPattern,
pub _80pct: MvrvNuplRealizedSupplyPattern,
pub _90pct: MvrvNuplRealizedSupplyPattern,
pub _100pct: MvrvNuplRealizedSupplyPattern,
pub _200pct: MvrvNuplRealizedSupplyPattern,
pub _300pct: MvrvNuplRealizedSupplyPattern,
pub _500pct: MvrvNuplRealizedSupplyPattern,
}
impl MetricsTree_Cohorts_Utxo_Profitability_Profit {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
breakeven: RealizedSupplyPattern::new(client.clone(), "utxos_in_profit".to_string()),
_10pct: RealizedSupplyPattern::new(client.clone(), "utxos_over_10pct_in_profit".to_string()),
_20pct: RealizedSupplyPattern::new(client.clone(), "utxos_over_20pct_in_profit".to_string()),
_30pct: RealizedSupplyPattern::new(client.clone(), "utxos_over_30pct_in_profit".to_string()),
_40pct: RealizedSupplyPattern::new(client.clone(), "utxos_over_40pct_in_profit".to_string()),
_50pct: RealizedSupplyPattern::new(client.clone(), "utxos_over_50pct_in_profit".to_string()),
_60pct: RealizedSupplyPattern::new(client.clone(), "utxos_over_60pct_in_profit".to_string()),
_70pct: RealizedSupplyPattern::new(client.clone(), "utxos_over_70pct_in_profit".to_string()),
_80pct: RealizedSupplyPattern::new(client.clone(), "utxos_over_80pct_in_profit".to_string()),
_90pct: RealizedSupplyPattern::new(client.clone(), "utxos_over_90pct_in_profit".to_string()),
_100pct: RealizedSupplyPattern::new(client.clone(), "utxos_over_100pct_in_profit".to_string()),
_200pct: RealizedSupplyPattern::new(client.clone(), "utxos_over_200pct_in_profit".to_string()),
_300pct: RealizedSupplyPattern::new(client.clone(), "utxos_over_300pct_in_profit".to_string()),
_500pct: RealizedSupplyPattern::new(client.clone(), "utxos_over_500pct_in_profit".to_string()),
breakeven: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_in_profit".to_string()),
_10pct: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_over_10pct_in_profit".to_string()),
_20pct: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_over_20pct_in_profit".to_string()),
_30pct: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_over_30pct_in_profit".to_string()),
_40pct: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_over_40pct_in_profit".to_string()),
_50pct: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_over_50pct_in_profit".to_string()),
_60pct: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_over_60pct_in_profit".to_string()),
_70pct: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_over_70pct_in_profit".to_string()),
_80pct: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_over_80pct_in_profit".to_string()),
_90pct: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_over_90pct_in_profit".to_string()),
_100pct: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_over_100pct_in_profit".to_string()),
_200pct: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_over_200pct_in_profit".to_string()),
_300pct: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_over_300pct_in_profit".to_string()),
_500pct: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_over_500pct_in_profit".to_string()),
}
}
}
/// Metrics tree node.
pub struct MetricsTree_Cohorts_Utxo_Profitability_Loss {
pub breakeven: RealizedSupplyPattern,
pub _10pct: RealizedSupplyPattern,
pub _20pct: RealizedSupplyPattern,
pub _30pct: RealizedSupplyPattern,
pub _40pct: RealizedSupplyPattern,
pub _50pct: RealizedSupplyPattern,
pub _60pct: RealizedSupplyPattern,
pub _70pct: RealizedSupplyPattern,
pub _80pct: RealizedSupplyPattern,
pub breakeven: MvrvNuplRealizedSupplyPattern,
pub _10pct: MvrvNuplRealizedSupplyPattern,
pub _20pct: MvrvNuplRealizedSupplyPattern,
pub _30pct: MvrvNuplRealizedSupplyPattern,
pub _40pct: MvrvNuplRealizedSupplyPattern,
pub _50pct: MvrvNuplRealizedSupplyPattern,
pub _60pct: MvrvNuplRealizedSupplyPattern,
pub _70pct: MvrvNuplRealizedSupplyPattern,
pub _80pct: MvrvNuplRealizedSupplyPattern,
}
impl MetricsTree_Cohorts_Utxo_Profitability_Loss {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
breakeven: RealizedSupplyPattern::new(client.clone(), "utxos_in_loss".to_string()),
_10pct: RealizedSupplyPattern::new(client.clone(), "utxos_over_10pct_in_loss".to_string()),
_20pct: RealizedSupplyPattern::new(client.clone(), "utxos_over_20pct_in_loss".to_string()),
_30pct: RealizedSupplyPattern::new(client.clone(), "utxos_over_30pct_in_loss".to_string()),
_40pct: RealizedSupplyPattern::new(client.clone(), "utxos_over_40pct_in_loss".to_string()),
_50pct: RealizedSupplyPattern::new(client.clone(), "utxos_over_50pct_in_loss".to_string()),
_60pct: RealizedSupplyPattern::new(client.clone(), "utxos_over_60pct_in_loss".to_string()),
_70pct: RealizedSupplyPattern::new(client.clone(), "utxos_over_70pct_in_loss".to_string()),
_80pct: RealizedSupplyPattern::new(client.clone(), "utxos_over_80pct_in_loss".to_string()),
breakeven: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_in_loss".to_string()),
_10pct: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_over_10pct_in_loss".to_string()),
_20pct: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_over_20pct_in_loss".to_string()),
_30pct: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_over_30pct_in_loss".to_string()),
_40pct: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_over_40pct_in_loss".to_string()),
_50pct: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_over_50pct_in_loss".to_string()),
_60pct: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_over_60pct_in_loss".to_string()),
_70pct: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_over_70pct_in_loss".to_string()),
_80pct: MvrvNuplRealizedSupplyPattern::new(client.clone(), "utxos_over_80pct_in_loss".to_string()),
}
}
}
/// Metrics tree node.
pub struct MetricsTree_Cohorts_Utxo_Matured {
pub under_1h: BtcCentsSatsUsdPattern,
pub _1h_to_1d: BtcCentsSatsUsdPattern,
pub _1d_to_1w: BtcCentsSatsUsdPattern,
pub _1w_to_1m: BtcCentsSatsUsdPattern,
pub _1m_to_2m: BtcCentsSatsUsdPattern,
pub _2m_to_3m: BtcCentsSatsUsdPattern,
pub _3m_to_4m: BtcCentsSatsUsdPattern,
pub _4m_to_5m: BtcCentsSatsUsdPattern,
pub _5m_to_6m: BtcCentsSatsUsdPattern,
pub _6m_to_1y: BtcCentsSatsUsdPattern,
pub _1y_to_2y: BtcCentsSatsUsdPattern,
pub _2y_to_3y: BtcCentsSatsUsdPattern,
pub _3y_to_4y: BtcCentsSatsUsdPattern,
pub _4y_to_5y: BtcCentsSatsUsdPattern,
pub _5y_to_6y: BtcCentsSatsUsdPattern,
pub _6y_to_7y: BtcCentsSatsUsdPattern,
pub _7y_to_8y: BtcCentsSatsUsdPattern,
pub _8y_to_10y: BtcCentsSatsUsdPattern,
pub _10y_to_12y: BtcCentsSatsUsdPattern,
pub _12y_to_15y: BtcCentsSatsUsdPattern,
pub over_15y: BtcCentsSatsUsdPattern,
pub under_1h: BaseCumulativeSumPattern4,
pub _1h_to_1d: BaseCumulativeSumPattern4,
pub _1d_to_1w: BaseCumulativeSumPattern4,
pub _1w_to_1m: BaseCumulativeSumPattern4,
pub _1m_to_2m: BaseCumulativeSumPattern4,
pub _2m_to_3m: BaseCumulativeSumPattern4,
pub _3m_to_4m: BaseCumulativeSumPattern4,
pub _4m_to_5m: BaseCumulativeSumPattern4,
pub _5m_to_6m: BaseCumulativeSumPattern4,
pub _6m_to_1y: BaseCumulativeSumPattern4,
pub _1y_to_2y: BaseCumulativeSumPattern4,
pub _2y_to_3y: BaseCumulativeSumPattern4,
pub _3y_to_4y: BaseCumulativeSumPattern4,
pub _4y_to_5y: BaseCumulativeSumPattern4,
pub _5y_to_6y: BaseCumulativeSumPattern4,
pub _6y_to_7y: BaseCumulativeSumPattern4,
pub _7y_to_8y: BaseCumulativeSumPattern4,
pub _8y_to_10y: BaseCumulativeSumPattern4,
pub _10y_to_12y: BaseCumulativeSumPattern4,
pub _12y_to_15y: BaseCumulativeSumPattern4,
pub over_15y: BaseCumulativeSumPattern4,
}
impl MetricsTree_Cohorts_Utxo_Matured {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
under_1h: BtcCentsSatsUsdPattern::new(client.clone(), "utxo_under_1h_old_matured".to_string()),
_1h_to_1d: BtcCentsSatsUsdPattern::new(client.clone(), "utxo_1h_to_1d_old_matured".to_string()),
_1d_to_1w: BtcCentsSatsUsdPattern::new(client.clone(), "utxo_1d_to_1w_old_matured".to_string()),
_1w_to_1m: BtcCentsSatsUsdPattern::new(client.clone(), "utxo_1w_to_1m_old_matured".to_string()),
_1m_to_2m: BtcCentsSatsUsdPattern::new(client.clone(), "utxo_1m_to_2m_old_matured".to_string()),
_2m_to_3m: BtcCentsSatsUsdPattern::new(client.clone(), "utxo_2m_to_3m_old_matured".to_string()),
_3m_to_4m: BtcCentsSatsUsdPattern::new(client.clone(), "utxo_3m_to_4m_old_matured".to_string()),
_4m_to_5m: BtcCentsSatsUsdPattern::new(client.clone(), "utxo_4m_to_5m_old_matured".to_string()),
_5m_to_6m: BtcCentsSatsUsdPattern::new(client.clone(), "utxo_5m_to_6m_old_matured".to_string()),
_6m_to_1y: BtcCentsSatsUsdPattern::new(client.clone(), "utxo_6m_to_1y_old_matured".to_string()),
_1y_to_2y: BtcCentsSatsUsdPattern::new(client.clone(), "utxo_1y_to_2y_old_matured".to_string()),
_2y_to_3y: BtcCentsSatsUsdPattern::new(client.clone(), "utxo_2y_to_3y_old_matured".to_string()),
_3y_to_4y: BtcCentsSatsUsdPattern::new(client.clone(), "utxo_3y_to_4y_old_matured".to_string()),
_4y_to_5y: BtcCentsSatsUsdPattern::new(client.clone(), "utxo_4y_to_5y_old_matured".to_string()),
_5y_to_6y: BtcCentsSatsUsdPattern::new(client.clone(), "utxo_5y_to_6y_old_matured".to_string()),
_6y_to_7y: BtcCentsSatsUsdPattern::new(client.clone(), "utxo_6y_to_7y_old_matured".to_string()),
_7y_to_8y: BtcCentsSatsUsdPattern::new(client.clone(), "utxo_7y_to_8y_old_matured".to_string()),
_8y_to_10y: BtcCentsSatsUsdPattern::new(client.clone(), "utxo_8y_to_10y_old_matured".to_string()),
_10y_to_12y: BtcCentsSatsUsdPattern::new(client.clone(), "utxo_10y_to_12y_old_matured".to_string()),
_12y_to_15y: BtcCentsSatsUsdPattern::new(client.clone(), "utxo_12y_to_15y_old_matured".to_string()),
over_15y: BtcCentsSatsUsdPattern::new(client.clone(), "utxo_over_15y_old_matured".to_string()),
under_1h: BaseCumulativeSumPattern4::new(client.clone(), "utxos_under_1h_old_matured_supply".to_string()),
_1h_to_1d: BaseCumulativeSumPattern4::new(client.clone(), "utxos_1h_to_1d_old_matured_supply".to_string()),
_1d_to_1w: BaseCumulativeSumPattern4::new(client.clone(), "utxos_1d_to_1w_old_matured_supply".to_string()),
_1w_to_1m: BaseCumulativeSumPattern4::new(client.clone(), "utxos_1w_to_1m_old_matured_supply".to_string()),
_1m_to_2m: BaseCumulativeSumPattern4::new(client.clone(), "utxos_1m_to_2m_old_matured_supply".to_string()),
_2m_to_3m: BaseCumulativeSumPattern4::new(client.clone(), "utxos_2m_to_3m_old_matured_supply".to_string()),
_3m_to_4m: BaseCumulativeSumPattern4::new(client.clone(), "utxos_3m_to_4m_old_matured_supply".to_string()),
_4m_to_5m: BaseCumulativeSumPattern4::new(client.clone(), "utxos_4m_to_5m_old_matured_supply".to_string()),
_5m_to_6m: BaseCumulativeSumPattern4::new(client.clone(), "utxos_5m_to_6m_old_matured_supply".to_string()),
_6m_to_1y: BaseCumulativeSumPattern4::new(client.clone(), "utxos_6m_to_1y_old_matured_supply".to_string()),
_1y_to_2y: BaseCumulativeSumPattern4::new(client.clone(), "utxos_1y_to_2y_old_matured_supply".to_string()),
_2y_to_3y: BaseCumulativeSumPattern4::new(client.clone(), "utxos_2y_to_3y_old_matured_supply".to_string()),
_3y_to_4y: BaseCumulativeSumPattern4::new(client.clone(), "utxos_3y_to_4y_old_matured_supply".to_string()),
_4y_to_5y: BaseCumulativeSumPattern4::new(client.clone(), "utxos_4y_to_5y_old_matured_supply".to_string()),
_5y_to_6y: BaseCumulativeSumPattern4::new(client.clone(), "utxos_5y_to_6y_old_matured_supply".to_string()),
_6y_to_7y: BaseCumulativeSumPattern4::new(client.clone(), "utxos_6y_to_7y_old_matured_supply".to_string()),
_7y_to_8y: BaseCumulativeSumPattern4::new(client.clone(), "utxos_7y_to_8y_old_matured_supply".to_string()),
_8y_to_10y: BaseCumulativeSumPattern4::new(client.clone(), "utxos_8y_to_10y_old_matured_supply".to_string()),
_10y_to_12y: BaseCumulativeSumPattern4::new(client.clone(), "utxos_10y_to_12y_old_matured_supply".to_string()),
_12y_to_15y: BaseCumulativeSumPattern4::new(client.clone(), "utxos_12y_to_15y_old_matured_supply".to_string()),
over_15y: BaseCumulativeSumPattern4::new(client.clone(), "utxos_over_15y_old_matured_supply".to_string()),
}
}
}

View File

@@ -1,3 +1,5 @@
use std::thread;
use brk_error::Result;
use brk_indexer::Indexer;
use brk_types::Indexes;
@@ -15,22 +17,40 @@ impl Vecs {
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
// Sequential: time → lookback (dependency chain)
self.time
.timestamp
.compute(indexer, indexes, starting_indexes, exit)?;
self.lookback
.compute(&self.time, starting_indexes, exit)?;
self.count
.compute(indexer, starting_indexes, exit)?;
self.interval
.compute(indexer, starting_indexes, exit)?;
self.size
.compute(indexer, &self.lookback, starting_indexes, exit)?;
self.weight
.compute(indexer, starting_indexes, exit)?;
self.difficulty
.compute(indexer, indexes, starting_indexes, exit)?;
self.halving.compute(indexes, starting_indexes, exit)?;
// Parallel: remaining sub-modules are independent of each other.
// size depends on lookback (already computed above).
let Vecs {
lookback,
count,
interval,
size,
weight,
difficulty,
halving,
..
} = self;
thread::scope(|s| -> Result<()> {
let r1 = s.spawn(|| count.compute(indexer, starting_indexes, exit));
let r2 = s.spawn(|| interval.compute(indexer, starting_indexes, exit));
let r3 = s.spawn(|| weight.compute(indexer, starting_indexes, exit));
let r4 =
s.spawn(|| difficulty.compute(indexer, indexes, starting_indexes, exit));
let r5 = s.spawn(|| halving.compute(indexes, starting_indexes, exit));
size.compute(indexer, &*lookback, starting_indexes, exit)?;
r1.join().unwrap()?;
r2.join().unwrap()?;
r3.join().unwrap()?;
r4.join().unwrap()?;
r5.join().unwrap()?;
Ok(())
})?;
let _lock = exit.lock();
self.db.compact()?;

View File

@@ -32,7 +32,7 @@ pub(super) struct CostBasisNode {
}
impl CostBasisNode {
#[inline]
#[inline(always)]
fn new(sats: i64, usd: i128, is_sth: bool) -> Self {
Self {
all_sats: sats,
@@ -237,7 +237,7 @@ impl CostBasisFenwick {
let mut sat_buckets = [0usize; PERCENTILES_LEN + 2];
self.tree
.batch_kth(&sat_targets, &sat_field, &mut sat_buckets);
.kth(&sat_targets, &sat_field, &mut sat_buckets);
result.min_price = bucket_to_cents(sat_buckets[0]);
(0..PERCENTILES_LEN).for_each(|i| {
@@ -254,7 +254,7 @@ impl CostBasisFenwick {
let mut usd_buckets = [0usize; PERCENTILES_LEN];
self.tree
.batch_kth(&usd_targets, &usd_field, &mut usd_buckets);
.kth(&usd_targets, &usd_field, &mut usd_buckets);
(0..PERCENTILES_LEN).for_each(|i| {
result.usd_prices[i] = bucket_to_cents(usd_buckets[i]);
@@ -310,16 +310,16 @@ impl CostBasisFenwick {
}
// -----------------------------------------------------------------------
// Profitability queries (all cohort only)
// Profitability queries
// -----------------------------------------------------------------------
/// Compute profitability range buckets from current spot price.
/// Returns 25 ranges: (sats, usd_raw) per range.
/// Returns 25 ranges with all/sth splits.
pub(super) fn profitability(
&self,
spot_price: Cents,
) -> [(u64, u128); PROFITABILITY_RANGE_COUNT] {
let mut result = [(0u64, 0u128); PROFITABILITY_RANGE_COUNT];
) -> [ProfitabilityRangeResult; PROFITABILITY_RANGE_COUNT] {
let mut result = [ProfitabilityRangeResult::ZERO; PROFITABILITY_RANGE_COUNT];
if self.totals.all_sats <= 0 {
return result;
@@ -327,34 +327,54 @@ impl CostBasisFenwick {
let boundaries = compute_profitability_boundaries(spot_price);
let mut prev_sats: i64 = 0;
let mut prev_usd: i128 = 0;
let mut prev = CostBasisNode::default();
for (i, &boundary) in boundaries.iter().enumerate() {
let boundary_bucket = cents_to_bucket(boundary);
// prefix_sum through the bucket BEFORE the boundary
let cum = if boundary_bucket > 0 {
self.tree.prefix_sum(boundary_bucket - 1)
} else {
CostBasisNode::default()
};
let range_sats = cum.all_sats - prev_sats;
let range_usd = cum.all_usd - prev_usd;
result[i] = (range_sats.max(0) as u64, range_usd.max(0) as u128);
prev_sats = cum.all_sats;
prev_usd = cum.all_usd;
result[i] = ProfitabilityRangeResult {
all_sats: (cum.all_sats - prev.all_sats).max(0) as u64,
all_usd: (cum.all_usd - prev.all_usd).max(0) as u128,
sth_sats: (cum.sth_sats - prev.sth_sats).max(0) as u64,
sth_usd: (cum.sth_usd - prev.sth_usd).max(0) as u128,
};
prev = cum;
}
// Last range: everything >= last boundary
let remaining_sats = self.totals.all_sats - prev_sats;
let remaining_usd = self.totals.all_usd - prev_usd;
result[PROFITABILITY_RANGE_COUNT - 1] =
(remaining_sats.max(0) as u64, remaining_usd.max(0) as u128);
result[PROFITABILITY_RANGE_COUNT - 1] = ProfitabilityRangeResult {
all_sats: (self.totals.all_sats - prev.all_sats).max(0) as u64,
all_usd: (self.totals.all_usd - prev.all_usd).max(0) as u128,
sth_sats: (self.totals.sth_sats - prev.sth_sats).max(0) as u64,
sth_usd: (self.totals.sth_usd - prev.sth_usd).max(0) as u128,
};
result
}
}
/// Per-range profitability result with all/sth split.
#[derive(Clone, Copy)]
pub(super) struct ProfitabilityRangeResult {
pub all_sats: u64,
pub all_usd: u128,
pub sth_sats: u64,
pub sth_usd: u128,
}
impl ProfitabilityRangeResult {
const ZERO: Self = Self {
all_sats: 0,
all_usd: 0,
sth_sats: 0,
sth_usd: 0,
};
}
/// Result of a percentile computation for one cohort.
#[derive(Default)]
pub(super) struct PercentileResult {

View File

@@ -25,7 +25,7 @@ use crate::{
state::UTXOCohortState,
},
indexes,
internal::{AmountPerBlock, CachedWindowStarts},
internal::{AmountPerBlockCumulativeWithSums, CachedWindowStarts},
prices,
};
@@ -50,7 +50,7 @@ pub struct UTXOCohorts<M: StorageMode = Rw> {
#[traversable(rename = "type")]
pub type_: SpendableType<UTXOCohortVecs<TypeCohortMetrics<M>>>,
pub profitability: ProfitabilityMetrics<M>,
pub matured: AgeRange<AmountPerBlock<M>>,
pub matured: AgeRange<AmountPerBlockCumulativeWithSums<M>>,
#[traversable(skip)]
pub(super) fenwick: CostBasisFenwick,
/// Cached partition_point positions for tick_tock boundary searches.
@@ -178,7 +178,7 @@ impl UTXOCohorts<Rw> {
);
// Phase 3b: Import profitability metrics (derived from "all" during k-way merge).
let profitability = ProfitabilityMetrics::forced_import(db, v, indexes)?;
let profitability = ProfitabilityMetrics::forced_import(db, v, indexes, cached_starts)?;
// Phase 4: Import aggregate cohorts.
@@ -256,10 +256,17 @@ impl UTXOCohorts<Rw> {
let under_amount = UnderAmount::try_new(&minimal_no_state)?;
let over_amount = OverAmount::try_new(&minimal_no_state)?;
let prefix = CohortContext::Utxo.prefix();
let matured = AgeRange::try_new(&|_f: Filter,
name: &'static str|
-> Result<AmountPerBlock> {
AmountPerBlock::forced_import(db, &format!("utxo_{name}_matured"), v, indexes)
-> Result<AmountPerBlockCumulativeWithSums> {
AmountPerBlockCumulativeWithSums::forced_import(
db,
&format!("{prefix}_{name}_matured_supply"),
v,
indexes,
cached_starts,
)
})?;
Ok(Self {
@@ -338,7 +345,7 @@ impl UTXOCohorts<Rw> {
matured: &AgeRange<Sats>,
) -> Result<()> {
for (v, &sats) in self.matured.iter_mut().zip(matured.iter()) {
v.sats.height.truncate_push(height, sats)?;
v.base.sats.height.truncate_push(height, sats)?;
}
Ok(())
}
@@ -509,10 +516,13 @@ impl UTXOCohorts<Rw> {
.try_for_each(|v| v.compute_rest_part1(prices, starting_indexes, exit))?;
}
// Compute matured cents from sats × price
// Compute matured cumulative + cents from sats × price
self.matured
.par_iter_mut()
.try_for_each(|v| v.compute(prices, starting_indexes.height, exit))?;
.try_for_each(|v| v.compute_rest(starting_indexes.height, prices, exit))?;
// Compute profitability supply cents and realized price
self.profitability.compute(prices, starting_indexes, exit)?;
Ok(())
}
@@ -709,8 +719,10 @@ impl UTXOCohorts<Rw> {
}
vecs.extend(self.profitability.collect_all_vecs_mut());
for v in self.matured.iter_mut() {
vecs.push(&mut v.sats.height);
vecs.push(&mut v.cents.height);
vecs.push(&mut v.base.sats.height);
vecs.push(&mut v.base.cents.height);
vecs.push(&mut v.cumulative.sats.height);
vecs.push(&mut v.cumulative.cents.height);
}
vecs.into_par_iter()
}
@@ -727,7 +739,7 @@ impl UTXOCohorts<Rw> {
.chain(
self.matured
.iter()
.map(|v| Height::from(v.min_stateful_len())),
.map(|v| Height::from(v.base.min_stateful_len())),
)
.min()
.unwrap_or_default()

View File

@@ -6,7 +6,7 @@ use brk_types::{BasisPoints16, Cents, CentsCompact, CostBasisDistribution, Date,
use crate::distribution::metrics::{CostBasis, ProfitabilityMetrics};
use super::fenwick::PercentileResult;
use super::fenwick::{PercentileResult, ProfitabilityRangeResult};
use super::groups::UTXOCohorts;
use super::COST_BASIS_PRICE_DIGITS;
@@ -104,7 +104,7 @@ fn push_cost_basis(
}
/// Convert raw (cents × sats) accumulator to Dollars (÷ 100 for cents→dollars, ÷ 1e8 for sats).
#[inline]
#[inline(always)]
fn raw_usd_to_dollars(raw: u128) -> Dollars {
Dollars::from(raw as f64 / 1e10)
}
@@ -112,25 +112,41 @@ fn raw_usd_to_dollars(raw: u128) -> Dollars {
/// Push profitability range + profit/loss aggregate values to vecs.
fn push_profitability(
height: Height,
buckets: &[(u64, u128); PROFITABILITY_RANGE_COUNT],
buckets: &[ProfitabilityRangeResult; PROFITABILITY_RANGE_COUNT],
metrics: &mut ProfitabilityMetrics,
) -> Result<()> {
// Truncate all buckets once upfront to avoid per-push checks
metrics.truncate(height)?;
// Push 25 range buckets
for (i, bucket) in metrics.range.as_array_mut().into_iter().enumerate() {
let (sats, usd_raw) = buckets[i];
bucket.truncate_push(height, Sats::from(sats), raw_usd_to_dollars(usd_raw))?;
let r = &buckets[i];
bucket.push(
Sats::from(r.all_sats),
Sats::from(r.sth_sats),
raw_usd_to_dollars(r.all_usd),
raw_usd_to_dollars(r.sth_usd),
);
}
// Profit: forward cumulative sum over ranges[0..15], pushed in reverse.
// profit[0] (breakeven) = sum(0..=13), ..., profit[13] (_500pct) = ranges[0]
let profit_arr = metrics.profit.as_array_mut();
let mut cum_sats = 0u64;
let mut cum_sth_sats = 0u64;
let mut cum_usd = 0u128;
let mut cum_sth_usd = 0u128;
for i in 0..PROFIT_COUNT {
cum_sats += buckets[i].0;
cum_usd += buckets[i].1;
profit_arr[PROFIT_COUNT - 1 - i]
.truncate_push(height, Sats::from(cum_sats), raw_usd_to_dollars(cum_usd))?;
cum_sats += buckets[i].all_sats;
cum_sth_sats += buckets[i].sth_sats;
cum_usd += buckets[i].all_usd;
cum_sth_usd += buckets[i].sth_usd;
profit_arr[PROFIT_COUNT - 1 - i].push(
Sats::from(cum_sats),
Sats::from(cum_sth_sats),
raw_usd_to_dollars(cum_usd),
raw_usd_to_dollars(cum_sth_usd),
);
}
// Loss: backward cumulative sum over ranges[15..25], pushed in reverse.
@@ -138,12 +154,21 @@ fn push_profitability(
let loss_arr = metrics.loss.as_array_mut();
let loss_count = loss_arr.len();
cum_sats = 0;
cum_sth_sats = 0;
cum_usd = 0;
cum_sth_usd = 0;
for i in 0..loss_count {
cum_sats += buckets[PROFITABILITY_RANGE_COUNT - 1 - i].0;
cum_usd += buckets[PROFITABILITY_RANGE_COUNT - 1 - i].1;
loss_arr[loss_count - 1 - i]
.truncate_push(height, Sats::from(cum_sats), raw_usd_to_dollars(cum_usd))?;
let r = &buckets[PROFITABILITY_RANGE_COUNT - 1 - i];
cum_sats += r.all_sats;
cum_sth_sats += r.sth_sats;
cum_usd += r.all_usd;
cum_sth_usd += r.sth_usd;
loss_arr[loss_count - 1 - i].push(
Sats::from(cum_sats),
Sats::from(cum_sth_sats),
raw_usd_to_dollars(cum_usd),
raw_usd_to_dollars(cum_sth_usd),
);
}
Ok(())

View File

@@ -16,8 +16,8 @@ impl UTXOCohorts<Rw> {
/// Since timestamps are monotonic, positions only advance forward.
/// Complexity: O(k * c) where k = 20 boundaries, c = ~1 (forward scan steps).
///
/// Returns how many sats matured INTO each cohort from the younger adjacent one.
/// `under_1h` is always zero since nothing ages into the youngest cohort.
/// Returns how many sats matured OUT OF each cohort into the older adjacent one.
/// `over_15y` is always zero since nothing ages out of the oldest cohort.
pub(crate) fn tick_tock_next_block(
&mut self,
chain_state: &[BlockState],
@@ -92,7 +92,7 @@ impl UTXOCohorts<Rw> {
if let Some(state) = age_cohorts[boundary_idx + 1].as_mut() {
state.increment_snapshot(&snapshot);
}
matured[boundary_idx + 1] += block_state.supply.value;
matured[boundary_idx] += block_state.supply.value;
}
}

View File

@@ -1,21 +1,43 @@
use brk_cohort::{Loss, Profit, ProfitabilityRange};
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Dollars, Height, Sats, Version};
use vecdb::{AnyStoredVec, AnyVec, Database, Rw, StorageMode, WritableVec};
use brk_types::{
BasisPoints32, BasisPointsSigned32, Cents, Dollars, Height, Indexes, Sats, StoredF32, Version,
};
use vecdb::{AnyStoredVec, AnyVec, Database, Exit, Rw, StorageMode, WritableVec};
use crate::{indexes, internal::PerBlock};
use crate::{
indexes,
internal::{
AmountPerBlock, AmountPerBlockWithDeltas, CachedWindowStarts, Identity, LazyPerBlock,
PerBlock, PriceWithRatioPerBlock, RatioPerBlock,
},
prices,
};
#[derive(Traversable)]
pub struct WithSth<All, Sth = All> {
pub all: All,
pub sth: Sth,
}
/// Supply + realized cap for a single profitability bucket.
#[derive(Traversable)]
pub struct ProfitabilityBucket<M: StorageMode = Rw> {
pub supply: PerBlock<Sats, M>,
pub realized_cap: PerBlock<Dollars, M>,
pub supply: WithSth<AmountPerBlockWithDeltas<M>, AmountPerBlock<M>>,
pub realized_cap: WithSth<PerBlock<Dollars, M>>,
pub realized_price: PriceWithRatioPerBlock<M>,
pub mvrv: LazyPerBlock<StoredF32>,
pub nupl: RatioPerBlock<BasisPointsSigned32, M>,
}
impl<M: StorageMode> ProfitabilityBucket<M> {
fn min_len(&self) -> usize {
self.supply.height.len().min(self.realized_cap.height.len())
self.supply
.all
.sats
.height
.len()
.min(self.realized_cap.all.height.len())
}
}
@@ -25,45 +47,154 @@ impl ProfitabilityBucket {
name: &str,
version: Version,
indexes: &indexes::Vecs,
cached_starts: &CachedWindowStarts,
) -> Result<Self> {
let realized_price = PriceWithRatioPerBlock::forced_import(
db,
&format!("{name}_realized_price"),
version,
indexes,
)?;
let mvrv = LazyPerBlock::from_lazy::<Identity<StoredF32>, BasisPoints32>(
&format!("{name}_mvrv"),
version,
&realized_price.ratio,
);
Ok(Self {
supply: PerBlock::forced_import(
supply: WithSth {
all: AmountPerBlockWithDeltas::forced_import(
db,
&format!("{name}_supply"),
version,
indexes,
cached_starts,
)?,
sth: AmountPerBlock::forced_import(
db,
&format!("{name}_sth_supply"),
version,
indexes,
)?,
},
realized_cap: WithSth {
all: PerBlock::forced_import(
db,
&format!("{name}_realized_cap"),
version,
indexes,
)?,
sth: PerBlock::forced_import(
db,
&format!("{name}_sth_realized_cap"),
version,
indexes,
)?,
},
realized_price,
mvrv,
nupl: RatioPerBlock::forced_import_raw(
db,
&format!("{name}_supply"),
version,
indexes,
)?,
realized_cap: PerBlock::forced_import(
db,
&format!("{name}_realized_cap"),
version,
&format!("{name}_nupl"),
version + Version::ONE,
indexes,
)?,
})
}
pub(crate) fn truncate_push(
#[inline(always)]
pub(crate) fn truncate(&mut self, height: Height) -> Result<()> {
self.supply.all.sats.height.truncate_if_needed(height)?;
self.supply.sth.sats.height.truncate_if_needed(height)?;
self.realized_cap.all.height.truncate_if_needed(height)?;
self.realized_cap.sth.height.truncate_if_needed(height)?;
Ok(())
}
#[inline(always)]
pub(crate) fn push(
&mut self,
height: Height,
supply: Sats,
sth_supply: Sats,
realized_cap: Dollars,
sth_realized_cap: Dollars,
) {
self.supply.all.sats.height.push(supply);
self.supply.sth.sats.height.push(sth_supply);
self.realized_cap.all.height.push(realized_cap);
self.realized_cap.sth.height.push(sth_realized_cap);
}
pub(crate) fn compute(
&mut self,
prices: &prices::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
self.supply.height.truncate_push(height, supply)?;
self.realized_cap
.height
.truncate_push(height, realized_cap)?;
let max_from = starting_indexes.height;
self.supply.all.compute(prices, max_from, exit)?;
self.supply.sth.compute(prices, max_from, exit)?;
// Realized price cents = realized_cap_cents × ONE_BTC / supply_sats
self.realized_price.cents.height.compute_transform2(
max_from,
&self.realized_cap.all.height,
&self.supply.all.sats.height,
|(i, cap_dollars, supply_sats, ..)| {
let cap_cents = Cents::from(cap_dollars).as_u128();
let supply = supply_sats.as_u128();
if supply == 0 {
(i, Cents::ZERO)
} else {
(i, Cents::from(cap_cents * Sats::ONE_BTC_U128 / supply))
}
},
exit,
)?;
// Ratio (spot / realized_price) → feeds MVRV lazily
self.realized_price
.compute_ratio(starting_indexes, &prices.spot.cents.height, exit)?;
// NUPL = (spot - realized_price) / spot
self.nupl.bps.height.compute_transform2(
max_from,
&prices.spot.cents.height,
&self.realized_price.cents.height,
|(i, spot, realized, ..)| {
let p = spot.as_u128();
if p == 0 {
(i, BasisPointsSigned32::ZERO)
} else {
let rp = realized.as_u128();
let bps = ((p as i128 - rp as i128) * 10000) / p as i128;
(i, BasisPointsSigned32::from(bps as i32))
}
},
exit,
)?;
Ok(())
}
pub(crate) fn collect_all_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
vec![
&mut self.supply.height as &mut dyn AnyStoredVec,
&mut self.realized_cap.height as &mut dyn AnyStoredVec,
&mut self.supply.all.inner.sats.height as &mut dyn AnyStoredVec,
&mut self.supply.all.inner.cents.height as &mut dyn AnyStoredVec,
&mut self.supply.sth.sats.height as &mut dyn AnyStoredVec,
&mut self.supply.sth.cents.height as &mut dyn AnyStoredVec,
&mut self.realized_cap.all.height as &mut dyn AnyStoredVec,
&mut self.realized_cap.sth.height as &mut dyn AnyStoredVec,
&mut self.realized_price.cents.height as &mut dyn AnyStoredVec,
&mut self.realized_price.bps.height as &mut dyn AnyStoredVec,
&mut self.nupl.bps.height as &mut dyn AnyStoredVec,
]
}
}
/// All profitability metrics: 25 ranges + 15 profit thresholds + 10 loss thresholds.
/// All profitability metrics: 25 ranges + 14 profit thresholds + 9 loss thresholds.
#[derive(Traversable)]
pub struct ProfitabilityMetrics<M: StorageMode = Rw> {
pub range: ProfitabilityRange<ProfitabilityBucket<M>>,
@@ -72,32 +203,46 @@ pub struct ProfitabilityMetrics<M: StorageMode = Rw> {
}
impl<M: StorageMode> ProfitabilityMetrics<M> {
pub(crate) fn min_stateful_len(&self) -> usize {
self.range.iter()
pub fn iter(&self) -> impl Iterator<Item = &ProfitabilityBucket<M>> {
self.range
.iter()
.chain(self.profit.iter())
.chain(self.loss.iter())
.map(|b| b.min_len())
.min()
.unwrap_or(0)
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut ProfitabilityBucket<M>> {
self.range
.iter_mut()
.chain(self.profit.iter_mut())
.chain(self.loss.iter_mut())
}
pub(crate) fn min_stateful_len(&self) -> usize {
self.iter().map(|b| b.min_len()).min().unwrap_or(0)
}
}
impl ProfitabilityMetrics {
pub(crate) fn truncate(&mut self, height: Height) -> Result<()> {
self.iter_mut().try_for_each(|b| b.truncate(height))
}
pub(crate) fn forced_import(
db: &Database,
version: Version,
indexes: &indexes::Vecs,
cached_starts: &CachedWindowStarts,
) -> Result<Self> {
let range = ProfitabilityRange::try_new(|name| {
ProfitabilityBucket::forced_import(db, name, version, indexes)
ProfitabilityBucket::forced_import(db, name, version, indexes, cached_starts)
})?;
let profit = Profit::try_new(|name| {
ProfitabilityBucket::forced_import(db, name, version, indexes)
ProfitabilityBucket::forced_import(db, name, version, indexes, cached_starts)
})?;
let loss = Loss::try_new(|name| {
ProfitabilityBucket::forced_import(db, name, version, indexes)
ProfitabilityBucket::forced_import(db, name, version, indexes, cached_starts)
})?;
Ok(Self {
@@ -107,18 +252,21 @@ impl ProfitabilityMetrics {
})
}
pub(crate) fn compute(
&mut self,
prices: &prices::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
self.iter_mut()
.try_for_each(|b| b.compute(prices, starting_indexes, exit))
}
pub(crate) fn collect_all_vecs_mut(&mut self) -> Vec<&mut dyn AnyStoredVec> {
let mut vecs = Vec::new();
for bucket in self.range.iter_mut() {
vecs.extend(bucket.collect_all_vecs_mut());
}
for bucket in self.profit.iter_mut() {
vecs.extend(bucket.collect_all_vecs_mut());
}
for bucket in self.loss.iter_mut() {
for bucket in self.iter_mut() {
vecs.extend(bucket.collect_all_vecs_mut());
}
vecs
}
}

View File

@@ -1,12 +1,12 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Cents, Height, Indexes, Version};
use brk_types::{Cents, Dollars, Height, Indexes, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{AnyStoredVec, AnyVec, Exit, Rw, StorageMode, WritableVec};
use vecdb::{AnyStoredVec, AnyVec, Exit, ReadableCloneableVec, Rw, StorageMode, WritableVec};
use crate::{
distribution::{metrics::ImportConfig, state::UnrealizedState},
internal::FiatPerBlockCumulativeWithSums,
internal::{FiatPerBlockCumulativeWithSums, LazyPerBlock, NegCentsUnsignedToDollars},
};
use super::UnrealizedMinimal;
@@ -19,16 +19,28 @@ pub struct UnrealizedBasic<M: StorageMode = Rw> {
pub minimal: UnrealizedMinimal<M>,
pub profit: FiatPerBlockCumulativeWithSums<Cents, M>,
pub loss: FiatPerBlockCumulativeWithSums<Cents, M>,
#[traversable(wrap = "loss", rename = "negative")]
pub neg_loss: LazyPerBlock<Dollars, Cents>,
}
impl UnrealizedBasic {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
let v1 = Version::ONE;
let loss: FiatPerBlockCumulativeWithSums<Cents> = cfg.import("unrealized_loss", v1)?;
let neg_loss = LazyPerBlock::from_computed::<NegCentsUnsignedToDollars>(
&cfg.name("neg_unrealized_loss"),
cfg.version,
loss.base.cents.height.read_only_boxed_clone(),
&loss.base.cents,
);
Ok(Self {
minimal: UnrealizedMinimal::forced_import(cfg)?,
profit: cfg.import("unrealized_profit", v1)?,
loss: cfg.import("unrealized_loss", v1)?,
loss,
neg_loss,
})
}

View File

@@ -2,18 +2,16 @@ use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Cents, CentsSigned, Height, Indexes, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{AnyStoredVec, Exit, ReadableCloneableVec, Rw, StorageMode};
use vecdb::{AnyStoredVec, Exit, Rw, StorageMode};
use crate::{
distribution::{
metrics::ImportConfig,
state::UnrealizedState,
},
internal::{CentsSubtractToCentsSigned, FiatPerBlock, LazyPerBlock, NegCentsUnsignedToDollars},
internal::{CentsSubtractToCentsSigned, FiatPerBlock},
};
use brk_types::Dollars;
use super::UnrealizedBasic;
#[derive(Deref, DerefMut, Traversable)]
@@ -23,27 +21,16 @@ pub struct UnrealizedCore<M: StorageMode = Rw> {
#[traversable(flatten)]
pub basic: UnrealizedBasic<M>,
#[traversable(wrap = "loss", rename = "negative")]
pub neg_loss: LazyPerBlock<Dollars, Cents>,
pub net_pnl: FiatPerBlock<CentsSigned, M>,
}
impl UnrealizedCore {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
let basic = UnrealizedBasic::forced_import(cfg)?;
let neg_unrealized_loss = LazyPerBlock::from_computed::<NegCentsUnsignedToDollars>(
&cfg.name("neg_unrealized_loss"),
cfg.version,
basic.loss.base.cents.height.read_only_boxed_clone(),
&basic.loss.base.cents,
);
let net_unrealized_pnl = cfg.import("net_unrealized_pnl", Version::ZERO)?;
Ok(Self {
basic,
neg_loss: neg_unrealized_loss,
net_pnl: net_unrealized_pnl,
})
}

View File

@@ -1,6 +1,6 @@
use brk_error::Result;
use brk_types::{Bitcoin, Dollars, Indexes, Sats, StoredF32};
use vecdb::{Exit, ReadableVec};
use brk_types::{Bitcoin, Dollars, Indexes, StoredF32};
use vecdb::Exit;
use super::{gini, Vecs};
use crate::{distribution, internal::RatioDollarsBp32, market, mining, transactions};
@@ -180,14 +180,12 @@ impl Vecs {
// Seller Exhaustion Constant: % supply_in_profit × 30d_volatility
self.seller_exhaustion_constant
.height
.compute_transform2(
.compute_transform3(
starting_indexes.height,
&all_metrics.supply.in_profit.sats.height,
&market.volatility._1m.height,
|(i, profit_sats, volatility, ..)| {
let total_sats: Sats = supply_total_sats
.collect_one(i)
.unwrap_or_default();
supply_total_sats,
|(i, profit_sats, volatility, total_sats, ..)| {
let total = total_sats.as_u128() as f64;
if total == 0.0 {
(i, StoredF32::from(0.0f32))

View File

@@ -1,6 +1,6 @@
use brk_error::Result;
use brk_indexer::Indexer;
use brk_types::{Indexes, Sats, TxInIndex, TxIndex, TxOutIndex, Vout};
use brk_types::{Indexes, Sats, TxIndex, TxOutIndex, Vout};
use tracing::info;
use vecdb::{AnyStoredVec, AnyVec, Database, Exit, ReadableVec, VecIndex, WritableVec};
@@ -98,11 +98,11 @@ impl Vecs {
out_value[entry.original_idx] = entry.value;
}
self.txout_index.truncate_if_needed_at(batch_start)?;
self.value.truncate_if_needed_at(batch_start)?;
for i in 0..batch_len {
let txin_index = TxInIndex::from(batch_start + i);
self.txout_index
.truncate_push(txin_index, out_txout_index[i])?;
self.value.truncate_push(txin_index, out_value[i])?;
self.txout_index.push(out_txout_index[i]);
self.value.push(out_value[i]);
}
if batch_end < target {

View File

@@ -71,18 +71,23 @@ impl ExpandingPercentiles {
self.tree.add(Self::to_bucket(value), &1);
}
/// Compute 6 percentiles in one call. O(6 × log N).
/// Quantiles q must be in (0, 1). Output is in BPS.
/// Compute 6 percentiles in one call via kth. O(6 × log N) but with
/// shared tree traversal across all 6 targets for better cache locality.
/// Quantiles q must be sorted ascending in (0, 1). Output is in BPS.
pub fn quantiles(&self, qs: &[f64; 6], out: &mut [u32; 6]) {
if self.count == 0 {
out.iter_mut().for_each(|o| *o = 0);
return;
}
let mut targets = [0u32; 6];
for (i, &q) in qs.iter().enumerate() {
let k = ((q * self.count as f64).ceil() as u32).clamp(1, self.count);
// kth with 0-indexed k: k-1; result is 0-indexed bucket
let bucket = self.tree.kth(k - 1, |n| *n);
out[i] = bucket as u32 * BUCKET_BPS as u32;
targets[i] = k - 1; // 0-indexed
}
let mut buckets = [0usize; 6];
self.tree.kth(&targets, &|n: &u32| *n, &mut buckets);
for (i, bucket) in buckets.iter().enumerate() {
out[i] = *bucket as u32 * BUCKET_BPS as u32;
}
}
}

View File

@@ -54,42 +54,15 @@ impl<N: FenwickNode> FenwickTree<N> {
result
}
/// Find the 0-indexed bucket containing the k-th element (0-indexed k).
/// Find the 0-indexed bucket containing the k-th element for each target.
///
/// `field_fn` extracts the relevant count field from a node.
/// The value type `V` must support comparison and subtraction
/// (works with `u32`, `i64`, `i128`).
#[inline]
pub fn kth<V, F>(&self, k: V, field_fn: F) -> usize
where
V: Copy + PartialOrd + std::ops::SubAssign,
F: Fn(&N) -> V,
{
debug_assert!(self.size > 0);
let mut pos = 0usize;
let mut remaining = k;
let mut bit = 1usize << (usize::BITS - 1 - self.size.leading_zeros());
while bit > 0 {
let next = pos + bit;
if next <= self.size {
let val = field_fn(&self.tree[next]);
if remaining >= val {
remaining -= val;
pos = next;
}
}
bit >>= 1;
}
pos // 0-indexed bucket
}
/// Batch kth for sorted targets. Processes all targets at each tree level
/// for better cache locality vs individual kth() calls.
/// `sorted_targets` must be sorted ascending. `out` receives the 0-indexed
/// bucket for each target. Both slices must have the same length.
///
/// `sorted_targets` must be sorted ascending. `out` receives the 0-indexed bucket
/// for each target. Both slices must have the same length.
/// Processes all targets at each tree level for better cache locality.
#[inline]
pub fn batch_kth<V, F>(&self, sorted_targets: &[V], field_fn: &F, out: &mut [usize])
pub fn kth<V, F>(&self, sorted_targets: &[V], field_fn: &F, out: &mut [usize])
where
V: Copy + PartialOrd + std::ops::SubAssign,
F: Fn(&N) -> V,
@@ -162,18 +135,14 @@ mod tests {
tree.add(3, &5);
tree.add(4, &1);
// kth(0) = first element → bucket 0
assert_eq!(tree.kth(0u32, |n| *n), 0);
// kth(2) = 3rd element → bucket 0 (last of bucket 0)
assert_eq!(tree.kth(2u32, |n| *n), 0);
// kth(3) = 4th element → bucket 1
assert_eq!(tree.kth(3u32, |n| *n), 1);
// kth(4) = 5th element → bucket 1
assert_eq!(tree.kth(4u32, |n| *n), 1);
// kth(5) = 6th element → bucket 3 (bucket 2 is empty)
assert_eq!(tree.kth(5u32, |n| *n), 3);
// kth(10) = 11th element → bucket 4
assert_eq!(tree.kth(10u32, |n| *n), 4);
let mut out = [0usize; 6];
tree.kth(&[0u32, 2, 3, 4, 5, 10], &|n: &u32| *n, &mut out);
assert_eq!(out[0], 0); // kth(0) → bucket 0
assert_eq!(out[1], 0); // kth(2) → bucket 0 (last of bucket 0)
assert_eq!(out[2], 1); // kth(3) → bucket 1
assert_eq!(out[3], 1); // kth(4) → bucket 1
assert_eq!(out[4], 3); // kth(5) → bucket 3 (bucket 2 is empty)
assert_eq!(out[5], 4); // kth(10) → bucket 4
}
#[test]

View File

@@ -8,6 +8,11 @@ use super::sliding_window::SlidingWindowSorted;
/// Compute all 8 rolling distribution stats (avg, min, max, p10, p25, median, p75, p90)
/// in a single sorted-vec pass per window.
///
/// When computing multiple windows from the same source, pass the same
/// `&mut Option<(usize, Vec<f64>)>` cache to each call — the first call reads
/// and caches, subsequent calls reuse if their range is covered.
/// Process the largest window first (1y) so its cache covers all smaller windows.
#[allow(clippy::too_many_arguments)]
pub fn compute_rolling_distribution_from_starts<I, T, A>(
max_from: I,
@@ -22,6 +27,7 @@ pub fn compute_rolling_distribution_from_starts<I, T, A>(
p75_out: &mut EagerVec<PcoVec<I, T>>,
p90_out: &mut EagerVec<PcoVec<I, T>>,
exit: &Exit,
values_cache: &mut Option<(usize, Vec<f64>)>,
) -> Result<()>
where
I: VecIndex,
@@ -68,8 +74,21 @@ where
} else {
0
};
let mut partial_values: Vec<f64> = Vec::with_capacity(end - range_start);
values.for_each_range_at(range_start, end, |a: A| partial_values.push(f64::from(a)));
// Reuse cached values if the cache covers our range, otherwise read and cache.
let need_read = match values_cache.as_ref() {
Some((cached_start, cached)) => {
range_start < *cached_start || end > *cached_start + cached.len()
}
None => true,
};
if need_read {
let mut v = Vec::with_capacity(end - range_start);
values.for_each_range_at(range_start, end, |a: A| v.push(f64::from(a)));
*values_cache = Some((range_start, v));
}
let (cached_start, cached) = values_cache.as_ref().unwrap();
let partial_values = &cached[(range_start - cached_start)..(end - cached_start)];
let capacity = if skip > 0 && skip < end {
let first_start = window_starts.collect_one_at(skip).unwrap().to_usize();
@@ -83,7 +102,7 @@ where
let mut window = SlidingWindowSorted::with_capacity(capacity);
if skip > 0 {
window.reconstruct(&partial_values, range_start, skip);
window.reconstruct(partial_values, range_start, skip);
}
let starts_batch = window_starts.collect_range_at(skip, end);
@@ -92,7 +111,7 @@ where
let i = skip + j;
let v = partial_values[i - range_start];
let start_usize = start.to_usize();
window.advance(v, start_usize, &partial_values, range_start);
window.advance(v, start_usize, partial_values, range_start);
if window.is_empty() {
let zero = T::from(0.0);

View File

@@ -26,10 +26,20 @@ impl<A> Windows<A> {
[&self._24h, &self._1w, &self._1m, &self._1y]
}
/// Largest window first (1y, 1m, 1w, 24h).
pub fn as_array_largest_first(&self) -> [&A; 4] {
[&self._1y, &self._1m, &self._1w, &self._24h]
}
pub fn as_mut_array(&mut self) -> [&mut A; 4] {
[&mut self._24h, &mut self._1w, &mut self._1m, &mut self._1y]
}
/// Largest window first (1y, 1m, 1w, 24h).
pub fn as_mut_array_largest_first(&mut self) -> [&mut A; 4] {
[&mut self._1y, &mut self._1m, &mut self._1w, &mut self._24h]
}
pub fn as_mut_array_from_1w(&mut self) -> [&mut A; 3] {
[&mut self._1w, &mut self._1m, &mut self._1y]
}

View File

@@ -1,7 +1,7 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Cents, Height, Sats, Version};
use vecdb::{Database, EagerVec, Exit, PcoVec, Rw, StorageMode};
use vecdb::{Database, Exit, Rw, StorageMode};
use crate::{
indexes,
@@ -37,17 +37,6 @@ impl AmountPerBlockCumulative {
})
}
pub(crate) fn compute_with(
&mut self,
max_from: Height,
prices: &prices::Vecs,
exit: &Exit,
compute_sats: impl FnOnce(&mut EagerVec<PcoVec<Height, Sats>>) -> Result<()>,
) -> Result<()> {
compute_sats(&mut self.base.sats.height)?;
self.compute(prices, max_from, exit)
}
pub(crate) fn compute(
&mut self,
prices: &prices::Vecs,

View File

@@ -6,6 +6,7 @@ mod lazy;
mod lazy_derived_resolutions;
mod lazy_rolling_sum;
mod rolling_distribution;
mod with_deltas;
pub use base::*;
pub use cumulative::*;
@@ -15,3 +16,4 @@ pub use lazy::*;
pub use lazy_derived_resolutions::*;
pub use lazy_rolling_sum::*;
pub use rolling_distribution::*;
pub use with_deltas::*;

View File

@@ -42,11 +42,13 @@ impl RollingDistributionSlot {
sats_source: &impl ReadableVec<Height, Sats>,
cents_source: &impl ReadableVec<Height, Cents>,
exit: &Exit,
sats_cache: &mut Option<(usize, Vec<f64>)>,
cents_cache: &mut Option<(usize, Vec<f64>)>,
) -> Result<()> {
let d = &mut self.distribution;
macro_rules! compute_unit {
($unit:ident, $source:expr) => {
($unit:ident, $source:expr, $cache:expr) => {
compute_rolling_distribution_from_starts(
max_from,
starts,
@@ -60,11 +62,12 @@ impl RollingDistributionSlot {
&mut d.pct75.$unit.height,
&mut d.pct90.$unit.height,
exit,
$cache,
)?
};
}
compute_unit!(sats, sats_source);
compute_unit!(cents, cents_source);
compute_unit!(sats, sats_source, sats_cache);
compute_unit!(cents, cents_source, cents_cache);
Ok(())
}
@@ -104,8 +107,23 @@ impl RollingDistributionAmountPerBlock {
cents_source: &impl ReadableVec<Height, Cents>,
exit: &Exit,
) -> Result<()> {
for (slot, starts) in self.0.as_mut_array().into_iter().zip(windows.as_array()) {
slot.compute(max_from, *starts, sats_source, cents_source, exit)?;
let mut sats_cache = None;
let mut cents_cache = None;
for (slot, starts) in self
.0
.as_mut_array_largest_first()
.into_iter()
.zip(windows.as_array_largest_first())
{
slot.compute(
max_from,
*starts,
sats_source,
cents_source,
exit,
&mut sats_cache,
&mut cents_cache,
)?;
}
Ok(())
}

View File

@@ -0,0 +1,41 @@
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{BasisPointsSigned32, Sats, SatsSigned, Version};
use derive_more::{Deref, DerefMut};
use vecdb::{Database, Rw, StorageMode};
use crate::{
indexes,
internal::{AmountPerBlock, CachedWindowStarts, LazyRollingDeltasFromHeight},
};
#[derive(Deref, DerefMut, Traversable)]
pub struct AmountPerBlockWithDeltas<M: StorageMode = Rw> {
#[deref]
#[deref_mut]
#[traversable(flatten)]
pub inner: AmountPerBlock<M>,
pub delta: LazyRollingDeltasFromHeight<Sats, SatsSigned, BasisPointsSigned32>,
}
impl AmountPerBlockWithDeltas {
pub(crate) fn forced_import(
db: &Database,
name: &str,
version: Version,
indexes: &indexes::Vecs,
cached_starts: &CachedWindowStarts,
) -> Result<Self> {
let inner = AmountPerBlock::forced_import(db, name, version, indexes)?;
let delta = LazyRollingDeltasFromHeight::new(
&format!("{name}_delta"),
version + Version::ONE,
&inner.sats.height,
cached_starts,
indexes,
);
Ok(Self { inner, delta })
}
}

View File

@@ -46,6 +46,7 @@ where
T: Copy + Ord + From<f64> + Default,
f64: From<T>,
{
let mut values_cache = None;
macro_rules! compute_window {
($w:ident) => {
compute_rolling_distribution_from_starts(
@@ -61,13 +62,15 @@ where
&mut self.0.pct75.$w.height,
&mut self.0.pct90.$w.height,
exit,
&mut values_cache,
)?
};
}
compute_window!(_24h);
compute_window!(_1w);
compute_window!(_1m);
// Largest window first: its cache covers all smaller windows.
compute_window!(_1y);
compute_window!(_1m);
compute_window!(_1w);
compute_window!(_24h);
Ok(())
}

View File

@@ -325,25 +325,33 @@ impl Computer {
.compute(indexer, &self.indexes, &starting_indexes, exit)
})?;
timed("Computed inputs", || {
self.inputs.compute(
indexer,
&self.indexes,
&self.blocks,
&starting_indexes,
exit,
)
})?;
timed("Computed scripts", || {
self.scripts.compute(
indexer,
&self.outputs,
&self.prices,
&starting_indexes,
exit,
)
})?;
// inputs and scripts are independent — parallelize
let (inputs_result, scripts_result) = rayon::join(
|| {
timed("Computed inputs", || {
self.inputs.compute(
indexer,
&self.indexes,
&self.blocks,
&starting_indexes,
exit,
)
})
},
|| {
timed("Computed scripts", || {
self.scripts.compute(
indexer,
&self.outputs,
&self.prices,
&starting_indexes,
exit,
)
})
},
);
inputs_result?;
scripts_result?;
timed("Computed outputs", || {
self.outputs.compute(

View File

@@ -18,62 +18,68 @@ impl Vecs {
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
self.coinbase.compute(
starting_indexes.height,
prices,
exit,
|vec| {
// Cursors avoid per-height PcoVec page decompression for the
// tx-indexed lookups. Coinbase tx_index values are strictly
// increasing, so the cursors only advance forward.
let mut txout_cursor = indexer.vecs.transactions.first_txout_index.cursor();
let mut count_cursor = indexes.tx_index.output_count.cursor();
vec.compute_transform(
starting_indexes.height,
&indexer.vecs.transactions.first_tx_index,
|(height, tx_index, ..)| {
let ti = tx_index.to_usize();
txout_cursor.advance(ti - txout_cursor.position());
let first_txout_index = txout_cursor.next().unwrap().to_usize();
count_cursor.advance(ti - count_cursor.position());
let output_count: usize = count_cursor.next().unwrap().into();
let sats = indexer.vecs.outputs.value.fold_range_at(
first_txout_index,
first_txout_index + output_count,
Sats::ZERO,
|acc, v| acc + v,
);
(height, sats)
},
exit,
)?;
Ok(())
},
)?;
// Coinbase fee is 0, so including it in the sum doesn't affect the result
// coinbase and fees are independent — parallelize
let window_starts = lookback.window_starts();
self.fees.compute(
starting_indexes.height,
&window_starts,
prices,
exit,
|vec| {
vec.compute_sum_from_indexes(
let (r_coinbase, r_fees) = rayon::join(
|| {
self.coinbase.compute(
starting_indexes.height,
&indexer.vecs.transactions.first_tx_index,
&indexes.height.tx_index_count,
&transactions_fees.fee.tx_index,
prices,
exit,
)?;
Ok(())
|vec| {
let mut txout_cursor =
indexer.vecs.transactions.first_txout_index.cursor();
let mut count_cursor = indexes.tx_index.output_count.cursor();
vec.compute_transform(
starting_indexes.height,
&indexer.vecs.transactions.first_tx_index,
|(height, tx_index, ..)| {
let ti = tx_index.to_usize();
txout_cursor.advance(ti - txout_cursor.position());
let first_txout_index =
txout_cursor.next().unwrap().to_usize();
count_cursor.advance(ti - count_cursor.position());
let output_count: usize =
count_cursor.next().unwrap().into();
let sats = indexer.vecs.outputs.value.fold_range_at(
first_txout_index,
first_txout_index + output_count,
Sats::ZERO,
|acc, v| acc + v,
);
(height, sats)
},
exit,
)?;
Ok(())
},
)
},
)?;
|| {
self.fees.compute(
starting_indexes.height,
&window_starts,
prices,
exit,
|vec| {
vec.compute_sum_from_indexes(
starting_indexes.height,
&indexer.vecs.transactions.first_tx_index,
&indexes.height.tx_index_count,
&transactions_fees.fee.tx_index,
exit,
)?;
Ok(())
},
)
},
);
r_coinbase?;
r_fees?;
self.subsidy.base.sats.height.compute_transform2(
starting_indexes.height,
@@ -110,7 +116,6 @@ impl Vecs {
},
)?;
// All-time cumulative fee dominance
self.fee_dominance
.compute_binary::<Sats, Sats, RatioSatsBp16>(
starting_indexes.height,
@@ -119,7 +124,6 @@ impl Vecs {
exit,
)?;
// Rolling fee dominance = sum(fees) / sum(coinbase)
self.fee_dominance_rolling
.compute_binary::<Sats, Sats, RatioSatsBp16, _, _>(
starting_indexes.height,
@@ -128,7 +132,6 @@ impl Vecs {
exit,
)?;
// All-time cumulative subsidy dominance
self.subsidy_dominance
.compute_binary::<Sats, Sats, RatioSatsBp16>(
starting_indexes.height,
@@ -144,7 +147,6 @@ impl Vecs {
exit,
)?;
// Fee Ratio Multiple: sum(coinbase) / sum(fees) per rolling window
self.fee_ratio_multiple
.compute_binary::<Dollars, Dollars, RatioDollarsBp32, _, _>(
starting_indexes.height,

View File

@@ -22,7 +22,7 @@ impl Vecs {
let version = parent_version;
let count = CountVecs::forced_import(&db, version, indexes, cached_starts)?;
let value = ValueVecs::forced_import(&db, version, indexes)?;
let value = ValueVecs::forced_import(&db, version, indexes, cached_starts)?;
let adoption = AdoptionVecs::forced_import(&db, version, indexes)?;
let this = Self {

View File

@@ -14,7 +14,7 @@ impl Vecs {
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
self.op_return.compute_with(
self.op_return.compute(
starting_indexes.height,
prices,
exit,

View File

@@ -3,20 +3,22 @@ use brk_types::Version;
use vecdb::Database;
use super::Vecs;
use crate::{indexes, internal::AmountPerBlockCumulative};
use crate::{indexes, internal::{AmountPerBlockCumulativeWithSums, CachedWindowStarts}};
impl Vecs {
pub(crate) fn forced_import(
db: &Database,
version: Version,
indexes: &indexes::Vecs,
cached_starts: &CachedWindowStarts,
) -> Result<Self> {
Ok(Self {
op_return: AmountPerBlockCumulative::forced_import(
op_return: AmountPerBlockCumulativeWithSums::forced_import(
db,
"op_return_value",
version,
indexes,
cached_starts,
)?,
})
}

View File

@@ -1,9 +1,9 @@
use brk_traversable::Traversable;
use vecdb::{Rw, StorageMode};
use crate::internal::AmountPerBlockCumulative;
use crate::internal::AmountPerBlockCumulativeWithSums;
#[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw> {
pub op_return: AmountPerBlockCumulative<M>,
pub op_return: AmountPerBlockCumulativeWithSums<M>,
}

View File

@@ -14,47 +14,7 @@ impl Vecs {
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
self.op_return.compute(
starting_indexes.height,
prices,
exit,
|height_vec| {
// Validate computed versions against dependencies
let op_return_dep_version = scripts.value.op_return.base.sats.height.version();
height_vec.validate_computed_version_or_reset(op_return_dep_version)?;
// Copy per-block op_return values from scripts
let scripts_target = scripts.value.op_return.base.sats.height.len();
if scripts_target > 0 {
let target_height = Height::from(scripts_target - 1);
let current_len = height_vec.len();
let starting_height =
Height::from(current_len.min(starting_indexes.height.to_usize()));
if starting_height <= target_height {
let start = starting_height.to_usize();
let end = target_height.to_usize() + 1;
scripts.value.op_return.base.sats.height.fold_range_at(
start,
end,
start,
|idx, value| {
height_vec.truncate_push(Height::from(idx), value).unwrap();
idx + 1
},
);
}
}
height_vec.write()?;
Ok(())
},
)?;
// 2. Compute unspendable supply = op_return + unclaimed_rewards + genesis (at height 0)
// Get reference to op_return height vec for computing unspendable
let op_return_height = &self.op_return.base.sats.height;
let op_return_height = &scripts.value.op_return.base.sats.height;
let unclaimed_height = &mining.rewards.unclaimed.base.sats.height;
self.unspendable.compute(

View File

@@ -13,13 +13,6 @@ impl Vecs {
cached_starts: &CachedWindowStarts,
) -> Result<Self> {
Ok(Self {
op_return: AmountPerBlockCumulativeWithSums::forced_import(
db,
"op_return_supply",
version,
indexes,
cached_starts,
)?,
unspendable: AmountPerBlockCumulativeWithSums::forced_import(
db,
"unspendable_supply",

View File

@@ -5,6 +5,5 @@ use crate::internal::AmountPerBlockCumulativeWithSums;
#[derive(Traversable)]
pub struct Vecs<M: StorageMode = Rw> {
pub op_return: AmountPerBlockCumulativeWithSums<M>,
pub unspendable: AmountPerBlockCumulativeWithSums<M>,
}

View File

@@ -20,17 +20,19 @@ impl Vecs {
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
// Count computes first
self.count
.compute(indexer, &blocks.lookback, starting_indexes, exit)?;
// Versions depends on count
self.versions
.compute(indexer, starting_indexes, exit)?;
// Size computes next (uses 6-block rolling window)
self.size
.compute(indexer, indexes, starting_indexes, exit)?;
// count, versions, size are independent — parallelize
let (r1, (r2, r3)) = rayon::join(
|| self.count.compute(indexer, &blocks.lookback, starting_indexes, exit),
|| {
rayon::join(
|| self.versions.compute(indexer, starting_indexes, exit),
|| self.size.compute(indexer, indexes, starting_indexes, exit),
)
},
);
r1?;
r2?;
r3?;
// Fees depends on size
self.fees

View File

@@ -18,21 +18,29 @@ impl Vecs {
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
self.input_value.compute_sum_from_indexes(
starting_indexes.tx_index,
&indexer.vecs.transactions.first_txin_index,
&indexes.tx_index.input_count,
&txins.spent.value,
exit,
)?;
self.output_value.compute_sum_from_indexes(
starting_indexes.tx_index,
&indexer.vecs.transactions.first_txout_index,
&indexes.tx_index.output_count,
&indexer.vecs.outputs.value,
exit,
)?;
// input_value and output_value are independent — parallelize
let (r1, r2) = rayon::join(
|| {
self.input_value.compute_sum_from_indexes(
starting_indexes.tx_index,
&indexer.vecs.transactions.first_txin_index,
&indexes.tx_index.input_count,
&txins.spent.value,
exit,
)
},
|| {
self.output_value.compute_sum_from_indexes(
starting_indexes.tx_index,
&indexer.vecs.transactions.first_txout_index,
&indexes.tx_index.output_count,
&indexer.vecs.outputs.value,
exit,
)
},
);
r1?;
r2?;
self.fee.tx_index.compute_transform2(
starting_indexes.tx_index,

View File

@@ -1,10 +1,9 @@
use brk_error::Result;
use brk_indexer::Indexer;
use brk_types::{Indexes, StoredU64, TxVersion};
use vecdb::{Exit, ReadableVec, VecIndex};
use vecdb::{AnyStoredVec, AnyVec, Exit, ReadableVec, VecIndex, WritableVec};
use super::Vecs;
use crate::internal::PerBlockCumulativeWithSums;
impl Vecs {
pub(crate) fn compute(
@@ -13,30 +12,86 @@ impl Vecs {
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
let tx_vany = |tx_vany: &mut PerBlockCumulativeWithSums<StoredU64, StoredU64>,
tx_version: TxVersion| {
let tx_version_vec = &indexer.vecs.transactions.tx_version;
// Cursor avoids per-transaction PcoVec page decompression.
// Txindex values are sequential, so the cursor only advances forward.
let mut cursor = tx_version_vec.cursor();
tx_vany.compute(starting_indexes.height, exit, |vec| {
vec.compute_filtered_count_from_indexes(
starting_indexes.height,
&indexer.vecs.transactions.first_tx_index,
&indexer.vecs.transactions.txid,
|tx_index| {
let ti = tx_index.to_usize();
cursor.advance(ti - cursor.position());
cursor.next().unwrap() == tx_version
},
exit,
)?;
Ok(())
})
};
tx_vany(&mut self.v1, TxVersion::ONE)?;
tx_vany(&mut self.v2, TxVersion::TWO)?;
tx_vany(&mut self.v3, TxVersion::THREE)?;
let dep_version = indexer.vecs.transactions.tx_version.version()
+ indexer.vecs.transactions.first_tx_index.version()
+ indexer.vecs.transactions.txid.version();
for vec in [
&mut self.v1.base.height,
&mut self.v2.base.height,
&mut self.v3.base.height,
] {
vec.validate_and_truncate(dep_version, starting_indexes.height)?;
}
let skip = self
.v1
.base
.height
.len()
.min(self.v2.base.height.len())
.min(self.v3.base.height.len());
let first_tx_index = &indexer.vecs.transactions.first_tx_index;
let end = first_tx_index.len();
if skip >= end {
return Ok(());
}
// Truncate all 3 to skip, then push (no per-element bounds checks).
self.v1.base.height.truncate_if_needed_at(skip)?;
self.v2.base.height.truncate_if_needed_at(skip)?;
self.v3.base.height.truncate_if_needed_at(skip)?;
// Single cursor over tx_version — scanned once for all 3 version counts.
let mut cursor = indexer.vecs.transactions.tx_version.cursor();
let fi_batch = first_tx_index.collect_range_at(skip, end);
let txid_len = indexer.vecs.transactions.txid.len();
for (j, first_index) in fi_batch.iter().enumerate() {
let next_first = fi_batch
.get(j + 1)
.map(|fi| fi.to_usize())
.unwrap_or(txid_len);
let mut c1: usize = 0;
let mut c2: usize = 0;
let mut c3: usize = 0;
let fi = first_index.to_usize();
cursor.advance(fi - cursor.position());
for _ in fi..next_first {
match cursor.next().unwrap() {
TxVersion::ONE => c1 += 1,
TxVersion::TWO => c2 += 1,
TxVersion::THREE => c3 += 1,
_ => {}
}
}
self.v1.base.height.push(StoredU64::from(c1 as u64));
self.v2.base.height.push(StoredU64::from(c2 as u64));
self.v3.base.height.push(StoredU64::from(c3 as u64));
if self.v1.base.height.batch_limit_reached() {
let _lock = exit.lock();
self.v1.base.height.write()?;
self.v2.base.height.write()?;
self.v3.base.height.write()?;
}
}
{
let _lock = exit.lock();
self.v1.base.height.write()?;
self.v2.base.height.write()?;
self.v3.base.height.write()?;
}
// Derive cumulative + sums from base
self.v1.compute_rest(starting_indexes.height, exit)?;
self.v2.compute_rest(starting_indexes.height, exit)?;
self.v3.compute_rest(starting_indexes.height, exit)?;
Ok(())
}

View File

@@ -22,36 +22,44 @@ impl Vecs {
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
self.sent_sum.compute(
starting_indexes.height,
prices,
exit,
|sats_vec| {
Ok(sats_vec.compute_filtered_sum_from_indexes(
// sent_sum and received_sum are independent — parallelize
let (r1, r2) = rayon::join(
|| {
self.sent_sum.compute(
starting_indexes.height,
&indexer.vecs.transactions.first_tx_index,
&indexes.height.tx_index_count,
&fees_vecs.input_value,
|sats| !sats.is_max(),
prices,
exit,
)?)
|sats_vec| {
Ok(sats_vec.compute_filtered_sum_from_indexes(
starting_indexes.height,
&indexer.vecs.transactions.first_tx_index,
&indexes.height.tx_index_count,
&fees_vecs.input_value,
|sats| !sats.is_max(),
exit,
)?)
},
)
},
)?;
self.received_sum.compute(
starting_indexes.height,
prices,
exit,
|sats_vec| {
Ok(sats_vec.compute_sum_from_indexes(
|| {
self.received_sum.compute(
starting_indexes.height,
&indexer.vecs.transactions.first_tx_index,
&indexes.height.tx_index_count,
&fees_vecs.output_value,
prices,
exit,
)?)
|sats_vec| {
Ok(sats_vec.compute_sum_from_indexes(
starting_indexes.height,
&indexer.vecs.transactions.first_tx_index,
&indexes.height.tx_index_count,
&fees_vecs.output_value,
exit,
)?)
},
)
},
)?;
);
r1?;
r2?;
self.tx_per_sec
.height

View File

@@ -1,7 +1,8 @@
use std::marker::PhantomData;
/// Direct-mapped cache size. Power of 2 for fast masking.
const CACHE_SIZE: usize = 128;
/// 1024 entries × ~32 bytes = 32 KB (fits in L1 cache).
const CACHE_SIZE: usize = 1024;
const CACHE_MASK: usize = CACHE_SIZE - 1;
/// Cache entry: (range_low, range_high, value, occupied).

View File

@@ -2633,6 +2633,31 @@ function createBpsCentsRatioSatsUsdPattern(client, acc) {
};
}
/**
* @typedef {Object} BtcCentsDeltaSatsUsdPattern
* @property {MetricPattern1<Bitcoin>} btc
* @property {MetricPattern1<Cents>} cents
* @property {AbsoluteRatePattern} delta
* @property {MetricPattern1<Sats>} sats
* @property {MetricPattern1<Dollars>} usd
*/
/**
* Create a BtcCentsDeltaSatsUsdPattern pattern node
* @param {BrkClientBase} client
* @param {string} acc - Accumulated metric name
* @returns {BtcCentsDeltaSatsUsdPattern}
*/
function createBtcCentsDeltaSatsUsdPattern(client, acc) {
return {
btc: createMetricPattern1(client, acc),
cents: createMetricPattern1(client, _m(acc, 'cents')),
delta: createAbsoluteRatePattern(client, _m(acc, 'delta')),
sats: createMetricPattern1(client, _m(acc, 'sats')),
usd: createMetricPattern1(client, _m(acc, 'usd')),
};
}
/**
* @typedef {Object} BtcCentsRelSatsUsdPattern
* @property {MetricPattern1<Bitcoin>} btc
@@ -2758,6 +2783,31 @@ function createInvestedMaxMinPercentilesSupplyPattern(client, acc) {
};
}
/**
* @typedef {Object} MvrvNuplRealizedSupplyPattern
* @property {MetricPattern1<StoredF32>} mvrv
* @property {BpsRatioPattern} nupl
* @property {AllSthPattern} realizedCap
* @property {BpsCentsRatioSatsUsdPattern} realizedPrice
* @property {AllSthPattern2} supply
*/
/**
* Create a MvrvNuplRealizedSupplyPattern pattern node
* @param {BrkClientBase} client
* @param {string} acc - Accumulated metric name
* @returns {MvrvNuplRealizedSupplyPattern}
*/
function createMvrvNuplRealizedSupplyPattern(client, acc) {
return {
mvrv: createMetricPattern1(client, _m(acc, 'mvrv')),
nupl: createBpsRatioPattern(client, _m(acc, 'nupl')),
realizedCap: createAllSthPattern(client, acc),
realizedPrice: createBpsCentsRatioSatsUsdPattern(client, _m(acc, 'realized_price')),
supply: createAllSthPattern2(client, acc),
};
}
/**
* @typedef {Object} PhsReboundThsPattern
* @property {MetricPattern1<StoredF32>} phs
@@ -3566,7 +3616,7 @@ function createGreedNetPainPattern(client, acc) {
/**
* @typedef {Object} LossNuplProfitPattern
* @property {BaseCumulativeSumPattern3} loss
* @property {BaseCumulativeNegativeSumPattern} loss
* @property {BpsRatioPattern} nupl
* @property {BaseCumulativeSumPattern3} profit
*/
@@ -3579,7 +3629,7 @@ function createGreedNetPainPattern(client, acc) {
*/
function createLossNuplProfitPattern(client, acc) {
return {
loss: createBaseCumulativeSumPattern3(client, _m(acc, 'unrealized_loss')),
loss: createBaseCumulativeNegativeSumPattern(client, acc),
nupl: createBpsRatioPattern(client, _m(acc, 'nupl')),
profit: createBaseCumulativeSumPattern3(client, _m(acc, 'unrealized_profit')),
};
@@ -3732,6 +3782,44 @@ function createAbsoluteRatePattern2(client, acc) {
};
}
/**
* @typedef {Object} AllSthPattern2
* @property {BtcCentsDeltaSatsUsdPattern} all
* @property {BtcCentsSatsUsdPattern} sth
*/
/**
* Create a AllSthPattern2 pattern node
* @param {BrkClientBase} client
* @param {string} acc - Accumulated metric name
* @returns {AllSthPattern2}
*/
function createAllSthPattern2(client, acc) {
return {
all: createBtcCentsDeltaSatsUsdPattern(client, _m(acc, 'supply')),
sth: createBtcCentsSatsUsdPattern(client, _m(acc, 'sth_supply')),
};
}
/**
* @typedef {Object} AllSthPattern
* @property {MetricPattern1<Dollars>} all
* @property {MetricPattern1<Dollars>} sth
*/
/**
* Create a AllSthPattern pattern node
* @param {BrkClientBase} client
* @param {string} acc - Accumulated metric name
* @returns {AllSthPattern}
*/
function createAllSthPattern(client, acc) {
return {
all: createMetricPattern1(client, _m(acc, 'realized_cap')),
sth: createMetricPattern1(client, _m(acc, 'sth_realized_cap')),
};
}
/**
* @typedef {Object} BlocksDominancePattern
* @property {BaseCumulativeSumPattern2} blocksMined
@@ -3903,25 +3991,6 @@ function createPriceValuePattern(client, acc) {
};
}
/**
* @typedef {Object} RealizedSupplyPattern
* @property {MetricPattern1<Dollars>} realizedCap
* @property {MetricPattern1<Sats>} supply
*/
/**
* Create a RealizedSupplyPattern pattern node
* @param {BrkClientBase} client
* @param {string} acc - Accumulated metric name
* @returns {RealizedSupplyPattern}
*/
function createRealizedSupplyPattern(client, acc) {
return {
realizedCap: createMetricPattern1(client, _m(acc, 'realized_cap')),
supply: createMetricPattern1(client, _m(acc, 'supply')),
};
}
/**
* @typedef {Object} RelPattern
* @property {BpsPercentRatioPattern} relToMcap
@@ -4481,13 +4550,7 @@ function createUnspentPattern(client, acc) {
/**
* @typedef {Object} MetricsTree_Scripts_Value
* @property {MetricsTree_Scripts_Value_OpReturn} opReturn
*/
/**
* @typedef {Object} MetricsTree_Scripts_Value_OpReturn
* @property {BtcCentsSatsUsdPattern} base
* @property {BtcCentsSatsUsdPattern} cumulative
* @property {BaseCumulativeSumPattern4} opReturn
*/
/**
@@ -5509,7 +5572,6 @@ function createUnspentPattern(client, acc) {
/**
* @typedef {Object} MetricsTree_Supply_Burned
* @property {BaseCumulativeSumPattern4} opReturn
* @property {BaseCumulativeSumPattern4} unspendable
*/
@@ -5819,87 +5881,87 @@ function createUnspentPattern(client, acc) {
/**
* @typedef {Object} MetricsTree_Cohorts_Utxo_Profitability_Range
* @property {RealizedSupplyPattern} over1000pctInProfit
* @property {RealizedSupplyPattern} _500pctTo1000pctInProfit
* @property {RealizedSupplyPattern} _300pctTo500pctInProfit
* @property {RealizedSupplyPattern} _200pctTo300pctInProfit
* @property {RealizedSupplyPattern} _100pctTo200pctInProfit
* @property {RealizedSupplyPattern} _90pctTo100pctInProfit
* @property {RealizedSupplyPattern} _80pctTo90pctInProfit
* @property {RealizedSupplyPattern} _70pctTo80pctInProfit
* @property {RealizedSupplyPattern} _60pctTo70pctInProfit
* @property {RealizedSupplyPattern} _50pctTo60pctInProfit
* @property {RealizedSupplyPattern} _40pctTo50pctInProfit
* @property {RealizedSupplyPattern} _30pctTo40pctInProfit
* @property {RealizedSupplyPattern} _20pctTo30pctInProfit
* @property {RealizedSupplyPattern} _10pctTo20pctInProfit
* @property {RealizedSupplyPattern} _0pctTo10pctInProfit
* @property {RealizedSupplyPattern} _0pctTo10pctInLoss
* @property {RealizedSupplyPattern} _10pctTo20pctInLoss
* @property {RealizedSupplyPattern} _20pctTo30pctInLoss
* @property {RealizedSupplyPattern} _30pctTo40pctInLoss
* @property {RealizedSupplyPattern} _40pctTo50pctInLoss
* @property {RealizedSupplyPattern} _50pctTo60pctInLoss
* @property {RealizedSupplyPattern} _60pctTo70pctInLoss
* @property {RealizedSupplyPattern} _70pctTo80pctInLoss
* @property {RealizedSupplyPattern} _80pctTo90pctInLoss
* @property {RealizedSupplyPattern} _90pctTo100pctInLoss
* @property {MvrvNuplRealizedSupplyPattern} over1000pctInProfit
* @property {MvrvNuplRealizedSupplyPattern} _500pctTo1000pctInProfit
* @property {MvrvNuplRealizedSupplyPattern} _300pctTo500pctInProfit
* @property {MvrvNuplRealizedSupplyPattern} _200pctTo300pctInProfit
* @property {MvrvNuplRealizedSupplyPattern} _100pctTo200pctInProfit
* @property {MvrvNuplRealizedSupplyPattern} _90pctTo100pctInProfit
* @property {MvrvNuplRealizedSupplyPattern} _80pctTo90pctInProfit
* @property {MvrvNuplRealizedSupplyPattern} _70pctTo80pctInProfit
* @property {MvrvNuplRealizedSupplyPattern} _60pctTo70pctInProfit
* @property {MvrvNuplRealizedSupplyPattern} _50pctTo60pctInProfit
* @property {MvrvNuplRealizedSupplyPattern} _40pctTo50pctInProfit
* @property {MvrvNuplRealizedSupplyPattern} _30pctTo40pctInProfit
* @property {MvrvNuplRealizedSupplyPattern} _20pctTo30pctInProfit
* @property {MvrvNuplRealizedSupplyPattern} _10pctTo20pctInProfit
* @property {MvrvNuplRealizedSupplyPattern} _0pctTo10pctInProfit
* @property {MvrvNuplRealizedSupplyPattern} _0pctTo10pctInLoss
* @property {MvrvNuplRealizedSupplyPattern} _10pctTo20pctInLoss
* @property {MvrvNuplRealizedSupplyPattern} _20pctTo30pctInLoss
* @property {MvrvNuplRealizedSupplyPattern} _30pctTo40pctInLoss
* @property {MvrvNuplRealizedSupplyPattern} _40pctTo50pctInLoss
* @property {MvrvNuplRealizedSupplyPattern} _50pctTo60pctInLoss
* @property {MvrvNuplRealizedSupplyPattern} _60pctTo70pctInLoss
* @property {MvrvNuplRealizedSupplyPattern} _70pctTo80pctInLoss
* @property {MvrvNuplRealizedSupplyPattern} _80pctTo90pctInLoss
* @property {MvrvNuplRealizedSupplyPattern} _90pctTo100pctInLoss
*/
/**
* @typedef {Object} MetricsTree_Cohorts_Utxo_Profitability_Profit
* @property {RealizedSupplyPattern} breakeven
* @property {RealizedSupplyPattern} _10pct
* @property {RealizedSupplyPattern} _20pct
* @property {RealizedSupplyPattern} _30pct
* @property {RealizedSupplyPattern} _40pct
* @property {RealizedSupplyPattern} _50pct
* @property {RealizedSupplyPattern} _60pct
* @property {RealizedSupplyPattern} _70pct
* @property {RealizedSupplyPattern} _80pct
* @property {RealizedSupplyPattern} _90pct
* @property {RealizedSupplyPattern} _100pct
* @property {RealizedSupplyPattern} _200pct
* @property {RealizedSupplyPattern} _300pct
* @property {RealizedSupplyPattern} _500pct
* @property {MvrvNuplRealizedSupplyPattern} breakeven
* @property {MvrvNuplRealizedSupplyPattern} _10pct
* @property {MvrvNuplRealizedSupplyPattern} _20pct
* @property {MvrvNuplRealizedSupplyPattern} _30pct
* @property {MvrvNuplRealizedSupplyPattern} _40pct
* @property {MvrvNuplRealizedSupplyPattern} _50pct
* @property {MvrvNuplRealizedSupplyPattern} _60pct
* @property {MvrvNuplRealizedSupplyPattern} _70pct
* @property {MvrvNuplRealizedSupplyPattern} _80pct
* @property {MvrvNuplRealizedSupplyPattern} _90pct
* @property {MvrvNuplRealizedSupplyPattern} _100pct
* @property {MvrvNuplRealizedSupplyPattern} _200pct
* @property {MvrvNuplRealizedSupplyPattern} _300pct
* @property {MvrvNuplRealizedSupplyPattern} _500pct
*/
/**
* @typedef {Object} MetricsTree_Cohorts_Utxo_Profitability_Loss
* @property {RealizedSupplyPattern} breakeven
* @property {RealizedSupplyPattern} _10pct
* @property {RealizedSupplyPattern} _20pct
* @property {RealizedSupplyPattern} _30pct
* @property {RealizedSupplyPattern} _40pct
* @property {RealizedSupplyPattern} _50pct
* @property {RealizedSupplyPattern} _60pct
* @property {RealizedSupplyPattern} _70pct
* @property {RealizedSupplyPattern} _80pct
* @property {MvrvNuplRealizedSupplyPattern} breakeven
* @property {MvrvNuplRealizedSupplyPattern} _10pct
* @property {MvrvNuplRealizedSupplyPattern} _20pct
* @property {MvrvNuplRealizedSupplyPattern} _30pct
* @property {MvrvNuplRealizedSupplyPattern} _40pct
* @property {MvrvNuplRealizedSupplyPattern} _50pct
* @property {MvrvNuplRealizedSupplyPattern} _60pct
* @property {MvrvNuplRealizedSupplyPattern} _70pct
* @property {MvrvNuplRealizedSupplyPattern} _80pct
*/
/**
* @typedef {Object} MetricsTree_Cohorts_Utxo_Matured
* @property {BtcCentsSatsUsdPattern} under1h
* @property {BtcCentsSatsUsdPattern} _1hTo1d
* @property {BtcCentsSatsUsdPattern} _1dTo1w
* @property {BtcCentsSatsUsdPattern} _1wTo1m
* @property {BtcCentsSatsUsdPattern} _1mTo2m
* @property {BtcCentsSatsUsdPattern} _2mTo3m
* @property {BtcCentsSatsUsdPattern} _3mTo4m
* @property {BtcCentsSatsUsdPattern} _4mTo5m
* @property {BtcCentsSatsUsdPattern} _5mTo6m
* @property {BtcCentsSatsUsdPattern} _6mTo1y
* @property {BtcCentsSatsUsdPattern} _1yTo2y
* @property {BtcCentsSatsUsdPattern} _2yTo3y
* @property {BtcCentsSatsUsdPattern} _3yTo4y
* @property {BtcCentsSatsUsdPattern} _4yTo5y
* @property {BtcCentsSatsUsdPattern} _5yTo6y
* @property {BtcCentsSatsUsdPattern} _6yTo7y
* @property {BtcCentsSatsUsdPattern} _7yTo8y
* @property {BtcCentsSatsUsdPattern} _8yTo10y
* @property {BtcCentsSatsUsdPattern} _10yTo12y
* @property {BtcCentsSatsUsdPattern} _12yTo15y
* @property {BtcCentsSatsUsdPattern} over15y
* @property {BaseCumulativeSumPattern4} under1h
* @property {BaseCumulativeSumPattern4} _1hTo1d
* @property {BaseCumulativeSumPattern4} _1dTo1w
* @property {BaseCumulativeSumPattern4} _1wTo1m
* @property {BaseCumulativeSumPattern4} _1mTo2m
* @property {BaseCumulativeSumPattern4} _2mTo3m
* @property {BaseCumulativeSumPattern4} _3mTo4m
* @property {BaseCumulativeSumPattern4} _4mTo5m
* @property {BaseCumulativeSumPattern4} _5mTo6m
* @property {BaseCumulativeSumPattern4} _6mTo1y
* @property {BaseCumulativeSumPattern4} _1yTo2y
* @property {BaseCumulativeSumPattern4} _2yTo3y
* @property {BaseCumulativeSumPattern4} _3yTo4y
* @property {BaseCumulativeSumPattern4} _4yTo5y
* @property {BaseCumulativeSumPattern4} _5yTo6y
* @property {BaseCumulativeSumPattern4} _6yTo7y
* @property {BaseCumulativeSumPattern4} _7yTo8y
* @property {BaseCumulativeSumPattern4} _8yTo10y
* @property {BaseCumulativeSumPattern4} _10yTo12y
* @property {BaseCumulativeSumPattern4} _12yTo15y
* @property {BaseCumulativeSumPattern4} over15y
*/
/**
@@ -6870,6 +6932,255 @@ class BrkClient extends BrkClientBase {
}
});
PROFITABILITY_RANGE_NAMES = /** @type {const} */ ({
"over1000pctInProfit": {
"id": "utxos_over_1000pct_in_profit",
"short": ">1000%",
"long": "Over 1000% Profit"
},
"_500pctTo1000pctInProfit": {
"id": "utxos_500pct_to_1000pct_in_profit",
"short": "500-1000%",
"long": "500-1000% Profit"
},
"_300pctTo500pctInProfit": {
"id": "utxos_300pct_to_500pct_in_profit",
"short": "300-500%",
"long": "300-500% Profit"
},
"_200pctTo300pctInProfit": {
"id": "utxos_200pct_to_300pct_in_profit",
"short": "200-300%",
"long": "200-300% Profit"
},
"_100pctTo200pctInProfit": {
"id": "utxos_100pct_to_200pct_in_profit",
"short": "100-200%",
"long": "100-200% Profit"
},
"_90pctTo100pctInProfit": {
"id": "utxos_90pct_to_100pct_in_profit",
"short": "90-100%",
"long": "90-100% Profit"
},
"_80pctTo90pctInProfit": {
"id": "utxos_80pct_to_90pct_in_profit",
"short": "80-90%",
"long": "80-90% Profit"
},
"_70pctTo80pctInProfit": {
"id": "utxos_70pct_to_80pct_in_profit",
"short": "70-80%",
"long": "70-80% Profit"
},
"_60pctTo70pctInProfit": {
"id": "utxos_60pct_to_70pct_in_profit",
"short": "60-70%",
"long": "60-70% Profit"
},
"_50pctTo60pctInProfit": {
"id": "utxos_50pct_to_60pct_in_profit",
"short": "50-60%",
"long": "50-60% Profit"
},
"_40pctTo50pctInProfit": {
"id": "utxos_40pct_to_50pct_in_profit",
"short": "40-50%",
"long": "40-50% Profit"
},
"_30pctTo40pctInProfit": {
"id": "utxos_30pct_to_40pct_in_profit",
"short": "30-40%",
"long": "30-40% Profit"
},
"_20pctTo30pctInProfit": {
"id": "utxos_20pct_to_30pct_in_profit",
"short": "20-30%",
"long": "20-30% Profit"
},
"_10pctTo20pctInProfit": {
"id": "utxos_10pct_to_20pct_in_profit",
"short": "10-20%",
"long": "10-20% Profit"
},
"_0pctTo10pctInProfit": {
"id": "utxos_0pct_to_10pct_in_profit",
"short": "0-10%",
"long": "0-10% Profit"
},
"_0pctTo10pctInLoss": {
"id": "utxos_0pct_to_10pct_in_loss",
"short": "0-10%L",
"long": "0-10% Loss"
},
"_10pctTo20pctInLoss": {
"id": "utxos_10pct_to_20pct_in_loss",
"short": "10-20%L",
"long": "10-20% Loss"
},
"_20pctTo30pctInLoss": {
"id": "utxos_20pct_to_30pct_in_loss",
"short": "20-30%L",
"long": "20-30% Loss"
},
"_30pctTo40pctInLoss": {
"id": "utxos_30pct_to_40pct_in_loss",
"short": "30-40%L",
"long": "30-40% Loss"
},
"_40pctTo50pctInLoss": {
"id": "utxos_40pct_to_50pct_in_loss",
"short": "40-50%L",
"long": "40-50% Loss"
},
"_50pctTo60pctInLoss": {
"id": "utxos_50pct_to_60pct_in_loss",
"short": "50-60%L",
"long": "50-60% Loss"
},
"_60pctTo70pctInLoss": {
"id": "utxos_60pct_to_70pct_in_loss",
"short": "60-70%L",
"long": "60-70% Loss"
},
"_70pctTo80pctInLoss": {
"id": "utxos_70pct_to_80pct_in_loss",
"short": "70-80%L",
"long": "70-80% Loss"
},
"_80pctTo90pctInLoss": {
"id": "utxos_80pct_to_90pct_in_loss",
"short": "80-90%L",
"long": "80-90% Loss"
},
"_90pctTo100pctInLoss": {
"id": "utxos_90pct_to_100pct_in_loss",
"short": "90-100%L",
"long": "90-100% Loss"
}
});
PROFIT_NAMES = /** @type {const} */ ({
"breakeven": {
"id": "utxos_in_profit",
"short": "≥0%",
"long": "In Profit (Breakeven+)"
},
"_10pct": {
"id": "utxos_over_10pct_in_profit",
"short": "≥10%",
"long": "10%+ Profit"
},
"_20pct": {
"id": "utxos_over_20pct_in_profit",
"short": "≥20%",
"long": "20%+ Profit"
},
"_30pct": {
"id": "utxos_over_30pct_in_profit",
"short": "≥30%",
"long": "30%+ Profit"
},
"_40pct": {
"id": "utxos_over_40pct_in_profit",
"short": "≥40%",
"long": "40%+ Profit"
},
"_50pct": {
"id": "utxos_over_50pct_in_profit",
"short": "≥50%",
"long": "50%+ Profit"
},
"_60pct": {
"id": "utxos_over_60pct_in_profit",
"short": "≥60%",
"long": "60%+ Profit"
},
"_70pct": {
"id": "utxos_over_70pct_in_profit",
"short": "≥70%",
"long": "70%+ Profit"
},
"_80pct": {
"id": "utxos_over_80pct_in_profit",
"short": "≥80%",
"long": "80%+ Profit"
},
"_90pct": {
"id": "utxos_over_90pct_in_profit",
"short": "≥90%",
"long": "90%+ Profit"
},
"_100pct": {
"id": "utxos_over_100pct_in_profit",
"short": "≥100%",
"long": "100%+ Profit"
},
"_200pct": {
"id": "utxos_over_200pct_in_profit",
"short": "≥200%",
"long": "200%+ Profit"
},
"_300pct": {
"id": "utxos_over_300pct_in_profit",
"short": "≥300%",
"long": "300%+ Profit"
},
"_500pct": {
"id": "utxos_over_500pct_in_profit",
"short": "≥500%",
"long": "500%+ Profit"
}
});
LOSS_NAMES = /** @type {const} */ ({
"breakeven": {
"id": "utxos_in_loss",
"short": "<0%",
"long": "In Loss (Below Breakeven)"
},
"_10pct": {
"id": "utxos_over_10pct_in_loss",
"short": "≥10%L",
"long": "10%+ Loss"
},
"_20pct": {
"id": "utxos_over_20pct_in_loss",
"short": "≥20%L",
"long": "20%+ Loss"
},
"_30pct": {
"id": "utxos_over_30pct_in_loss",
"short": "≥30%L",
"long": "30%+ Loss"
},
"_40pct": {
"id": "utxos_over_40pct_in_loss",
"short": "≥40%L",
"long": "40%+ Loss"
},
"_50pct": {
"id": "utxos_over_50pct_in_loss",
"short": "≥50%L",
"long": "50%+ Loss"
},
"_60pct": {
"id": "utxos_over_60pct_in_loss",
"short": "≥60%L",
"long": "60%+ Loss"
},
"_70pct": {
"id": "utxos_over_70pct_in_loss",
"short": "≥70%L",
"long": "70%+ Loss"
},
"_80pct": {
"id": "utxos_over_80pct_in_loss",
"short": "≥80%L",
"long": "80%+ Loss"
}
});
/**
* Convert an index value to a Date for date-based indexes.
* @param {Index} index - The index type
@@ -7203,10 +7514,7 @@ class BrkClient extends BrkClientBase {
segwit: createBaseCumulativeSumPattern(this, 'segwit_count'),
},
value: {
opReturn: {
base: createBtcCentsSatsUsdPattern(this, 'op_return_value'),
cumulative: createBtcCentsSatsUsdPattern(this, 'op_return_value_cumulative'),
},
opReturn: createBaseCumulativeSumPattern4(this, 'op_return_value'),
},
adoption: {
taproot: createBpsPercentRatioPattern3(this, 'taproot_adoption'),
@@ -7945,7 +8253,6 @@ class BrkClient extends BrkClientBase {
state: createMetricPattern18(this, 'supply_state'),
circulating: createBtcCentsSatsUsdPattern(this, 'circulating_supply'),
burned: {
opReturn: createBaseCumulativeSumPattern4(this, 'op_return_supply'),
unspendable: createBaseCumulativeSumPattern4(this, 'unspendable_supply'),
},
inflationRate: createBpsPercentRatioPattern(this, 'inflation_rate'),
@@ -8183,82 +8490,82 @@ class BrkClient extends BrkClientBase {
},
profitability: {
range: {
over1000pctInProfit: createRealizedSupplyPattern(this, 'utxos_over_1000pct_in_profit'),
_500pctTo1000pctInProfit: createRealizedSupplyPattern(this, 'utxos_500pct_to_1000pct_in_profit'),
_300pctTo500pctInProfit: createRealizedSupplyPattern(this, 'utxos_300pct_to_500pct_in_profit'),
_200pctTo300pctInProfit: createRealizedSupplyPattern(this, 'utxos_200pct_to_300pct_in_profit'),
_100pctTo200pctInProfit: createRealizedSupplyPattern(this, 'utxos_100pct_to_200pct_in_profit'),
_90pctTo100pctInProfit: createRealizedSupplyPattern(this, 'utxos_90pct_to_100pct_in_profit'),
_80pctTo90pctInProfit: createRealizedSupplyPattern(this, 'utxos_80pct_to_90pct_in_profit'),
_70pctTo80pctInProfit: createRealizedSupplyPattern(this, 'utxos_70pct_to_80pct_in_profit'),
_60pctTo70pctInProfit: createRealizedSupplyPattern(this, 'utxos_60pct_to_70pct_in_profit'),
_50pctTo60pctInProfit: createRealizedSupplyPattern(this, 'utxos_50pct_to_60pct_in_profit'),
_40pctTo50pctInProfit: createRealizedSupplyPattern(this, 'utxos_40pct_to_50pct_in_profit'),
_30pctTo40pctInProfit: createRealizedSupplyPattern(this, 'utxos_30pct_to_40pct_in_profit'),
_20pctTo30pctInProfit: createRealizedSupplyPattern(this, 'utxos_20pct_to_30pct_in_profit'),
_10pctTo20pctInProfit: createRealizedSupplyPattern(this, 'utxos_10pct_to_20pct_in_profit'),
_0pctTo10pctInProfit: createRealizedSupplyPattern(this, 'utxos_0pct_to_10pct_in_profit'),
_0pctTo10pctInLoss: createRealizedSupplyPattern(this, 'utxos_0pct_to_10pct_in_loss'),
_10pctTo20pctInLoss: createRealizedSupplyPattern(this, 'utxos_10pct_to_20pct_in_loss'),
_20pctTo30pctInLoss: createRealizedSupplyPattern(this, 'utxos_20pct_to_30pct_in_loss'),
_30pctTo40pctInLoss: createRealizedSupplyPattern(this, 'utxos_30pct_to_40pct_in_loss'),
_40pctTo50pctInLoss: createRealizedSupplyPattern(this, 'utxos_40pct_to_50pct_in_loss'),
_50pctTo60pctInLoss: createRealizedSupplyPattern(this, 'utxos_50pct_to_60pct_in_loss'),
_60pctTo70pctInLoss: createRealizedSupplyPattern(this, 'utxos_60pct_to_70pct_in_loss'),
_70pctTo80pctInLoss: createRealizedSupplyPattern(this, 'utxos_70pct_to_80pct_in_loss'),
_80pctTo90pctInLoss: createRealizedSupplyPattern(this, 'utxos_80pct_to_90pct_in_loss'),
_90pctTo100pctInLoss: createRealizedSupplyPattern(this, 'utxos_90pct_to_100pct_in_loss'),
over1000pctInProfit: createMvrvNuplRealizedSupplyPattern(this, 'utxos_over_1000pct_in_profit'),
_500pctTo1000pctInProfit: createMvrvNuplRealizedSupplyPattern(this, 'utxos_500pct_to_1000pct_in_profit'),
_300pctTo500pctInProfit: createMvrvNuplRealizedSupplyPattern(this, 'utxos_300pct_to_500pct_in_profit'),
_200pctTo300pctInProfit: createMvrvNuplRealizedSupplyPattern(this, 'utxos_200pct_to_300pct_in_profit'),
_100pctTo200pctInProfit: createMvrvNuplRealizedSupplyPattern(this, 'utxos_100pct_to_200pct_in_profit'),
_90pctTo100pctInProfit: createMvrvNuplRealizedSupplyPattern(this, 'utxos_90pct_to_100pct_in_profit'),
_80pctTo90pctInProfit: createMvrvNuplRealizedSupplyPattern(this, 'utxos_80pct_to_90pct_in_profit'),
_70pctTo80pctInProfit: createMvrvNuplRealizedSupplyPattern(this, 'utxos_70pct_to_80pct_in_profit'),
_60pctTo70pctInProfit: createMvrvNuplRealizedSupplyPattern(this, 'utxos_60pct_to_70pct_in_profit'),
_50pctTo60pctInProfit: createMvrvNuplRealizedSupplyPattern(this, 'utxos_50pct_to_60pct_in_profit'),
_40pctTo50pctInProfit: createMvrvNuplRealizedSupplyPattern(this, 'utxos_40pct_to_50pct_in_profit'),
_30pctTo40pctInProfit: createMvrvNuplRealizedSupplyPattern(this, 'utxos_30pct_to_40pct_in_profit'),
_20pctTo30pctInProfit: createMvrvNuplRealizedSupplyPattern(this, 'utxos_20pct_to_30pct_in_profit'),
_10pctTo20pctInProfit: createMvrvNuplRealizedSupplyPattern(this, 'utxos_10pct_to_20pct_in_profit'),
_0pctTo10pctInProfit: createMvrvNuplRealizedSupplyPattern(this, 'utxos_0pct_to_10pct_in_profit'),
_0pctTo10pctInLoss: createMvrvNuplRealizedSupplyPattern(this, 'utxos_0pct_to_10pct_in_loss'),
_10pctTo20pctInLoss: createMvrvNuplRealizedSupplyPattern(this, 'utxos_10pct_to_20pct_in_loss'),
_20pctTo30pctInLoss: createMvrvNuplRealizedSupplyPattern(this, 'utxos_20pct_to_30pct_in_loss'),
_30pctTo40pctInLoss: createMvrvNuplRealizedSupplyPattern(this, 'utxos_30pct_to_40pct_in_loss'),
_40pctTo50pctInLoss: createMvrvNuplRealizedSupplyPattern(this, 'utxos_40pct_to_50pct_in_loss'),
_50pctTo60pctInLoss: createMvrvNuplRealizedSupplyPattern(this, 'utxos_50pct_to_60pct_in_loss'),
_60pctTo70pctInLoss: createMvrvNuplRealizedSupplyPattern(this, 'utxos_60pct_to_70pct_in_loss'),
_70pctTo80pctInLoss: createMvrvNuplRealizedSupplyPattern(this, 'utxos_70pct_to_80pct_in_loss'),
_80pctTo90pctInLoss: createMvrvNuplRealizedSupplyPattern(this, 'utxos_80pct_to_90pct_in_loss'),
_90pctTo100pctInLoss: createMvrvNuplRealizedSupplyPattern(this, 'utxos_90pct_to_100pct_in_loss'),
},
profit: {
breakeven: createRealizedSupplyPattern(this, 'utxos_in_profit'),
_10pct: createRealizedSupplyPattern(this, 'utxos_over_10pct_in_profit'),
_20pct: createRealizedSupplyPattern(this, 'utxos_over_20pct_in_profit'),
_30pct: createRealizedSupplyPattern(this, 'utxos_over_30pct_in_profit'),
_40pct: createRealizedSupplyPattern(this, 'utxos_over_40pct_in_profit'),
_50pct: createRealizedSupplyPattern(this, 'utxos_over_50pct_in_profit'),
_60pct: createRealizedSupplyPattern(this, 'utxos_over_60pct_in_profit'),
_70pct: createRealizedSupplyPattern(this, 'utxos_over_70pct_in_profit'),
_80pct: createRealizedSupplyPattern(this, 'utxos_over_80pct_in_profit'),
_90pct: createRealizedSupplyPattern(this, 'utxos_over_90pct_in_profit'),
_100pct: createRealizedSupplyPattern(this, 'utxos_over_100pct_in_profit'),
_200pct: createRealizedSupplyPattern(this, 'utxos_over_200pct_in_profit'),
_300pct: createRealizedSupplyPattern(this, 'utxos_over_300pct_in_profit'),
_500pct: createRealizedSupplyPattern(this, 'utxos_over_500pct_in_profit'),
breakeven: createMvrvNuplRealizedSupplyPattern(this, 'utxos_in_profit'),
_10pct: createMvrvNuplRealizedSupplyPattern(this, 'utxos_over_10pct_in_profit'),
_20pct: createMvrvNuplRealizedSupplyPattern(this, 'utxos_over_20pct_in_profit'),
_30pct: createMvrvNuplRealizedSupplyPattern(this, 'utxos_over_30pct_in_profit'),
_40pct: createMvrvNuplRealizedSupplyPattern(this, 'utxos_over_40pct_in_profit'),
_50pct: createMvrvNuplRealizedSupplyPattern(this, 'utxos_over_50pct_in_profit'),
_60pct: createMvrvNuplRealizedSupplyPattern(this, 'utxos_over_60pct_in_profit'),
_70pct: createMvrvNuplRealizedSupplyPattern(this, 'utxos_over_70pct_in_profit'),
_80pct: createMvrvNuplRealizedSupplyPattern(this, 'utxos_over_80pct_in_profit'),
_90pct: createMvrvNuplRealizedSupplyPattern(this, 'utxos_over_90pct_in_profit'),
_100pct: createMvrvNuplRealizedSupplyPattern(this, 'utxos_over_100pct_in_profit'),
_200pct: createMvrvNuplRealizedSupplyPattern(this, 'utxos_over_200pct_in_profit'),
_300pct: createMvrvNuplRealizedSupplyPattern(this, 'utxos_over_300pct_in_profit'),
_500pct: createMvrvNuplRealizedSupplyPattern(this, 'utxos_over_500pct_in_profit'),
},
loss: {
breakeven: createRealizedSupplyPattern(this, 'utxos_in_loss'),
_10pct: createRealizedSupplyPattern(this, 'utxos_over_10pct_in_loss'),
_20pct: createRealizedSupplyPattern(this, 'utxos_over_20pct_in_loss'),
_30pct: createRealizedSupplyPattern(this, 'utxos_over_30pct_in_loss'),
_40pct: createRealizedSupplyPattern(this, 'utxos_over_40pct_in_loss'),
_50pct: createRealizedSupplyPattern(this, 'utxos_over_50pct_in_loss'),
_60pct: createRealizedSupplyPattern(this, 'utxos_over_60pct_in_loss'),
_70pct: createRealizedSupplyPattern(this, 'utxos_over_70pct_in_loss'),
_80pct: createRealizedSupplyPattern(this, 'utxos_over_80pct_in_loss'),
breakeven: createMvrvNuplRealizedSupplyPattern(this, 'utxos_in_loss'),
_10pct: createMvrvNuplRealizedSupplyPattern(this, 'utxos_over_10pct_in_loss'),
_20pct: createMvrvNuplRealizedSupplyPattern(this, 'utxos_over_20pct_in_loss'),
_30pct: createMvrvNuplRealizedSupplyPattern(this, 'utxos_over_30pct_in_loss'),
_40pct: createMvrvNuplRealizedSupplyPattern(this, 'utxos_over_40pct_in_loss'),
_50pct: createMvrvNuplRealizedSupplyPattern(this, 'utxos_over_50pct_in_loss'),
_60pct: createMvrvNuplRealizedSupplyPattern(this, 'utxos_over_60pct_in_loss'),
_70pct: createMvrvNuplRealizedSupplyPattern(this, 'utxos_over_70pct_in_loss'),
_80pct: createMvrvNuplRealizedSupplyPattern(this, 'utxos_over_80pct_in_loss'),
},
},
matured: {
under1h: createBtcCentsSatsUsdPattern(this, 'utxo_under_1h_old_matured'),
_1hTo1d: createBtcCentsSatsUsdPattern(this, 'utxo_1h_to_1d_old_matured'),
_1dTo1w: createBtcCentsSatsUsdPattern(this, 'utxo_1d_to_1w_old_matured'),
_1wTo1m: createBtcCentsSatsUsdPattern(this, 'utxo_1w_to_1m_old_matured'),
_1mTo2m: createBtcCentsSatsUsdPattern(this, 'utxo_1m_to_2m_old_matured'),
_2mTo3m: createBtcCentsSatsUsdPattern(this, 'utxo_2m_to_3m_old_matured'),
_3mTo4m: createBtcCentsSatsUsdPattern(this, 'utxo_3m_to_4m_old_matured'),
_4mTo5m: createBtcCentsSatsUsdPattern(this, 'utxo_4m_to_5m_old_matured'),
_5mTo6m: createBtcCentsSatsUsdPattern(this, 'utxo_5m_to_6m_old_matured'),
_6mTo1y: createBtcCentsSatsUsdPattern(this, 'utxo_6m_to_1y_old_matured'),
_1yTo2y: createBtcCentsSatsUsdPattern(this, 'utxo_1y_to_2y_old_matured'),
_2yTo3y: createBtcCentsSatsUsdPattern(this, 'utxo_2y_to_3y_old_matured'),
_3yTo4y: createBtcCentsSatsUsdPattern(this, 'utxo_3y_to_4y_old_matured'),
_4yTo5y: createBtcCentsSatsUsdPattern(this, 'utxo_4y_to_5y_old_matured'),
_5yTo6y: createBtcCentsSatsUsdPattern(this, 'utxo_5y_to_6y_old_matured'),
_6yTo7y: createBtcCentsSatsUsdPattern(this, 'utxo_6y_to_7y_old_matured'),
_7yTo8y: createBtcCentsSatsUsdPattern(this, 'utxo_7y_to_8y_old_matured'),
_8yTo10y: createBtcCentsSatsUsdPattern(this, 'utxo_8y_to_10y_old_matured'),
_10yTo12y: createBtcCentsSatsUsdPattern(this, 'utxo_10y_to_12y_old_matured'),
_12yTo15y: createBtcCentsSatsUsdPattern(this, 'utxo_12y_to_15y_old_matured'),
over15y: createBtcCentsSatsUsdPattern(this, 'utxo_over_15y_old_matured'),
under1h: createBaseCumulativeSumPattern4(this, 'utxos_under_1h_old_matured_supply'),
_1hTo1d: createBaseCumulativeSumPattern4(this, 'utxos_1h_to_1d_old_matured_supply'),
_1dTo1w: createBaseCumulativeSumPattern4(this, 'utxos_1d_to_1w_old_matured_supply'),
_1wTo1m: createBaseCumulativeSumPattern4(this, 'utxos_1w_to_1m_old_matured_supply'),
_1mTo2m: createBaseCumulativeSumPattern4(this, 'utxos_1m_to_2m_old_matured_supply'),
_2mTo3m: createBaseCumulativeSumPattern4(this, 'utxos_2m_to_3m_old_matured_supply'),
_3mTo4m: createBaseCumulativeSumPattern4(this, 'utxos_3m_to_4m_old_matured_supply'),
_4mTo5m: createBaseCumulativeSumPattern4(this, 'utxos_4m_to_5m_old_matured_supply'),
_5mTo6m: createBaseCumulativeSumPattern4(this, 'utxos_5m_to_6m_old_matured_supply'),
_6mTo1y: createBaseCumulativeSumPattern4(this, 'utxos_6m_to_1y_old_matured_supply'),
_1yTo2y: createBaseCumulativeSumPattern4(this, 'utxos_1y_to_2y_old_matured_supply'),
_2yTo3y: createBaseCumulativeSumPattern4(this, 'utxos_2y_to_3y_old_matured_supply'),
_3yTo4y: createBaseCumulativeSumPattern4(this, 'utxos_3y_to_4y_old_matured_supply'),
_4yTo5y: createBaseCumulativeSumPattern4(this, 'utxos_4y_to_5y_old_matured_supply'),
_5yTo6y: createBaseCumulativeSumPattern4(this, 'utxos_5y_to_6y_old_matured_supply'),
_6yTo7y: createBaseCumulativeSumPattern4(this, 'utxos_6y_to_7y_old_matured_supply'),
_7yTo8y: createBaseCumulativeSumPattern4(this, 'utxos_7y_to_8y_old_matured_supply'),
_8yTo10y: createBaseCumulativeSumPattern4(this, 'utxos_8y_to_10y_old_matured_supply'),
_10yTo12y: createBaseCumulativeSumPattern4(this, 'utxos_10y_to_12y_old_matured_supply'),
_12yTo15y: createBaseCumulativeSumPattern4(this, 'utxos_12y_to_15y_old_matured_supply'),
over15y: createBaseCumulativeSumPattern4(this, 'utxos_over_15y_old_matured_supply'),
},
},
address: {

View File

@@ -2585,6 +2585,17 @@ class BpsCentsRatioSatsUsdPattern:
self.sats: MetricPattern1[SatsFract] = MetricPattern1(client, _m(acc, 'sats'))
self.usd: MetricPattern1[Dollars] = MetricPattern1(client, acc)
class BtcCentsDeltaSatsUsdPattern:
"""Pattern struct for repeated tree structure."""
def __init__(self, client: BrkClientBase, acc: str):
"""Create pattern node with accumulated metric name."""
self.btc: MetricPattern1[Bitcoin] = MetricPattern1(client, acc)
self.cents: MetricPattern1[Cents] = MetricPattern1(client, _m(acc, 'cents'))
self.delta: AbsoluteRatePattern = AbsoluteRatePattern(client, _m(acc, 'delta'))
self.sats: MetricPattern1[Sats] = MetricPattern1(client, _m(acc, 'sats'))
self.usd: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'usd'))
class BtcCentsRelSatsUsdPattern:
"""Pattern struct for repeated tree structure."""
@@ -2640,6 +2651,17 @@ class InvestedMaxMinPercentilesSupplyPattern:
self.percentiles: Pct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern = Pct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern(client, _m(acc, 'cost_basis'))
self.supply_density: BpsPercentRatioPattern3 = BpsPercentRatioPattern3(client, _m(acc, 'supply_density'))
class MvrvNuplRealizedSupplyPattern:
"""Pattern struct for repeated tree structure."""
def __init__(self, client: BrkClientBase, acc: str):
"""Create pattern node with accumulated metric name."""
self.mvrv: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'mvrv'))
self.nupl: BpsRatioPattern = BpsRatioPattern(client, _m(acc, 'nupl'))
self.realized_cap: AllSthPattern = AllSthPattern(client, acc)
self.realized_price: BpsCentsRatioSatsUsdPattern = BpsCentsRatioSatsUsdPattern(client, _m(acc, 'realized_price'))
self.supply: AllSthPattern2 = AllSthPattern2(client, acc)
class PhsReboundThsPattern:
"""Pattern struct for repeated tree structure."""
@@ -2992,7 +3014,7 @@ class LossNuplProfitPattern:
def __init__(self, client: BrkClientBase, acc: str):
"""Create pattern node with accumulated metric name."""
self.loss: BaseCumulativeSumPattern3 = BaseCumulativeSumPattern3(client, _m(acc, 'unrealized_loss'))
self.loss: BaseCumulativeNegativeSumPattern = BaseCumulativeNegativeSumPattern(client, acc)
self.nupl: BpsRatioPattern = BpsRatioPattern(client, _m(acc, 'nupl'))
self.profit: BaseCumulativeSumPattern3 = BaseCumulativeSumPattern3(client, _m(acc, 'unrealized_profit'))
@@ -3057,6 +3079,22 @@ class AbsoluteRatePattern2:
self.absolute: _1m1w1y24hPattern3 = _1m1w1y24hPattern3(client, acc)
self.rate: _1m1w1y24hPattern2 = _1m1w1y24hPattern2(client, acc)
class AllSthPattern2:
"""Pattern struct for repeated tree structure."""
def __init__(self, client: BrkClientBase, acc: str):
"""Create pattern node with accumulated metric name."""
self.all: BtcCentsDeltaSatsUsdPattern = BtcCentsDeltaSatsUsdPattern(client, _m(acc, 'supply'))
self.sth: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, _m(acc, 'sth_supply'))
class AllSthPattern:
"""Pattern struct for repeated tree structure."""
def __init__(self, client: BrkClientBase, acc: str):
"""Create pattern node with accumulated metric name."""
self.all: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'realized_cap'))
self.sth: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'sth_realized_cap'))
class BlocksDominancePattern:
"""Pattern struct for repeated tree structure."""
@@ -3129,14 +3167,6 @@ class PriceValuePattern:
self.price: CentsSatsUsdPattern = CentsSatsUsdPattern(client, _m(acc, 'p3sd_4y'))
self.value: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'ratio_p3sd_4y'))
class RealizedSupplyPattern:
"""Pattern struct for repeated tree structure."""
def __init__(self, client: BrkClientBase, acc: str):
"""Create pattern node with accumulated metric name."""
self.realized_cap: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'realized_cap'))
self.supply: MetricPattern1[Sats] = MetricPattern1(client, _m(acc, 'supply'))
class RelPattern:
"""Pattern struct for repeated tree structure."""
@@ -3647,18 +3677,11 @@ class MetricsTree_Scripts_Count:
self.unknown_output: BaseCumulativeSumPattern[StoredU64] = BaseCumulativeSumPattern(client, 'unknown_output_count')
self.segwit: BaseCumulativeSumPattern[StoredU64] = BaseCumulativeSumPattern(client, 'segwit_count')
class MetricsTree_Scripts_Value_OpReturn:
"""Metrics tree node."""
def __init__(self, client: BrkClientBase, base_path: str = ''):
self.base: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'op_return_value')
self.cumulative: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'op_return_value_cumulative')
class MetricsTree_Scripts_Value:
"""Metrics tree node."""
def __init__(self, client: BrkClientBase, base_path: str = ''):
self.op_return: MetricsTree_Scripts_Value_OpReturn = MetricsTree_Scripts_Value_OpReturn(client)
self.op_return: BaseCumulativeSumPattern4 = BaseCumulativeSumPattern4(client, 'op_return_value')
class MetricsTree_Scripts_Adoption:
"""Metrics tree node."""
@@ -4772,7 +4795,6 @@ class MetricsTree_Supply_Burned:
"""Metrics tree node."""
def __init__(self, client: BrkClientBase, base_path: str = ''):
self.op_return: BaseCumulativeSumPattern4 = BaseCumulativeSumPattern4(client, 'op_return_supply')
self.unspendable: BaseCumulativeSumPattern4 = BaseCumulativeSumPattern4(client, 'unspendable_supply')
class MetricsTree_Supply_Velocity:
@@ -5085,64 +5107,64 @@ class MetricsTree_Cohorts_Utxo_Profitability_Range:
"""Metrics tree node."""
def __init__(self, client: BrkClientBase, base_path: str = ''):
self.over_1000pct_in_profit: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_over_1000pct_in_profit')
self._500pct_to_1000pct_in_profit: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_500pct_to_1000pct_in_profit')
self._300pct_to_500pct_in_profit: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_300pct_to_500pct_in_profit')
self._200pct_to_300pct_in_profit: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_200pct_to_300pct_in_profit')
self._100pct_to_200pct_in_profit: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_100pct_to_200pct_in_profit')
self._90pct_to_100pct_in_profit: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_90pct_to_100pct_in_profit')
self._80pct_to_90pct_in_profit: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_80pct_to_90pct_in_profit')
self._70pct_to_80pct_in_profit: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_70pct_to_80pct_in_profit')
self._60pct_to_70pct_in_profit: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_60pct_to_70pct_in_profit')
self._50pct_to_60pct_in_profit: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_50pct_to_60pct_in_profit')
self._40pct_to_50pct_in_profit: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_40pct_to_50pct_in_profit')
self._30pct_to_40pct_in_profit: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_30pct_to_40pct_in_profit')
self._20pct_to_30pct_in_profit: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_20pct_to_30pct_in_profit')
self._10pct_to_20pct_in_profit: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_10pct_to_20pct_in_profit')
self._0pct_to_10pct_in_profit: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_0pct_to_10pct_in_profit')
self._0pct_to_10pct_in_loss: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_0pct_to_10pct_in_loss')
self._10pct_to_20pct_in_loss: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_10pct_to_20pct_in_loss')
self._20pct_to_30pct_in_loss: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_20pct_to_30pct_in_loss')
self._30pct_to_40pct_in_loss: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_30pct_to_40pct_in_loss')
self._40pct_to_50pct_in_loss: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_40pct_to_50pct_in_loss')
self._50pct_to_60pct_in_loss: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_50pct_to_60pct_in_loss')
self._60pct_to_70pct_in_loss: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_60pct_to_70pct_in_loss')
self._70pct_to_80pct_in_loss: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_70pct_to_80pct_in_loss')
self._80pct_to_90pct_in_loss: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_80pct_to_90pct_in_loss')
self._90pct_to_100pct_in_loss: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_90pct_to_100pct_in_loss')
self.over_1000pct_in_profit: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_over_1000pct_in_profit')
self._500pct_to_1000pct_in_profit: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_500pct_to_1000pct_in_profit')
self._300pct_to_500pct_in_profit: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_300pct_to_500pct_in_profit')
self._200pct_to_300pct_in_profit: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_200pct_to_300pct_in_profit')
self._100pct_to_200pct_in_profit: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_100pct_to_200pct_in_profit')
self._90pct_to_100pct_in_profit: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_90pct_to_100pct_in_profit')
self._80pct_to_90pct_in_profit: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_80pct_to_90pct_in_profit')
self._70pct_to_80pct_in_profit: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_70pct_to_80pct_in_profit')
self._60pct_to_70pct_in_profit: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_60pct_to_70pct_in_profit')
self._50pct_to_60pct_in_profit: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_50pct_to_60pct_in_profit')
self._40pct_to_50pct_in_profit: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_40pct_to_50pct_in_profit')
self._30pct_to_40pct_in_profit: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_30pct_to_40pct_in_profit')
self._20pct_to_30pct_in_profit: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_20pct_to_30pct_in_profit')
self._10pct_to_20pct_in_profit: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_10pct_to_20pct_in_profit')
self._0pct_to_10pct_in_profit: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_0pct_to_10pct_in_profit')
self._0pct_to_10pct_in_loss: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_0pct_to_10pct_in_loss')
self._10pct_to_20pct_in_loss: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_10pct_to_20pct_in_loss')
self._20pct_to_30pct_in_loss: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_20pct_to_30pct_in_loss')
self._30pct_to_40pct_in_loss: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_30pct_to_40pct_in_loss')
self._40pct_to_50pct_in_loss: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_40pct_to_50pct_in_loss')
self._50pct_to_60pct_in_loss: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_50pct_to_60pct_in_loss')
self._60pct_to_70pct_in_loss: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_60pct_to_70pct_in_loss')
self._70pct_to_80pct_in_loss: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_70pct_to_80pct_in_loss')
self._80pct_to_90pct_in_loss: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_80pct_to_90pct_in_loss')
self._90pct_to_100pct_in_loss: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_90pct_to_100pct_in_loss')
class MetricsTree_Cohorts_Utxo_Profitability_Profit:
"""Metrics tree node."""
def __init__(self, client: BrkClientBase, base_path: str = ''):
self.breakeven: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_in_profit')
self._10pct: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_over_10pct_in_profit')
self._20pct: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_over_20pct_in_profit')
self._30pct: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_over_30pct_in_profit')
self._40pct: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_over_40pct_in_profit')
self._50pct: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_over_50pct_in_profit')
self._60pct: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_over_60pct_in_profit')
self._70pct: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_over_70pct_in_profit')
self._80pct: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_over_80pct_in_profit')
self._90pct: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_over_90pct_in_profit')
self._100pct: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_over_100pct_in_profit')
self._200pct: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_over_200pct_in_profit')
self._300pct: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_over_300pct_in_profit')
self._500pct: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_over_500pct_in_profit')
self.breakeven: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_in_profit')
self._10pct: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_over_10pct_in_profit')
self._20pct: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_over_20pct_in_profit')
self._30pct: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_over_30pct_in_profit')
self._40pct: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_over_40pct_in_profit')
self._50pct: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_over_50pct_in_profit')
self._60pct: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_over_60pct_in_profit')
self._70pct: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_over_70pct_in_profit')
self._80pct: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_over_80pct_in_profit')
self._90pct: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_over_90pct_in_profit')
self._100pct: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_over_100pct_in_profit')
self._200pct: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_over_200pct_in_profit')
self._300pct: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_over_300pct_in_profit')
self._500pct: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_over_500pct_in_profit')
class MetricsTree_Cohorts_Utxo_Profitability_Loss:
"""Metrics tree node."""
def __init__(self, client: BrkClientBase, base_path: str = ''):
self.breakeven: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_in_loss')
self._10pct: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_over_10pct_in_loss')
self._20pct: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_over_20pct_in_loss')
self._30pct: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_over_30pct_in_loss')
self._40pct: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_over_40pct_in_loss')
self._50pct: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_over_50pct_in_loss')
self._60pct: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_over_60pct_in_loss')
self._70pct: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_over_70pct_in_loss')
self._80pct: RealizedSupplyPattern = RealizedSupplyPattern(client, 'utxos_over_80pct_in_loss')
self.breakeven: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_in_loss')
self._10pct: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_over_10pct_in_loss')
self._20pct: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_over_20pct_in_loss')
self._30pct: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_over_30pct_in_loss')
self._40pct: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_over_40pct_in_loss')
self._50pct: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_over_50pct_in_loss')
self._60pct: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_over_60pct_in_loss')
self._70pct: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_over_70pct_in_loss')
self._80pct: MvrvNuplRealizedSupplyPattern = MvrvNuplRealizedSupplyPattern(client, 'utxos_over_80pct_in_loss')
class MetricsTree_Cohorts_Utxo_Profitability:
"""Metrics tree node."""
@@ -5156,27 +5178,27 @@ class MetricsTree_Cohorts_Utxo_Matured:
"""Metrics tree node."""
def __init__(self, client: BrkClientBase, base_path: str = ''):
self.under_1h: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxo_under_1h_old_matured')
self._1h_to_1d: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxo_1h_to_1d_old_matured')
self._1d_to_1w: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxo_1d_to_1w_old_matured')
self._1w_to_1m: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxo_1w_to_1m_old_matured')
self._1m_to_2m: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxo_1m_to_2m_old_matured')
self._2m_to_3m: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxo_2m_to_3m_old_matured')
self._3m_to_4m: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxo_3m_to_4m_old_matured')
self._4m_to_5m: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxo_4m_to_5m_old_matured')
self._5m_to_6m: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxo_5m_to_6m_old_matured')
self._6m_to_1y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxo_6m_to_1y_old_matured')
self._1y_to_2y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxo_1y_to_2y_old_matured')
self._2y_to_3y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxo_2y_to_3y_old_matured')
self._3y_to_4y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxo_3y_to_4y_old_matured')
self._4y_to_5y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxo_4y_to_5y_old_matured')
self._5y_to_6y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxo_5y_to_6y_old_matured')
self._6y_to_7y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxo_6y_to_7y_old_matured')
self._7y_to_8y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxo_7y_to_8y_old_matured')
self._8y_to_10y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxo_8y_to_10y_old_matured')
self._10y_to_12y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxo_10y_to_12y_old_matured')
self._12y_to_15y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxo_12y_to_15y_old_matured')
self.over_15y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxo_over_15y_old_matured')
self.under_1h: BaseCumulativeSumPattern4 = BaseCumulativeSumPattern4(client, 'utxos_under_1h_old_matured_supply')
self._1h_to_1d: BaseCumulativeSumPattern4 = BaseCumulativeSumPattern4(client, 'utxos_1h_to_1d_old_matured_supply')
self._1d_to_1w: BaseCumulativeSumPattern4 = BaseCumulativeSumPattern4(client, 'utxos_1d_to_1w_old_matured_supply')
self._1w_to_1m: BaseCumulativeSumPattern4 = BaseCumulativeSumPattern4(client, 'utxos_1w_to_1m_old_matured_supply')
self._1m_to_2m: BaseCumulativeSumPattern4 = BaseCumulativeSumPattern4(client, 'utxos_1m_to_2m_old_matured_supply')
self._2m_to_3m: BaseCumulativeSumPattern4 = BaseCumulativeSumPattern4(client, 'utxos_2m_to_3m_old_matured_supply')
self._3m_to_4m: BaseCumulativeSumPattern4 = BaseCumulativeSumPattern4(client, 'utxos_3m_to_4m_old_matured_supply')
self._4m_to_5m: BaseCumulativeSumPattern4 = BaseCumulativeSumPattern4(client, 'utxos_4m_to_5m_old_matured_supply')
self._5m_to_6m: BaseCumulativeSumPattern4 = BaseCumulativeSumPattern4(client, 'utxos_5m_to_6m_old_matured_supply')
self._6m_to_1y: BaseCumulativeSumPattern4 = BaseCumulativeSumPattern4(client, 'utxos_6m_to_1y_old_matured_supply')
self._1y_to_2y: BaseCumulativeSumPattern4 = BaseCumulativeSumPattern4(client, 'utxos_1y_to_2y_old_matured_supply')
self._2y_to_3y: BaseCumulativeSumPattern4 = BaseCumulativeSumPattern4(client, 'utxos_2y_to_3y_old_matured_supply')
self._3y_to_4y: BaseCumulativeSumPattern4 = BaseCumulativeSumPattern4(client, 'utxos_3y_to_4y_old_matured_supply')
self._4y_to_5y: BaseCumulativeSumPattern4 = BaseCumulativeSumPattern4(client, 'utxos_4y_to_5y_old_matured_supply')
self._5y_to_6y: BaseCumulativeSumPattern4 = BaseCumulativeSumPattern4(client, 'utxos_5y_to_6y_old_matured_supply')
self._6y_to_7y: BaseCumulativeSumPattern4 = BaseCumulativeSumPattern4(client, 'utxos_6y_to_7y_old_matured_supply')
self._7y_to_8y: BaseCumulativeSumPattern4 = BaseCumulativeSumPattern4(client, 'utxos_7y_to_8y_old_matured_supply')
self._8y_to_10y: BaseCumulativeSumPattern4 = BaseCumulativeSumPattern4(client, 'utxos_8y_to_10y_old_matured_supply')
self._10y_to_12y: BaseCumulativeSumPattern4 = BaseCumulativeSumPattern4(client, 'utxos_10y_to_12y_old_matured_supply')
self._12y_to_15y: BaseCumulativeSumPattern4 = BaseCumulativeSumPattern4(client, 'utxos_12y_to_15y_old_matured_supply')
self.over_15y: BaseCumulativeSumPattern4 = BaseCumulativeSumPattern4(client, 'utxos_over_15y_old_matured_supply')
class MetricsTree_Cohorts_Utxo:
"""Metrics tree node."""
@@ -6195,6 +6217,255 @@ class BrkClient(BrkClientBase):
}
}
PROFITABILITY_RANGE_NAMES = {
"over_1000pct_in_profit": {
"id": "utxos_over_1000pct_in_profit",
"short": ">1000%",
"long": "Over 1000% Profit"
},
"_500pct_to_1000pct_in_profit": {
"id": "utxos_500pct_to_1000pct_in_profit",
"short": "500-1000%",
"long": "500-1000% Profit"
},
"_300pct_to_500pct_in_profit": {
"id": "utxos_300pct_to_500pct_in_profit",
"short": "300-500%",
"long": "300-500% Profit"
},
"_200pct_to_300pct_in_profit": {
"id": "utxos_200pct_to_300pct_in_profit",
"short": "200-300%",
"long": "200-300% Profit"
},
"_100pct_to_200pct_in_profit": {
"id": "utxos_100pct_to_200pct_in_profit",
"short": "100-200%",
"long": "100-200% Profit"
},
"_90pct_to_100pct_in_profit": {
"id": "utxos_90pct_to_100pct_in_profit",
"short": "90-100%",
"long": "90-100% Profit"
},
"_80pct_to_90pct_in_profit": {
"id": "utxos_80pct_to_90pct_in_profit",
"short": "80-90%",
"long": "80-90% Profit"
},
"_70pct_to_80pct_in_profit": {
"id": "utxos_70pct_to_80pct_in_profit",
"short": "70-80%",
"long": "70-80% Profit"
},
"_60pct_to_70pct_in_profit": {
"id": "utxos_60pct_to_70pct_in_profit",
"short": "60-70%",
"long": "60-70% Profit"
},
"_50pct_to_60pct_in_profit": {
"id": "utxos_50pct_to_60pct_in_profit",
"short": "50-60%",
"long": "50-60% Profit"
},
"_40pct_to_50pct_in_profit": {
"id": "utxos_40pct_to_50pct_in_profit",
"short": "40-50%",
"long": "40-50% Profit"
},
"_30pct_to_40pct_in_profit": {
"id": "utxos_30pct_to_40pct_in_profit",
"short": "30-40%",
"long": "30-40% Profit"
},
"_20pct_to_30pct_in_profit": {
"id": "utxos_20pct_to_30pct_in_profit",
"short": "20-30%",
"long": "20-30% Profit"
},
"_10pct_to_20pct_in_profit": {
"id": "utxos_10pct_to_20pct_in_profit",
"short": "10-20%",
"long": "10-20% Profit"
},
"_0pct_to_10pct_in_profit": {
"id": "utxos_0pct_to_10pct_in_profit",
"short": "0-10%",
"long": "0-10% Profit"
},
"_0pct_to_10pct_in_loss": {
"id": "utxos_0pct_to_10pct_in_loss",
"short": "0-10%L",
"long": "0-10% Loss"
},
"_10pct_to_20pct_in_loss": {
"id": "utxos_10pct_to_20pct_in_loss",
"short": "10-20%L",
"long": "10-20% Loss"
},
"_20pct_to_30pct_in_loss": {
"id": "utxos_20pct_to_30pct_in_loss",
"short": "20-30%L",
"long": "20-30% Loss"
},
"_30pct_to_40pct_in_loss": {
"id": "utxos_30pct_to_40pct_in_loss",
"short": "30-40%L",
"long": "30-40% Loss"
},
"_40pct_to_50pct_in_loss": {
"id": "utxos_40pct_to_50pct_in_loss",
"short": "40-50%L",
"long": "40-50% Loss"
},
"_50pct_to_60pct_in_loss": {
"id": "utxos_50pct_to_60pct_in_loss",
"short": "50-60%L",
"long": "50-60% Loss"
},
"_60pct_to_70pct_in_loss": {
"id": "utxos_60pct_to_70pct_in_loss",
"short": "60-70%L",
"long": "60-70% Loss"
},
"_70pct_to_80pct_in_loss": {
"id": "utxos_70pct_to_80pct_in_loss",
"short": "70-80%L",
"long": "70-80% Loss"
},
"_80pct_to_90pct_in_loss": {
"id": "utxos_80pct_to_90pct_in_loss",
"short": "80-90%L",
"long": "80-90% Loss"
},
"_90pct_to_100pct_in_loss": {
"id": "utxos_90pct_to_100pct_in_loss",
"short": "90-100%L",
"long": "90-100% Loss"
}
}
PROFIT_NAMES = {
"breakeven": {
"id": "utxos_in_profit",
"short": "≥0%",
"long": "In Profit (Breakeven+)"
},
"_10pct": {
"id": "utxos_over_10pct_in_profit",
"short": "≥10%",
"long": "10%+ Profit"
},
"_20pct": {
"id": "utxos_over_20pct_in_profit",
"short": "≥20%",
"long": "20%+ Profit"
},
"_30pct": {
"id": "utxos_over_30pct_in_profit",
"short": "≥30%",
"long": "30%+ Profit"
},
"_40pct": {
"id": "utxos_over_40pct_in_profit",
"short": "≥40%",
"long": "40%+ Profit"
},
"_50pct": {
"id": "utxos_over_50pct_in_profit",
"short": "≥50%",
"long": "50%+ Profit"
},
"_60pct": {
"id": "utxos_over_60pct_in_profit",
"short": "≥60%",
"long": "60%+ Profit"
},
"_70pct": {
"id": "utxos_over_70pct_in_profit",
"short": "≥70%",
"long": "70%+ Profit"
},
"_80pct": {
"id": "utxos_over_80pct_in_profit",
"short": "≥80%",
"long": "80%+ Profit"
},
"_90pct": {
"id": "utxos_over_90pct_in_profit",
"short": "≥90%",
"long": "90%+ Profit"
},
"_100pct": {
"id": "utxos_over_100pct_in_profit",
"short": "≥100%",
"long": "100%+ Profit"
},
"_200pct": {
"id": "utxos_over_200pct_in_profit",
"short": "≥200%",
"long": "200%+ Profit"
},
"_300pct": {
"id": "utxos_over_300pct_in_profit",
"short": "≥300%",
"long": "300%+ Profit"
},
"_500pct": {
"id": "utxos_over_500pct_in_profit",
"short": "≥500%",
"long": "500%+ Profit"
}
}
LOSS_NAMES = {
"breakeven": {
"id": "utxos_in_loss",
"short": "<0%",
"long": "In Loss (Below Breakeven)"
},
"_10pct": {
"id": "utxos_over_10pct_in_loss",
"short": "≥10%L",
"long": "10%+ Loss"
},
"_20pct": {
"id": "utxos_over_20pct_in_loss",
"short": "≥20%L",
"long": "20%+ Loss"
},
"_30pct": {
"id": "utxos_over_30pct_in_loss",
"short": "≥30%L",
"long": "30%+ Loss"
},
"_40pct": {
"id": "utxos_over_40pct_in_loss",
"short": "≥40%L",
"long": "40%+ Loss"
},
"_50pct": {
"id": "utxos_over_50pct_in_loss",
"short": "≥50%L",
"long": "50%+ Loss"
},
"_60pct": {
"id": "utxos_over_60pct_in_loss",
"short": "≥60%L",
"long": "60%+ Loss"
},
"_70pct": {
"id": "utxos_over_70pct_in_loss",
"short": "≥70%L",
"long": "70%+ Loss"
},
"_80pct": {
"id": "utxos_over_80pct_in_loss",
"short": "≥80%L",
"long": "80%+ Loss"
}
}
def __init__(self, base_url: str = 'http://localhost:3000', timeout: float = 30.0):
super().__init__(base_url, timeout)
self.metrics = MetricsTree(self)

View File

@@ -77,6 +77,127 @@ function volumeAndCoinsTree(activity, color, title) {
];
}
/**
* Sent in profit/loss breakdown tree (shared by full and mid-level activity)
* @param {Brk.BaseCumulativeInSumPattern} sent
* @param {(metric: string) => string} title
* @returns {PartialOptionsTree}
*/
function sentProfitLossTree(sent, title) {
return [
{
name: "Sent In Profit",
tree: [
{
name: "USD",
title: title("Sent Volume In Profit"),
bottom: [
line({ metric: sent.inProfit.base.usd, name: "Base", color: colors.profit, unit: Unit.usd }),
line({ metric: sent.inProfit.sum._24h.usd, name: "24h", color: colors.time._24h, unit: Unit.usd, defaultActive: false }),
line({ metric: sent.inProfit.sum._1w.usd, name: "1w", color: colors.time._1w, unit: Unit.usd, defaultActive: false }),
line({ metric: sent.inProfit.sum._1m.usd, name: "1m", color: colors.time._1m, unit: Unit.usd, defaultActive: false }),
line({ metric: sent.inProfit.sum._1y.usd, name: "1y", color: colors.time._1y, unit: Unit.usd, defaultActive: false }),
],
},
{
name: "BTC",
title: title("Sent Volume In Profit (BTC)"),
bottom: [
line({ metric: sent.inProfit.base.btc, name: "Base", color: colors.profit, unit: Unit.btc }),
line({ metric: sent.inProfit.sum._24h.btc, name: "24h", color: colors.time._24h, unit: Unit.btc, defaultActive: false }),
line({ metric: sent.inProfit.sum._1w.btc, name: "1w", color: colors.time._1w, unit: Unit.btc, defaultActive: false }),
line({ metric: sent.inProfit.sum._1m.btc, name: "1m", color: colors.time._1m, unit: Unit.btc, defaultActive: false }),
line({ metric: sent.inProfit.sum._1y.btc, name: "1y", color: colors.time._1y, unit: Unit.btc, defaultActive: false }),
],
},
{
name: "Sats",
title: title("Sent Volume In Profit (Sats)"),
bottom: [
line({ metric: sent.inProfit.base.sats, name: "Base", color: colors.profit, unit: Unit.sats }),
line({ metric: sent.inProfit.sum._24h.sats, name: "24h", color: colors.time._24h, unit: Unit.sats, defaultActive: false }),
line({ metric: sent.inProfit.sum._1w.sats, name: "1w", color: colors.time._1w, unit: Unit.sats, defaultActive: false }),
line({ metric: sent.inProfit.sum._1m.sats, name: "1m", color: colors.time._1m, unit: Unit.sats, defaultActive: false }),
line({ metric: sent.inProfit.sum._1y.sats, name: "1y", color: colors.time._1y, unit: Unit.sats, defaultActive: false }),
],
},
{ name: "Cumulative", title: title("Cumulative Sent In Profit"), bottom: [
line({ metric: sent.inProfit.cumulative.usd, name: "USD", color: colors.profit, unit: Unit.usd }),
line({ metric: sent.inProfit.cumulative.btc, name: "BTC", color: colors.profit, unit: Unit.btc, defaultActive: false }),
line({ metric: sent.inProfit.cumulative.sats, name: "Sats", color: colors.profit, unit: Unit.sats, defaultActive: false }),
]},
],
},
{
name: "Sent In Loss",
tree: [
{
name: "USD",
title: title("Sent Volume In Loss"),
bottom: [
line({ metric: sent.inLoss.base.usd, name: "Base", color: colors.loss, unit: Unit.usd }),
line({ metric: sent.inLoss.sum._24h.usd, name: "24h", color: colors.time._24h, unit: Unit.usd, defaultActive: false }),
line({ metric: sent.inLoss.sum._1w.usd, name: "1w", color: colors.time._1w, unit: Unit.usd, defaultActive: false }),
line({ metric: sent.inLoss.sum._1m.usd, name: "1m", color: colors.time._1m, unit: Unit.usd, defaultActive: false }),
line({ metric: sent.inLoss.sum._1y.usd, name: "1y", color: colors.time._1y, unit: Unit.usd, defaultActive: false }),
],
},
{
name: "BTC",
title: title("Sent Volume In Loss (BTC)"),
bottom: [
line({ metric: sent.inLoss.base.btc, name: "Base", color: colors.loss, unit: Unit.btc }),
line({ metric: sent.inLoss.sum._24h.btc, name: "24h", color: colors.time._24h, unit: Unit.btc, defaultActive: false }),
line({ metric: sent.inLoss.sum._1w.btc, name: "1w", color: colors.time._1w, unit: Unit.btc, defaultActive: false }),
line({ metric: sent.inLoss.sum._1m.btc, name: "1m", color: colors.time._1m, unit: Unit.btc, defaultActive: false }),
line({ metric: sent.inLoss.sum._1y.btc, name: "1y", color: colors.time._1y, unit: Unit.btc, defaultActive: false }),
],
},
{
name: "Sats",
title: title("Sent Volume In Loss (Sats)"),
bottom: [
line({ metric: sent.inLoss.base.sats, name: "Base", color: colors.loss, unit: Unit.sats }),
line({ metric: sent.inLoss.sum._24h.sats, name: "24h", color: colors.time._24h, unit: Unit.sats, defaultActive: false }),
line({ metric: sent.inLoss.sum._1w.sats, name: "1w", color: colors.time._1w, unit: Unit.sats, defaultActive: false }),
line({ metric: sent.inLoss.sum._1m.sats, name: "1m", color: colors.time._1m, unit: Unit.sats, defaultActive: false }),
line({ metric: sent.inLoss.sum._1y.sats, name: "1y", color: colors.time._1y, unit: Unit.sats, defaultActive: false }),
],
},
{ name: "Cumulative", title: title("Cumulative Sent In Loss"), bottom: [
line({ metric: sent.inLoss.cumulative.usd, name: "USD", color: colors.loss, unit: Unit.usd }),
line({ metric: sent.inLoss.cumulative.btc, name: "BTC", color: colors.loss, unit: Unit.btc, defaultActive: false }),
line({ metric: sent.inLoss.cumulative.sats, name: "Sats", color: colors.loss, unit: Unit.sats, defaultActive: false }),
]},
],
},
];
}
/**
* Volume and coins tree with coinyears, dormancy, and sent in profit/loss (All/STH/LTH)
* @param {Brk.CoindaysCoinyearsDormancySentPattern} activity
* @param {Color} color
* @param {(metric: string) => string} title
* @returns {PartialOptionsTree}
*/
function fullVolumeTree(activity, color, title) {
return [
...volumeAndCoinsTree(activity, color, title),
...sentProfitLossTree(activity.sent, title),
{
name: "Coinyears Destroyed",
title: title("Coinyears Destroyed"),
bottom: [line({ metric: activity.coinyearsDestroyed, name: "CYD", color, unit: Unit.years })],
},
{
name: "Dormancy",
title: title("Dormancy"),
bottom: [line({ metric: activity.dormancy, name: "Dormancy", color, unit: Unit.days })],
},
];
}
// ============================================================================
// Shared SOPR Helpers
// ============================================================================
@@ -349,7 +470,7 @@ export function createActivitySectionWithAdjusted({ cohort, title }) {
return {
name: "Activity",
tree: [
...volumeAndCoinsTree(tree.activity, color, title),
...fullVolumeTree(tree.activity, color, title),
{
name: "SOPR",
tree: [
@@ -400,7 +521,7 @@ export function createActivitySection({ cohort, title }) {
return {
name: "Activity",
tree: [
...volumeAndCoinsTree(tree.activity, color, title),
...fullVolumeTree(tree.activity, color, title),
{
name: "SOPR",
tree: singleRollingSoprTree(sopr.ratio, title),
@@ -430,6 +551,7 @@ export function createActivitySectionWithActivity({ cohort, title }) {
name: "Activity",
tree: [
...volumeAndCoinsTree(tree.activity, color, title),
...sentProfitLossTree(tree.activity.sent, title),
{
name: "SOPR",
title: title("SOPR (24h)"),

View File

@@ -83,6 +83,7 @@ export function buildCohortData() {
title: `UTXOs ${names.long}`,
color: colors.at(i, arr.length),
tree: utxoCohorts.ageRange[key],
matured: utxoCohorts.matured[key],
}));
const epoch = entries(EPOCH_NAMES).map(([key, names], i, arr) => ({

View File

@@ -115,6 +115,34 @@ function circulatingSupplyPctSeries(supply) {
];
}
/**
* Ratio of Circulating Supply series (total, profit, loss)
* @param {{ relToCirculating: { ratio: AnyMetricPattern }, inProfit: { relToCirculating: { ratio: AnyMetricPattern } }, inLoss: { relToCirculating: { ratio: AnyMetricPattern } } }} supply
* @returns {AnyFetchedSeriesBlueprint[]}
*/
function circulatingSupplyRatioSeries(supply) {
return [
line({
metric: supply.relToCirculating.ratio,
name: "Total",
color: colors.default,
unit: Unit.ratio,
}),
line({
metric: supply.inProfit.relToCirculating.ratio,
name: "In Profit",
color: colors.profit,
unit: Unit.ratio,
}),
line({
metric: supply.inLoss.relToCirculating.ratio,
name: "In Loss",
color: colors.loss,
unit: Unit.ratio,
}),
];
}
/**
* @param {readonly (UtxoCohortObject | CohortWithoutRelative)[]} list
* @param {CohortAll} all
@@ -157,7 +185,7 @@ function singleDeltaTree(delta, unit, title, name) {
* @template {{ name: string, color: Color }} A
* @param {readonly T[]} list
* @param {A} all
* @param {(c: T | A) => ChangeRatePattern | ChangeRatePattern2} getDelta
* @param {(c: T | A) => DeltaPattern} getDelta
* @param {Unit} unit
* @param {(metric: string) => string} title
* @param {string} name
@@ -309,11 +337,21 @@ export function createHoldingsSectionWithRelative({ cohort, title }) {
tree: [
{
name: "Supply",
title: title("Supply"),
bottom: [
...fullSupplySeries(supply),
...circulatingSupplyPctSeries(supply),
...ownSupplyPctSeries(supply),
tree: [
{
name: "Overview",
title: title("Supply"),
bottom: [
...fullSupplySeries(supply),
...circulatingSupplyPctSeries(supply),
...ownSupplyPctSeries(supply),
],
},
{
name: "Ratio",
title: title("Supply (% of Circulating)"),
bottom: circulatingSupplyRatioSeries(supply),
},
],
},
singleUtxoCountChart(cohort, title),
@@ -341,10 +379,20 @@ export function createHoldingsSectionWithOwnSupply({ cohort, title }) {
tree: [
{
name: "Supply",
title: title("Supply"),
bottom: [
...fullSupplySeries(supply),
...circulatingSupplyPctSeries(supply),
tree: [
{
name: "Overview",
title: title("Supply"),
bottom: [
...fullSupplySeries(supply),
...circulatingSupplyPctSeries(supply),
],
},
{
name: "Ratio",
title: title("Supply (% of Circulating)"),
bottom: circulatingSupplyRatioSeries(supply),
},
],
},
singleUtxoCountChart(cohort, title),
@@ -359,6 +407,33 @@ export function createHoldingsSectionWithOwnSupply({ cohort, title }) {
};
}
/**
* Holdings with inProfit/inLoss (no rel, no address count)
* For: CohortWithoutRelative (p2ms, unknown, empty)
* @param {{ cohort: CohortWithoutRelative, title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createHoldingsSectionWithProfitLoss({ cohort, title }) {
return {
name: "Holdings",
tree: [
{
name: "Supply",
title: title("Supply"),
bottom: fullSupplySeries(cohort.tree.supply),
},
singleUtxoCountChart(cohort, title),
{
name: "Change",
tree: [
singleDeltaTree(cohort.tree.supply.delta, Unit.sats, title, "Supply"),
singleDeltaTree(cohort.tree.outputs.unspentCount.delta, Unit.count, title, "UTXO Count"),
],
},
],
};
}
/**
* Holdings for CohortAddress (has inProfit/inLoss but no rel, plus address count)
* @param {{ cohort: CohortAddress, title: (metric: string) => string }} args
@@ -559,6 +634,66 @@ export function createGroupedHoldingsSection({ list, all, title }) {
};
}
/**
* Grouped holdings with inProfit/inLoss (no rel, no address count)
* For: CohortWithoutRelative (p2ms, unknown, empty)
* @param {{ list: readonly CohortWithoutRelative[], all: CohortAll, title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createGroupedHoldingsSectionWithProfitLoss({
list,
all,
title,
}) {
return {
name: "Holdings",
tree: [
{
name: "Supply",
tree: [
{
name: "Total",
title: title("Supply"),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.supply.total, name, color }),
),
},
{
name: "In Profit",
title: title("Supply In Profit"),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
satsBtcUsd({
pattern: tree.supply.inProfit,
name,
color,
}),
),
},
{
name: "In Loss",
title: title("Supply In Loss"),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
satsBtcUsd({
pattern: tree.supply.inLoss,
name,
color,
}),
),
},
],
},
groupedUtxoCountChart(list, all, title),
{
name: "Change",
tree: [
groupedDeltaTree(list, all, (c) => c.tree.supply.delta, Unit.sats, title, "Supply"),
groupedDeltaTree(list, all, (c) => c.tree.outputs.unspentCount.delta, Unit.count, title, "UTXO Count"),
],
},
],
};
}
/**
* Grouped holdings with inProfit/inLoss + relToCirculating (no relToOwn)
* For: CohortWithAdjusted, CohortAgeRange

View File

@@ -10,7 +10,7 @@
* - activity.js: SOPR, Volume, Lifespan
*/
import { formatCohortTitle } from "../shared.js";
import { formatCohortTitle, satsBtcUsd } from "../shared.js";
// Section builders
import {
@@ -18,9 +18,11 @@ import {
createHoldingsSectionAll,
createHoldingsSectionAddress,
createHoldingsSectionAddressAmount,
createHoldingsSectionWithProfitLoss,
createHoldingsSectionWithRelative,
createHoldingsSectionWithOwnSupply,
createGroupedHoldingsSection,
createGroupedHoldingsSectionWithProfitLoss,
createGroupedHoldingsSectionAddress,
createGroupedHoldingsSectionAddressAmount,
createGroupedHoldingsSectionWithRelative,
@@ -42,14 +44,16 @@ import {
createGroupedCostBasisSectionWithPercentiles,
} from "./cost-basis.js";
import {
createProfitabilitySection,
createProfitabilitySectionAll,
createProfitabilitySectionFull,
createProfitabilitySectionWithNupl,
createProfitabilitySectionWithInvestedCapitalPct,
createProfitabilitySectionBasicWithInvestedCapitalPct,
createProfitabilitySectionAddress,
createProfitabilitySectionWithProfitLoss,
createProfitabilitySectionLongTerm,
createGroupedProfitabilitySection,
createGroupedProfitabilitySectionWithProfitLoss,
createGroupedProfitabilitySectionWithNupl,
createGroupedProfitabilitySectionWithInvestedCapitalPct,
createGroupedProfitabilitySectionBasicWithInvestedCapitalPct,
@@ -126,7 +130,7 @@ export function createCohortFolderWithAdjusted(cohort) {
createHoldingsSectionWithOwnSupply({ cohort, title }),
createValuationSection({ cohort, title }),
createPricesSectionBasic({ cohort, title }),
createProfitabilitySectionWithNupl({ cohort, title }),
createProfitabilitySectionWithInvestedCapitalPct({ cohort, title }),
createActivitySectionWithActivity({ cohort, title }),
],
};
@@ -191,6 +195,22 @@ export function createCohortFolderAgeRange(cohort) {
};
}
/**
* Age range folder with matured supply
* @param {CohortAgeRangeWithMatured} cohort
* @returns {PartialOptionsGroup}
*/
export function createCohortFolderAgeRangeWithMatured(cohort) {
const folder = createCohortFolderAgeRange(cohort);
const title = formatCohortTitle(cohort.name);
folder.tree.push({
name: "Matured",
title: title("Matured Supply"),
bottom: satsBtcUsd({ pattern: cohort.matured, name: cohort.name }),
});
return folder;
}
/**
* Basic folder WITH RelToMarketCap
* @param {CohortBasicWithMarketCap} cohort
@@ -242,7 +262,7 @@ export function createCohortFolderAddress(cohort) {
createHoldingsSectionAddress({ cohort, title }),
createValuationSection({ cohort, title }),
createPricesSectionBasic({ cohort, title }),
createProfitabilitySectionBasicWithInvestedCapitalPct({ cohort, title }),
createProfitabilitySectionAddress({ cohort, title }),
createActivitySectionMinimal({ cohort, title }),
],
};
@@ -258,10 +278,10 @@ export function createCohortFolderWithoutRelative(cohort) {
return {
name: cohort.name || "all",
tree: [
createHoldingsSection({ cohort, title }),
createHoldingsSectionWithProfitLoss({ cohort, title }),
createValuationSection({ cohort, title }),
createPricesSectionBasic({ cohort, title }),
createProfitabilitySection({ cohort, title }),
createProfitabilitySectionWithProfitLoss({ cohort, title }),
createActivitySectionMinimal({ cohort, title }),
],
};
@@ -331,7 +351,11 @@ export function createGroupedCohortFolderWithAdjusted({
createGroupedHoldingsSectionWithOwnSupply({ list, all, title }),
createGroupedValuationSection({ list, all, title }),
createGroupedPricesSection({ list, all, title }),
createGroupedProfitabilitySectionWithInvestedCapitalPct({ list, all, title }),
createGroupedProfitabilitySectionWithInvestedCapitalPct({
list,
all,
title,
}),
createGroupedActivitySectionWithActivity({ list, all, title }),
],
};
@@ -412,6 +436,28 @@ export function createGroupedCohortFolderAgeRange({
};
}
/**
* @param {{ name: string, title: string, list: readonly CohortAgeRangeWithMatured[], all: CohortAll }} args
* @returns {PartialOptionsGroup}
*/
export function createGroupedCohortFolderAgeRangeWithMatured({
name,
title: groupTitle,
list,
all,
}) {
const folder = createGroupedCohortFolderAgeRange({ name, title: groupTitle, list, all });
const title = formatCohortTitle(groupTitle);
folder.tree.push({
name: "Matured",
title: title("Matured Supply"),
bottom: list.flatMap((cohort) =>
satsBtcUsd({ pattern: cohort.matured, name: cohort.name, color: cohort.color }),
),
});
return folder;
}
/**
* @param {CohortGroupBasicWithMarketCap} args
* @returns {PartialOptionsGroup}
@@ -503,10 +549,10 @@ export function createGroupedCohortFolderWithoutRelative({
return {
name: name || "all",
tree: [
createGroupedHoldingsSection({ list, all, title }),
createGroupedHoldingsSectionWithProfitLoss({ list, all, title }),
createGroupedValuationSection({ list, all, title }),
createGroupedPricesSection({ list, all, title }),
createGroupedProfitabilitySection({ list, all, title }),
createGroupedProfitabilitySectionWithProfitLoss({ list, all, title }),
createGroupedActivitySectionMinimal({ list, all, title }),
],
};

View File

@@ -98,8 +98,8 @@ export function createPricesSectionBasic({ cohort, title }) {
top: [price({ metric: tree.realized.price, name: "Realized", color })],
},
{
name: "Ratio",
title: title("Realized Price Ratio"),
name: "MVRV",
title: title("MVRV"),
bottom: [
baseline({
metric: tree.realized.mvrv,
@@ -109,6 +109,18 @@ export function createPricesSectionBasic({ cohort, title }) {
}),
],
},
{
name: "Price Ratio",
title: title("Realized Price Ratio"),
bottom: [
baseline({
metric: tree.realized.price.ratio,
name: "Price Ratio",
unit: Unit.ratio,
base: 1,
}),
],
},
],
},
],

View File

@@ -105,6 +105,7 @@ function unrealizedPnlTreeAll(u, title) {
{ name: "USD", title: title("Unrealized P&L"), bottom: unrealizedUsdSeries(u) },
relPnlChart(u.profit.relToMcap, u.loss.relToMcap, "% of Mcap", title),
relPnlChart(u.profit.relToOwnGross, u.loss.relToOwnGross, "% of Own P&L", title),
...unrealizedCumulativeRollingTree(u.profit, u.loss, title),
];
}
@@ -120,6 +121,7 @@ function unrealizedPnlTreeFull(u, title) {
relPnlChart(u.profit.relToMcap, u.loss.relToMcap, "% of Mcap", title),
relPnlChart(u.profit.relToOwnMcap, u.loss.relToOwnMcap, "% of Own Mcap", title),
relPnlChart(u.profit.relToOwnGross, u.loss.relToOwnGross, "% of Own P&L", title),
...unrealizedCumulativeRollingTree(u.profit, u.loss, title),
];
}
@@ -139,21 +141,89 @@ function unrealizedPnlTreeLongTerm(u, title) {
},
relPnlChart(u.profit.relToOwnMcap, u.loss.relToOwnMcap, "% of Own Mcap", title),
relPnlChart(u.profit.relToOwnGross, u.loss.relToOwnGross, "% of Own P&L", title),
...unrealizedCumulativeRollingTree(u.profit, u.loss, title),
];
}
/**
* Unrealized P&L (USD only) for mid-tier cohorts (AgeRange/MaxAge)
* Unrealized P&L tree for mid-tier cohorts (AgeRange/MaxAge)
* @param {Brk.LossNetNuplProfitPattern} u
* @returns {AnyFetchedSeriesBlueprint[]}
* @param {(metric: string) => string} title
* @returns {PartialOptionsTree}
*/
function unrealizedMid(u) {
function unrealizedPnlTreeMid(u, title) {
return [
...pnlLines(
{ profit: u.profit.base.usd, loss: u.loss.base.usd, negLoss: u.loss.negative },
Unit.usd,
),
priceLine({ unit: Unit.usd, defaultActive: false }),
{
name: "USD",
title: title("Unrealized P&L"),
bottom: [
...pnlLines(
{ profit: u.profit.base.usd, loss: u.loss.base.usd, negLoss: u.loss.negative },
Unit.usd,
),
priceLine({ unit: Unit.usd, defaultActive: false }),
],
},
...unrealizedCumulativeRollingTree(u.profit, u.loss, title),
];
}
/**
* Unrealized cumulative + rolling P&L tree (profit and loss have cumulative.usd + sum[w].usd)
* @param {{ cumulative: { usd: AnyMetricPattern }, sum: { _24h: { usd: AnyMetricPattern }, _1w: { usd: AnyMetricPattern }, _1m: { usd: AnyMetricPattern }, _1y: { usd: AnyMetricPattern } } }} profit
* @param {{ cumulative: { usd: AnyMetricPattern }, sum: { _24h: { usd: AnyMetricPattern }, _1w: { usd: AnyMetricPattern }, _1m: { usd: AnyMetricPattern }, _1y: { usd: AnyMetricPattern } } }} loss
* @param {(metric: string) => string} title
* @returns {PartialOptionsTree}
*/
function unrealizedCumulativeRollingTree(profit, loss, title) {
return [
{
name: "Cumulative",
title: title("Cumulative Unrealized P&L"),
bottom: [
line({ metric: profit.cumulative.usd, name: "Profit", color: colors.profit, unit: Unit.usd }),
line({ metric: loss.cumulative.usd, name: "Loss", color: colors.loss, unit: Unit.usd }),
],
},
{
name: "Rolling",
tree: [
{
name: "Profit",
tree: [
{
name: "Compare",
title: title("Rolling Unrealized Profit"),
bottom: ROLLING_WINDOWS.map((w) =>
line({ metric: profit.sum[w.key].usd, name: w.name, color: w.color, unit: Unit.usd }),
),
},
...ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`Unrealized Profit (${w.name})`),
bottom: [line({ metric: profit.sum[w.key].usd, name: "Profit", color: colors.profit, unit: Unit.usd })],
})),
],
},
{
name: "Loss",
tree: [
{
name: "Compare",
title: title("Rolling Unrealized Loss"),
bottom: ROLLING_WINDOWS.map((w) =>
line({ metric: loss.sum[w.key].usd, name: w.name, color: w.color, unit: Unit.usd }),
),
},
...ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`Unrealized Loss (${w.name})`),
bottom: [line({ metric: loss.sum[w.key].usd, name: "Loss", color: colors.loss, unit: Unit.usd })],
})),
],
},
],
},
];
}
@@ -320,24 +390,103 @@ function realizedPnlCumulativeTreeFull(r, title) {
}
/**
* Net realized P&L delta tree (absolute + rate across all rolling windows)
* @param {Brk.BaseChangeCumulativeDeltaRelSumPattern | Brk.BaseCumulativeDeltaSumPattern} netPnl
* @param {(metric: string) => string} title
* @returns {PartialOptionsGroup}
*/
function realizedNetPnlDeltaTree(netPnl, title) {
return {
name: "Change",
tree: [
{
name: "Absolute",
tree: [
{
name: "Compare",
title: title("Net Realized P&L Change"),
bottom: ROLLING_WINDOWS.map((w) =>
baseline({ metric: netPnl.delta.absolute[w.key].usd, name: w.name, color: w.color, unit: Unit.usd }),
),
},
...ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`Net Realized P&L Change (${w.name})`),
bottom: [baseline({ metric: netPnl.delta.absolute[w.key].usd, name: "Change", unit: Unit.usd })],
})),
],
},
{
name: "Rate",
tree: [
{
name: "Compare",
title: title("Net Realized P&L Rate"),
bottom: ROLLING_WINDOWS.flatMap((w) =>
percentRatio({ pattern: netPnl.delta.rate[w.key], name: w.name, color: w.color }),
),
},
...ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`Net Realized P&L Rate (${w.name})`),
bottom: percentRatioBaseline({ pattern: netPnl.delta.rate[w.key], name: "Rate" }),
})),
],
},
],
};
}
/**
* Full realized delta tree (absolute + rate + rel to mcap/rcap)
* @param {Brk.CapGrossInvestorLossMvrvNetPeakPriceProfitSellSoprPattern | Brk.MetricsTree_Cohorts_Utxo_Lth_Realized} r
* @param {(metric: string) => string} title
* @returns {PartialOptionsTree}
* @returns {PartialOptionsGroup}
*/
function realized30dChangeTreeFull(r, title) {
return [
{ name: "USD", title: title("Realized P&L 30d Change"), bottom: [baseline({ metric: r.netPnl.delta.absolute._1m.usd, name: "30d Change", unit: Unit.usd })] },
{
name: "% of Mcap",
title: title("Realized 30d Change (% of Mcap)"),
bottom: percentRatioBaseline({ pattern: r.netPnl.change1m.relToMcap, name: "30d Change" }),
},
{
name: "% of Rcap",
title: title("Realized 30d Change (% of Realized Cap)"),
bottom: percentRatioBaseline({ pattern: r.netPnl.change1m.relToRcap, name: "30d Change" }),
},
];
function realizedNetPnlDeltaTreeFull(r, title) {
const base = realizedNetPnlDeltaTree(r.netPnl, title);
return {
...base,
tree: [
...base.tree,
{
name: "% of Mcap",
title: title("Net Realized P&L Change (% of Mcap)"),
bottom: percentRatioBaseline({ pattern: r.netPnl.change1m.relToMcap, name: "30d Change" }),
},
{
name: "% of Rcap",
title: title("Net Realized P&L Change (% of Rcap)"),
bottom: percentRatioBaseline({ pattern: r.netPnl.change1m.relToRcap, name: "30d Change" }),
},
],
};
}
/**
* Rolling net realized P&L tree (reusable by full and mid realized)
* @param {{ sum: { _24h: { usd: AnyMetricPattern }, _1w: { usd: AnyMetricPattern }, _1m: { usd: AnyMetricPattern }, _1y: { usd: AnyMetricPattern } } }} netPnl
* @param {(metric: string) => string} title
* @returns {PartialOptionsGroup}
*/
function rollingNetRealizedTree(netPnl, title) {
return {
name: "Net",
tree: [
{
name: "Compare",
title: title("Rolling Net Realized P&L"),
bottom: ROLLING_WINDOWS.map((w) =>
baseline({ metric: netPnl.sum[w.key].usd, name: w.name, color: w.color, unit: Unit.usd }),
),
},
...ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`Net Realized P&L (${w.name})`),
bottom: [baseline({ metric: netPnl.sum[w.key].usd, name: "Net", unit: Unit.usd })],
})),
],
};
}
/**
@@ -382,23 +531,7 @@ function singleRollingRealizedTreeFull(r, title) {
})),
],
},
{
name: "Net",
tree: [
{
name: "Compare",
title: title("Rolling Net Realized P&L"),
bottom: ROLLING_WINDOWS.map((w) =>
baseline({ metric: r.netPnl.sum[w.key].usd, name: w.name, color: w.color, unit: Unit.usd }),
),
},
...ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`Net Realized P&L (${w.name})`),
bottom: [baseline({ metric: r.netPnl.sum[w.key].usd, name: "Net", unit: Unit.usd })],
})),
],
},
rollingNetRealizedTree(r.netPnl, title),
{
name: "P/L Ratio",
tree: [
@@ -451,6 +584,96 @@ function singleRollingRealizedTreeBasic(profit, loss, title) {
// Realized Subfolder Builders
// ============================================================================
/**
* Value Created/Destroyed tree for a single P&L side (profit or loss)
* @param {CountPattern<number>} valueCreated
* @param {CountPattern<number>} valueDestroyed
* @param {string} label - "Profit" or "Loss"
* @param {(metric: string) => string} title
* @returns {PartialOptionsGroup}
*/
function realizedValueTree(valueCreated, valueDestroyed, label, title) {
return {
name: label,
tree: [
{
name: "Rolling",
tree: [
{
name: "Compare",
title: title(`${label} Value Created vs Destroyed`),
bottom: ROLLING_WINDOWS.flatMap((w) => [
line({ metric: valueCreated.sum[w.key], name: `Created (${w.name})`, color: w.color, unit: Unit.usd }),
line({ metric: valueDestroyed.sum[w.key], name: `Destroyed (${w.name})`, color: w.color, unit: Unit.usd, style: 2 }),
]),
},
...ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`${label} Value (${w.name})`),
bottom: [
line({ metric: valueCreated.sum[w.key], name: "Created", color: colors.profit, unit: Unit.usd }),
line({ metric: valueDestroyed.sum[w.key], name: "Destroyed", color: colors.loss, unit: Unit.usd }),
],
})),
],
},
{
name: "Cumulative",
title: title(`Cumulative ${label} Value`),
bottom: [
line({ metric: valueCreated.cumulative, name: "Created", color: colors.profit, unit: Unit.usd }),
line({ metric: valueDestroyed.cumulative, name: "Destroyed", color: colors.loss, unit: Unit.usd }),
],
},
],
};
}
/**
* Investor price percentiles tree (pct1/2/5/95/98/99)
* @param {InvestorPercentilesPattern} percentiles
* @param {(metric: string) => string} title
* @returns {PartialOptionsGroup}
*/
function investorPricePercentilesTree(percentiles, title) {
/** @type {readonly [InvestorPercentileEntry, string, Color][]} */
const pcts = [
[percentiles.pct99, "p99", colors.stat.max],
[percentiles.pct98, "p98", colors.stat.pct90],
[percentiles.pct95, "p95", colors.stat.pct75],
[percentiles.pct5, "p5", colors.stat.pct25],
[percentiles.pct2, "p2", colors.stat.pct10],
[percentiles.pct1, "p1", colors.stat.min],
];
return {
name: "Percentiles",
tree: [
{
name: "USD",
title: title("Investor Price Percentiles"),
bottom: pcts.map(([p, name, color]) =>
line({ metric: p.price.usd, name, color, unit: Unit.usd }),
),
},
{
name: "Sats",
title: title("Investor Price Percentiles (Sats)"),
bottom: pcts.map(([p, name, color]) =>
line({ metric: p.price.sats, name, color, unit: Unit.sats }),
),
},
{
name: "Ratio",
title: title("Investor Price Percentile Ratios"),
bottom: pcts.map(([p, name, color]) =>
baseline({ metric: p.ratio, name, color, unit: Unit.ratio }),
),
},
],
};
}
/**
* Full realized subfolder (All/STH/LTH)
* @param {Brk.CapGrossInvestorLossMvrvNetPeakPriceProfitSellSoprPattern | Brk.MetricsTree_Cohorts_Utxo_Lth_Realized} r
@@ -463,7 +686,7 @@ function realizedSubfolderFull(r, title) {
tree: [
{ name: "P&L", tree: realizedPnlSumTreeFull(r, title) },
{ name: "Net", tree: realizedNetPnlSumTreeFull(r, title) },
{ name: "30d Change", tree: realized30dChangeTreeFull(r, title) },
realizedNetPnlDeltaTreeFull(r, title),
{
name: "Gross P&L",
tree: [
@@ -488,6 +711,13 @@ function realizedSubfolderFull(r, title) {
{ name: "Cumulative", title: title("Total Realized P&L"), bottom: [line({ metric: r.grossPnl.cumulative.usd, name: "Total", unit: Unit.usd, color: colors.bitcoin })] },
],
},
{
name: "Value",
tree: [
realizedValueTree(r.profit.valueCreated, r.profit.valueDestroyed, "Profit", title),
realizedValueTree(r.loss.valueCreated, r.loss.valueDestroyed, "Loss", title),
],
},
{
name: "P/L Ratio",
title: title("Realized Profit/Loss Ratio"),
@@ -498,6 +728,12 @@ function realizedSubfolderFull(r, title) {
title: title("Realized Peak Regret"),
bottom: [line({ metric: r.peakRegret.base, name: "Peak Regret", unit: Unit.usd })],
},
{
name: "Investor Price",
tree: [
investorPricePercentilesTree(r.investor.price.percentiles, title),
],
},
{ name: "Rolling", tree: singleRollingRealizedTreeFull(r, title) },
{
name: "Cumulative",
@@ -555,12 +791,14 @@ function realizedSubfolderMid(r, title) {
title: title("Net Realized P&L"),
bottom: [dotsBaseline({ metric: r.netPnl.base.usd, name: "Net", unit: Unit.usd })],
},
realizedNetPnlDeltaTree(r.netPnl, title),
{
name: "30d Change",
title: title("Realized P&L 30d Change"),
bottom: [baseline({ metric: r.netPnl.delta.absolute._1m.usd, name: "30d Change", unit: Unit.usd })],
name: "Rolling",
tree: [
...singleRollingRealizedTreeBasic(r.profit, r.loss, title),
rollingNetRealizedTree(r.netPnl, title),
],
},
{ name: "Rolling", tree: singleRollingRealizedTreeBasic(r.profit, r.loss, title) },
{
name: "Cumulative",
tree: [
@@ -620,7 +858,7 @@ function realizedSubfolderBasic(r, title) {
/**
* Basic profitability section (NUPL only unrealized, basic realized)
* @param {{ cohort: UtxoCohortObject | CohortWithoutRelative, title: (metric: string) => string }} args
* @param {{ cohort: UtxoCohortObject, title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createProfitabilitySection({ cohort, title }) {
@@ -639,6 +877,42 @@ export function createProfitabilitySection({ cohort, title }) {
};
}
/**
* Profitability section with unrealized P&L + NUPL (no netPnl, no rel)
* For: CohortWithoutRelative (p2ms, unknown, empty)
* @param {{ cohort: CohortWithoutRelative, title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createProfitabilitySectionWithProfitLoss({ cohort, title }) {
const u = cohort.tree.unrealized;
return {
name: "Profitability",
tree: [
{
name: "Unrealized",
tree: [
{
name: "P&L",
tree: [
{
name: "USD",
title: title("Unrealized P&L"),
bottom: [
...pnlLines({ profit: u.profit.base.usd, loss: u.loss.base.usd, negLoss: u.loss.negative }, Unit.usd),
priceLine({ unit: Unit.usd, defaultActive: false }),
],
},
...unrealizedCumulativeRollingTree(u.profit, u.loss, title),
],
},
{ name: "NUPL", title: title("NUPL"), bottom: nuplSeries(u.nupl) },
],
},
realizedSubfolderBasic(cohort.tree.realized, title),
],
};
}
/**
* Section for All cohort
* @param {{ cohort: CohortAll, title: (metric: string) => string }} args
@@ -764,7 +1038,7 @@ export function createProfitabilitySectionWithInvestedCapitalPct({ cohort, title
{
name: "Unrealized",
tree: [
{ name: "P&L", title: title("Unrealized P&L"), bottom: unrealizedMid(u) },
{ name: "P&L", tree: unrealizedPnlTreeMid(u, title) },
{ name: "Net P&L", title: title("Net Unrealized P&L"), bottom: netUnrealizedMid(u) },
{ name: "NUPL", title: title("NUPL"), bottom: nuplSeries(u.nupl) },
],
@@ -795,6 +1069,41 @@ export function createProfitabilitySectionBasicWithInvestedCapitalPct({ cohort,
};
}
/**
* Section for CohortAddress (has unrealized profit/loss + NUPL, basic realized)
* @param {{ cohort: CohortAddress, title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createProfitabilitySectionAddress({ cohort, title }) {
const u = cohort.tree.unrealized;
return {
name: "Profitability",
tree: [
{
name: "Unrealized",
tree: [
{
name: "P&L",
tree: [
{
name: "USD",
title: title("Unrealized P&L"),
bottom: [
...pnlLines({ profit: u.profit.base.usd, loss: u.loss.base.usd, negLoss: u.loss.negative }, Unit.usd),
priceLine({ unit: Unit.usd, defaultActive: false }),
],
},
...unrealizedCumulativeRollingTree(u.profit, u.loss, title),
],
},
{ name: "NUPL", title: title("NUPL"), bottom: nuplSeries(u.nupl) },
],
},
realizedSubfolderBasic(cohort.tree.realized, title),
],
};
}
// ============================================================================
// Grouped Cohort Helpers
// ============================================================================
@@ -945,6 +1254,63 @@ function groupedRealizedSubfolder(list, all, title) {
};
}
/**
* Grouped net realized P&L delta (Absolute + Rate with all rolling windows)
* @param {readonly (CohortAll | CohortFull | CohortLongTerm)[]} list
* @param {CohortAll} all
* @param {(metric: string) => string} title
* @returns {PartialOptionsGroup}
*/
function groupedRealizedNetPnlDeltaTree(list, all, title) {
return {
name: "Change",
tree: [
{
name: "Absolute",
tree: [
{
name: "Compare",
title: title("Net Realized P&L Change"),
bottom: ROLLING_WINDOWS.flatMap((w) =>
mapCohortsWithAll(list, all, ({ name, tree }) =>
baseline({ metric: tree.realized.netPnl.delta.absolute[w.key].usd, name: `${name} (${w.name})`, color: w.color, unit: Unit.usd }),
),
),
},
...ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`Net Realized P&L Change (${w.name})`),
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
baseline({ metric: tree.realized.netPnl.delta.absolute[w.key].usd, name, color, unit: Unit.usd }),
),
})),
],
},
{
name: "Rate",
tree: [
{
name: "Compare",
title: title("Net Realized P&L Rate"),
bottom: ROLLING_WINDOWS.flatMap((w) =>
flatMapCohortsWithAll(list, all, ({ name, tree }) =>
percentRatio({ pattern: tree.realized.netPnl.delta.rate[w.key], name: `${name} (${w.name})`, color: w.color }),
),
),
},
...ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`Net Realized P&L Rate (${w.name})`),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
percentRatio({ pattern: tree.realized.netPnl.delta.rate[w.key], name, color }),
),
})),
],
},
],
};
}
/**
* Grouped realized subfolder for full cohorts
* @param {readonly (CohortAll | CohortFull | CohortLongTerm)[]} list
@@ -964,13 +1330,7 @@ function groupedRealizedSubfolderFull(list, all, title) {
baseline({ metric: tree.realized.netPnl.base.usd, name, color, unit: Unit.usd }),
),
},
{
name: "30d Change",
title: title("Realized P&L 30d Change"),
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
baseline({ metric: tree.realized.netPnl.delta.absolute._1m.usd, name, color, unit: Unit.usd }),
),
},
groupedRealizedNetPnlDeltaTree(list, all, title),
{ name: "Rolling", tree: groupedRollingRealizedChartsFull(list, all, title) },
{
name: "Cumulative",
@@ -1265,6 +1625,41 @@ export function createGroupedProfitabilitySection({ list, all, title }) {
};
}
/**
* Grouped profitability with unrealized profit/loss + NUPL
* For: CohortWithoutRelative (p2ms, unknown, empty)
* @param {{ list: readonly CohortWithoutRelative[], all: CohortAll, title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createGroupedProfitabilitySectionWithProfitLoss({ list, all, title }) {
return {
name: "Profitability",
tree: [
{
name: "Unrealized",
tree: [
{
name: "Profit",
title: title("Unrealized Profit"),
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
line({ metric: tree.unrealized.profit.base.usd, name, color, unit: Unit.usd }),
),
},
{
name: "Loss",
title: title("Unrealized Loss"),
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
line({ metric: tree.unrealized.loss.base.usd, name, color, unit: Unit.usd }),
),
},
...groupedNuplCharts(list, all, title),
],
},
groupedRealizedSubfolder(list, all, title),
],
};
}
/**
* Grouped section with invested capital % (basic cohorts — uses NUPL only)
* @param {{ list: readonly CohortBasicWithoutMarketCap[], all: CohortAll, title: (metric: string) => string }} args

View File

@@ -0,0 +1,89 @@
/** UTXO Profitability section — range bands, cumulative profit/loss thresholds */
import { colors } from "../../utils/colors.js";
import { entries } from "../../utils/array.js";
import { Unit } from "../../utils/units.js";
import { line, price } from "../series.js";
import { brk } from "../../client.js";
import { satsBtcUsd } from "../shared.js";
/**
* @param {{ name: string, color: Color, pattern: RealizedSupplyPattern }[]} list
* @param {string} titlePrefix
* @returns {PartialOptionsTree}
*/
function bucketCharts(list, titlePrefix) {
return [
{
name: "Supply",
title: `${titlePrefix}: Supply`,
bottom: list.flatMap(({ name, color, pattern }) =>
satsBtcUsd({ pattern: pattern.supply, name, color }),
),
},
{
name: "Realized Cap",
title: `${titlePrefix}: Realized Cap`,
bottom: list.map(({ name, color, pattern }) =>
line({ metric: pattern.realizedCap, name, color, unit: Unit.usd }),
),
},
{
name: "Realized Price",
title: `${titlePrefix}: Realized Price`,
top: list.map(({ name, color, pattern }) =>
price({ metric: pattern.realizedPrice, name, color }),
),
},
];
}
/**
* @returns {PartialOptionsGroup}
*/
export function createUtxoProfitabilitySection() {
const { range, profit, loss } = brk.metrics.cohorts.utxo.profitability;
const {
PROFITABILITY_RANGE_NAMES,
PROFIT_NAMES,
LOSS_NAMES,
} = brk;
const rangeList = entries(PROFITABILITY_RANGE_NAMES).map(
([key, names], i, arr) => ({
name: names.short,
color: colors.at(i, arr.length),
pattern: range[key],
}),
);
const profitList = entries(PROFIT_NAMES).map(([key, names], i, arr) => ({
name: names.short,
color: colors.at(i, arr.length),
pattern: profit[key],
}));
const lossList = entries(LOSS_NAMES).map(([key, names], i, arr) => ({
name: names.short,
color: colors.at(i, arr.length),
pattern: loss[key],
}));
return {
name: "UTXO Profitability",
tree: [
{
name: "Range",
tree: bucketCharts(rangeList, "Profitability Range"),
},
{
name: "In Profit",
tree: bucketCharts(profitList, "In Profit"),
},
{
name: "In Loss",
tree: bucketCharts(lossList, "In Loss"),
},
],
};
}

View File

@@ -36,6 +36,18 @@ import { periodIdToName } from "./utils.js";
* @property {Brk.BpsCentsRatioSatsUsdPattern} ratio
*/
/**
* Create index (percent) + ratio line pair from a BpsPercentRatioPattern
* @param {{ pattern: { percent: AnyMetricPattern, ratio: AnyMetricPattern }, name: string, color?: Color, defaultActive?: boolean }} args
* @returns {AnyFetchedSeriesBlueprint[]}
*/
function indexRatio({ pattern, name, color, defaultActive }) {
return [
line({ metric: pattern.percent, name, color, defaultActive, unit: Unit.index }),
line({ metric: pattern.ratio, name, color, defaultActive, unit: Unit.ratio }),
];
}
const commonMaIds = /** @type {const} */ ([
"1w",
"1m",
@@ -671,166 +683,43 @@ export function createMarketSection() {
name: "Compare",
title: "RSI Comparison",
bottom: [
line({
metric: technical.rsi._24h.rsi.percent,
name: "1d",
color: colors.time._24h,
unit: Unit.index,
}),
line({
metric: technical.rsi._1w.rsi.percent,
name: "1w",
color: colors.time._1w,
unit: Unit.index,
}),
line({
metric: technical.rsi._1m.rsi.percent,
name: "1m",
color: colors.time._1m,
unit: Unit.index,
}),
line({
metric: technical.rsi._1y.rsi.percent,
name: "1y",
color: colors.time._1y,
unit: Unit.index,
}),
...ROLLING_WINDOWS.flatMap((w) =>
indexRatio({ pattern: technical.rsi[w.key].rsi, name: w.name, color: w.color }),
),
priceLine({ unit: Unit.index, number: 70 }),
priceLine({ unit: Unit.index, number: 30 }),
],
},
{
name: "1 Day",
title: "RSI (1d)",
bottom: [
line({
metric: technical.rsi._24h.rsi.percent,
name: "RSI",
color: colors.indicator.main,
unit: Unit.index,
}),
line({
metric: technical.rsi._24h.rsiMax.percent,
name: "Max",
color: colors.stat.max,
defaultActive: false,
unit: Unit.index,
}),
line({
metric: technical.rsi._24h.rsiMin.percent,
name: "Min",
color: colors.stat.min,
defaultActive: false,
unit: Unit.index,
}),
priceLine({ unit: Unit.index, number: 70 }),
priceLine({
unit: Unit.index,
number: 50,
defaultActive: false,
}),
priceLine({ unit: Unit.index, number: 30 }),
],
},
{
name: "1 Week",
title: "RSI (1w)",
bottom: [
line({
metric: technical.rsi._1w.rsi.percent,
name: "RSI",
color: colors.indicator.main,
unit: Unit.index,
}),
line({
metric: technical.rsi._1w.rsiMax.percent,
name: "Max",
color: colors.stat.max,
defaultActive: false,
unit: Unit.index,
}),
line({
metric: technical.rsi._1w.rsiMin.percent,
name: "Min",
color: colors.stat.min,
defaultActive: false,
unit: Unit.index,
}),
priceLine({ unit: Unit.index, number: 70 }),
priceLine({
unit: Unit.index,
number: 50,
defaultActive: false,
}),
priceLine({ unit: Unit.index, number: 30 }),
],
},
{
name: "1 Month",
title: "RSI (1m)",
bottom: [
line({
metric: technical.rsi._1m.rsi.percent,
name: "RSI",
color: colors.indicator.main,
unit: Unit.index,
}),
line({
metric: technical.rsi._1m.rsiMax.percent,
name: "Max",
color: colors.stat.max,
defaultActive: false,
unit: Unit.index,
}),
line({
metric: technical.rsi._1m.rsiMin.percent,
name: "Min",
color: colors.stat.min,
defaultActive: false,
unit: Unit.index,
}),
priceLine({ unit: Unit.index, number: 70 }),
priceLine({
unit: Unit.index,
number: 50,
defaultActive: false,
}),
priceLine({ unit: Unit.index, number: 30 }),
],
},
{
name: "1 Year",
title: "RSI (1y)",
bottom: [
line({
metric: technical.rsi._1y.rsi.percent,
name: "RSI",
color: colors.indicator.main,
unit: Unit.index,
}),
line({
metric: technical.rsi._1y.rsiMax.percent,
name: "Max",
color: colors.stat.max,
defaultActive: false,
unit: Unit.index,
}),
line({
metric: technical.rsi._1y.rsiMin.percent,
name: "Min",
color: colors.stat.min,
defaultActive: false,
unit: Unit.index,
}),
priceLine({ unit: Unit.index, number: 70 }),
priceLine({
unit: Unit.index,
number: 50,
defaultActive: false,
}),
priceLine({ unit: Unit.index, number: 30 }),
],
},
...ROLLING_WINDOWS.map((w) => {
const rsi = technical.rsi[w.key];
return {
name: w.name,
tree: [
{
name: "Value",
title: `RSI (${w.name})`,
bottom: [
...indexRatio({ pattern: rsi.rsi, name: "RSI", color: colors.indicator.main }),
...indexRatio({ pattern: rsi.rsiMax, name: "Max", color: colors.stat.max, defaultActive: false }),
...indexRatio({ pattern: rsi.rsiMin, name: "Min", color: colors.stat.min, defaultActive: false }),
priceLine({ unit: Unit.index, number: 70 }),
priceLine({ unit: Unit.index, number: 50, defaultActive: false }),
priceLine({ unit: Unit.index, number: 30 }),
],
},
{
name: "Components",
title: `RSI Components (${w.name})`,
bottom: [
line({ metric: rsi.averageGain, name: "Avg Gain", color: colors.profit, unit: Unit.usd }),
line({ metric: rsi.averageLoss, name: "Avg Loss", color: colors.loss, unit: Unit.usd }),
line({ metric: rsi.gains, name: "Gains", color: colors.profit, defaultActive: false, unit: Unit.usd }),
line({ metric: rsi.losses, name: "Losses", color: colors.loss, defaultActive: false, unit: Unit.usd }),
],
},
],
};
}),
],
},
{
@@ -840,127 +729,33 @@ export function createMarketSection() {
name: "Compare",
title: "Stochastic RSI Comparison",
bottom: [
line({
metric: technical.rsi._24h.stochRsiK.percent,
name: "1d K",
color: colors.time._24h,
unit: Unit.index,
}),
line({
metric: technical.rsi._1w.stochRsiK.percent,
name: "1w K",
color: colors.time._1w,
unit: Unit.index,
}),
line({
metric: technical.rsi._1m.stochRsiK.percent,
name: "1m K",
color: colors.time._1m,
unit: Unit.index,
}),
line({
metric: technical.rsi._1y.stochRsiK.percent,
name: "1y K",
color: colors.time._1y,
unit: Unit.index,
}),
...priceLines({ unit: Unit.index, numbers: [80, 20] }),
],
},
{
name: "1 Day",
title: "Stochastic RSI (1d)",
bottom: [
line({
metric: technical.rsi._24h.stochRsiK.percent,
name: "K",
color: colors.indicator.fast,
unit: Unit.index,
}),
line({
metric: technical.rsi._24h.stochRsiD.percent,
name: "D",
color: colors.indicator.slow,
unit: Unit.index,
}),
...priceLines({ unit: Unit.index, numbers: [80, 20] }),
],
},
{
name: "1 Week",
title: "Stochastic RSI (1w)",
bottom: [
line({
metric: technical.rsi._1w.stochRsiK.percent,
name: "K",
color: colors.indicator.fast,
unit: Unit.index,
}),
line({
metric: technical.rsi._1w.stochRsiD.percent,
name: "D",
color: colors.indicator.slow,
unit: Unit.index,
}),
...priceLines({ unit: Unit.index, numbers: [80, 20] }),
],
},
{
name: "1 Month",
title: "Stochastic RSI (1m)",
bottom: [
line({
metric: technical.rsi._1m.stochRsiK.percent,
name: "K",
color: colors.indicator.fast,
unit: Unit.index,
}),
line({
metric: technical.rsi._1m.stochRsiD.percent,
name: "D",
color: colors.indicator.slow,
unit: Unit.index,
}),
...priceLines({ unit: Unit.index, numbers: [80, 20] }),
],
},
{
name: "1 Year",
title: "Stochastic RSI (1y)",
bottom: [
line({
metric: technical.rsi._1y.stochRsiK.percent,
name: "K",
color: colors.indicator.fast,
unit: Unit.index,
}),
line({
metric: technical.rsi._1y.stochRsiD.percent,
name: "D",
color: colors.indicator.slow,
unit: Unit.index,
}),
...ROLLING_WINDOWS.flatMap((w) =>
indexRatio({ pattern: technical.rsi[w.key].stochRsiK, name: `${w.name} K`, color: w.color }),
),
...priceLines({ unit: Unit.index, numbers: [80, 20] }),
],
},
...ROLLING_WINDOWS.map((w) => {
const rsi = technical.rsi[w.key];
return {
name: w.name,
title: `Stochastic RSI (${w.name})`,
bottom: [
...indexRatio({ pattern: rsi.stochRsi, name: "Raw", color: colors.indicator.main, defaultActive: false }),
...indexRatio({ pattern: rsi.stochRsiK, name: "K", color: colors.indicator.fast }),
...indexRatio({ pattern: rsi.stochRsiD, name: "D", color: colors.indicator.slow }),
...priceLines({ unit: Unit.index, numbers: [80, 20] }),
],
};
}),
],
},
{
name: "Stochastic",
title: "Stochastic Oscillator",
bottom: [
line({
metric: technical.stochK.percent,
name: "K",
color: colors.indicator.fast,
unit: Unit.index,
}),
line({
metric: technical.stochD.percent,
name: "D",
color: colors.indicator.slow,
unit: Unit.index,
}),
...indexRatio({ pattern: technical.stochK, name: "K", color: colors.indicator.fast }),
...indexRatio({ pattern: technical.stochD, name: "D", color: colors.indicator.slow }),
...priceLines({ unit: Unit.index, numbers: [80, 20] }),
],
},
@@ -970,32 +765,9 @@ export function createMarketSection() {
{
name: "Compare",
title: "MACD Comparison",
bottom: [
line({
metric: technical.macd._24h.line,
name: "1d",
color: colors.time._24h,
unit: Unit.usd,
}),
line({
metric: technical.macd._1w.line,
name: "1w",
color: colors.time._1w,
unit: Unit.usd,
}),
line({
metric: technical.macd._1m.line,
name: "1m",
color: colors.time._1m,
unit: Unit.usd,
}),
line({
metric: technical.macd._1y.line,
name: "1y",
color: colors.time._1y,
unit: Unit.usd,
}),
],
bottom: ROLLING_WINDOWS.map((w) =>
line({ metric: technical.macd[w.key].line, name: w.name, color: w.color, unit: Unit.usd }),
),
},
...ROLLING_WINDOWS.map((w) => ({
name: w.name,

View File

@@ -118,7 +118,6 @@ export function createNetworkSection() {
},
]);
const countTypes = /** @type {const} */ ([
{
name: "Funded",
@@ -568,7 +567,7 @@ export function createNetworkSection() {
name: "Base",
title: "OP_RETURN Burned",
bottom: satsBtcUsd({
pattern: supply.burned.opReturn.base,
pattern: scripts.value.opReturn.base,
name: "sum",
}),
},
@@ -580,7 +579,7 @@ export function createNetworkSection() {
title: "OP_RETURN Burned Rolling",
bottom: ROLLING_WINDOWS.flatMap((w) =>
satsBtcUsd({
pattern: supply.burned.opReturn.sum[w.key],
pattern: scripts.value.opReturn.sum[w.key],
name: w.name,
color: w.color,
}),
@@ -590,7 +589,7 @@ export function createNetworkSection() {
name: w.name,
title: `OP_RETURN Burned ${w.name}`,
bottom: satsBtcUsd({
pattern: supply.burned.opReturn.sum[w.key],
pattern: scripts.value.opReturn.sum[w.key],
name: w.name,
color: w.color,
}),
@@ -601,7 +600,7 @@ export function createNetworkSection() {
name: "Cumulative",
title: "OP_RETURN Burned (Total)",
bottom: satsBtcUsd({
pattern: supply.burned.opReturn.cumulative,
pattern: scripts.value.opReturn.cumulative,
name: "all-time",
}),
},
@@ -1074,7 +1073,8 @@ export function createNetworkSection() {
title: "UTXO Count 30d Change",
bottom: [
baseline({
metric: cohorts.utxo.all.outputs.unspentCount.delta.absolute._1m,
metric:
cohorts.utxo.all.outputs.unspentCount.delta.absolute._1m,
name: "30d Change",
unit: Unit.count,
}),

View File

@@ -6,7 +6,7 @@ import {
createCohortFolderFull,
createCohortFolderWithAdjusted,
createCohortFolderLongTerm,
createCohortFolderAgeRange,
createCohortFolderAgeRangeWithMatured,
createCohortFolderBasicWithMarketCap,
createCohortFolderBasicWithoutMarketCap,
createCohortFolderWithoutRelative,
@@ -14,12 +14,13 @@ import {
createAddressCohortFolder,
createGroupedCohortFolderWithAdjusted,
createGroupedCohortFolderWithNupl,
createGroupedCohortFolderAgeRange,
createGroupedCohortFolderAgeRangeWithMatured,
createGroupedCohortFolderBasicWithMarketCap,
createGroupedCohortFolderBasicWithoutMarketCap,
createGroupedCohortFolderAddress,
createGroupedAddressCohortFolder,
} from "./distribution/index.js";
import { createUtxoProfitabilitySection } from "./distribution/utxo-profitability.js";
import { createMarketSection } from "./market.js";
import { createNetworkSection } from "./network.js";
import { createMiningSection } from "./mining.js";
@@ -110,26 +111,26 @@ export function createPartialOptions() {
{
name: "Older Than",
tree: [
createGroupedCohortFolderBasicWithMarketCap({
createGroupedCohortFolderWithAdjusted({
name: "Compare",
title: "Over Age",
list: overAge,
all: cohortAll,
}),
...overAge.map(createCohortFolderBasicWithMarketCap),
...overAge.map(createCohortFolderWithAdjusted),
],
},
// Range
{
name: "Range",
tree: [
createGroupedCohortFolderAgeRange({
createGroupedCohortFolderAgeRangeWithMatured({
name: "Compare",
title: "Age Ranges",
list: ageRange,
all: cohortAll,
}),
...ageRange.map(createCohortFolderAgeRange),
...ageRange.map(createCohortFolderAgeRangeWithMatured),
],
},
],
@@ -246,13 +247,13 @@ export function createPartialOptions() {
{
name: "Epochs",
tree: [
createGroupedCohortFolderBasicWithoutMarketCap({
createGroupedCohortFolderWithAdjusted({
name: "Compare",
title: "Epochs",
list: epoch,
all: cohortAll,
}),
...epoch.map(createCohortFolderBasicWithoutMarketCap),
...epoch.map(createCohortFolderWithAdjusted),
],
},
@@ -260,15 +261,18 @@ export function createPartialOptions() {
{
name: "Years",
tree: [
createGroupedCohortFolderBasicWithoutMarketCap({
createGroupedCohortFolderWithAdjusted({
name: "Compare",
title: "Years",
list: class_,
all: cohortAll,
}),
...class_.map(createCohortFolderBasicWithoutMarketCap),
...class_.map(createCohortFolderWithAdjusted),
],
},
// UTXO Profitability bands
createUtxoProfitabilitySection(),
],
},

View File

@@ -233,6 +233,9 @@
* @property {Color} color
* @property {AgeRangePattern} tree
*
* Age range cohort with matured supply
* @typedef {CohortAgeRange & { matured: AnyValuePattern }} CohortAgeRangeWithMatured
*
* Basic cohort WITH RelToMarketCap (geAmount.*, ltAmount.*)
* @typedef {Object} CohortBasicWithMarketCap
* @property {string} name

View File

@@ -12,7 +12,7 @@
*
* @import { Color } from "./utils/colors.js"
*
* @import { Option, PartialChartOption, ChartOption, AnyPartialOption, ProcessedOptionAddons, OptionsTree, SimulationOption, AnySeriesBlueprint, SeriesType, AnyFetchedSeriesBlueprint, TableOption, ExplorerOption, UrlOption, PartialOptionsGroup, OptionsGroup, PartialOptionsTree, UtxoCohortObject, AddressCohortObject, CohortObject, CohortGroupObject, FetchedLineSeriesBlueprint, FetchedBaselineSeriesBlueprint, FetchedHistogramSeriesBlueprint, FetchedDotsBaselineSeriesBlueprint, PatternAll, PatternFull, PatternWithAdjusted, PatternWithPercentiles, PatternBasic, PatternBasicWithMarketCap, PatternBasicWithoutMarketCap, PatternWithoutRelative, CohortAll, CohortFull, CohortWithAdjusted, CohortWithPercentiles, CohortBasic, CohortBasicWithMarketCap, CohortBasicWithoutMarketCap, CohortWithoutRelative, CohortAddress, CohortLongTerm, CohortAgeRange, CohortGroupFull, CohortGroupWithAdjusted, CohortGroupWithPercentiles, CohortGroupLongTerm, CohortGroupAgeRange, CohortGroupBasic, CohortGroupBasicWithMarketCap, CohortGroupBasicWithoutMarketCap, CohortGroupWithoutRelative, CohortGroupAddress, UtxoCohortGroupObject, AddressCohortGroupObject, FetchedDotsSeriesBlueprint, FetchedCandlestickSeriesBlueprint, FetchedPriceSeriesBlueprint, AnyPricePattern, AnyValuePattern } from "./options/partial.js"
* @import { Option, PartialChartOption, ChartOption, AnyPartialOption, ProcessedOptionAddons, OptionsTree, SimulationOption, AnySeriesBlueprint, SeriesType, AnyFetchedSeriesBlueprint, TableOption, ExplorerOption, UrlOption, PartialOptionsGroup, OptionsGroup, PartialOptionsTree, UtxoCohortObject, AddressCohortObject, CohortObject, CohortGroupObject, FetchedLineSeriesBlueprint, FetchedBaselineSeriesBlueprint, FetchedHistogramSeriesBlueprint, FetchedDotsBaselineSeriesBlueprint, PatternAll, PatternFull, PatternWithAdjusted, PatternWithPercentiles, PatternBasic, PatternBasicWithMarketCap, PatternBasicWithoutMarketCap, PatternWithoutRelative, CohortAll, CohortFull, CohortWithAdjusted, CohortWithPercentiles, CohortBasic, CohortBasicWithMarketCap, CohortBasicWithoutMarketCap, CohortWithoutRelative, CohortAddress, CohortLongTerm, CohortAgeRange, CohortAgeRangeWithMatured, CohortGroupFull, CohortGroupWithAdjusted, CohortGroupWithPercentiles, CohortGroupLongTerm, CohortGroupAgeRange, CohortGroupBasic, CohortGroupBasicWithMarketCap, CohortGroupBasicWithoutMarketCap, CohortGroupWithoutRelative, CohortGroupAddress, UtxoCohortGroupObject, AddressCohortGroupObject, FetchedDotsSeriesBlueprint, FetchedCandlestickSeriesBlueprint, FetchedPriceSeriesBlueprint, AnyPricePattern, AnyValuePattern } from "./options/partial.js"
*
*
* @import { UnitObject as Unit } from "./utils/units.js"
@@ -49,7 +49,7 @@
* @typedef {Brk.AddressOutputsRealizedSupplyUnrealizedPattern} AddressAmountPattern
* @typedef {Brk.ActivityOutputsRealizedSupplyUnrealizedPattern} BasicUtxoPattern
* @typedef {Brk.ActivityOutputsRealizedSupplyUnrealizedPattern} EpochPattern
* @typedef {Brk.OutputsRealizedSupplyUnrealizedPattern} EmptyPattern
* @typedef {Brk.OutputsRealizedSupplyUnrealizedPattern2} EmptyPattern
* @typedef {Brk._0sdM0M1M1sdM2M2sdM3sdP0P1P1sdP2P2sdP3sdSdZscorePattern} Ratio1ySdPattern
* @typedef {Brk.Dollars} Dollars
* CoinbasePattern: base + cumulative + rolling windows (flattened)
@@ -82,6 +82,9 @@
* @typedef {Brk.GrossInvestedLossNetNuplProfitSentimentPattern2} FullRelativePattern
* @typedef {Brk.GrossInvestedLossNetNuplProfitSentimentPattern2} UnrealizedPattern
*
* Profitability bucket pattern
* @typedef {Brk.RealizedSupplyPattern} RealizedSupplyPattern
*
* Realized patterns
* @typedef {Brk.CapGrossInvestorLossMvrvNetPeakPriceProfitSellSoprPattern} RealizedPattern
* @typedef {Brk.CapGrossInvestorLossMvrvNetPeakPriceProfitSellSoprPattern} RealizedPattern2
@@ -204,6 +207,14 @@
* All cohorts with circulating supply relative metrics
* @typedef {UtxoCohortWithCirculatingSupplyRelative | AddressCohortWithCirculatingSupplyRelative} CohortWithCirculatingSupplyRelative
*
* Delta patterns with absolute + rate rolling windows
* @typedef {Brk.AbsoluteRatePattern} DeltaPattern
* @typedef {Brk.AbsoluteRatePattern2} FiatDeltaPattern
*
* Investor price percentiles (pct1/2/5/95/98/99)
* @typedef {Brk.Pct1Pct2Pct5Pct95Pct98Pct99Pattern} InvestorPercentilesPattern
* @typedef {Brk.BpsPriceRatioPattern} InvestorPercentileEntry
*
* Generic tree node type for walking
* @typedef {AnyMetricPattern | Record<string, unknown>} TreeNode
*

View File

@@ -19,11 +19,6 @@ export const Unit = /** @type {const} */ ({
// Relative percentages
pctSupply: { id: "pct-supply", name: "% of circulating" },
pctOwn: { id: "pct-own", name: "% of Own" },
pctMcap: { id: "pct-mcap", name: "% of Market Cap" },
pctRcap: { id: "pct-rcap", name: "% of Realized Cap" },
pctOwnRcap: { id: "pct-own-rcap", name: "% of Own Realized Cap" },
pctOwnMcap: { id: "pct-own-mcap", name: "% of Own Market Cap" },
pctOwnPnl: { id: "pct-own-pnl", name: "% of Own P&L" },
// Time
days: { id: "days", name: "Days" },