diff --git a/crates/brk_client/src/lib.rs b/crates/brk_client/src/lib.rs index 88aed410c..764e369a7 100644 --- a/crates/brk_client/src/lib.rs +++ b/crates/brk_client/src/lib.rs @@ -1532,6 +1532,42 @@ impl PeriodLumpSumStackPattern { } } +/// Pattern struct for repeated tree structure. +pub struct ClassAveragePricePattern { + pub _2015: MetricPattern4, + pub _2016: MetricPattern4, + pub _2017: MetricPattern4, + pub _2018: MetricPattern4, + pub _2019: MetricPattern4, + pub _2020: MetricPattern4, + pub _2021: MetricPattern4, + pub _2022: MetricPattern4, + pub _2023: MetricPattern4, + pub _2024: MetricPattern4, + pub _2025: MetricPattern4, + pub _2026: MetricPattern4, +} + +impl ClassAveragePricePattern { + /// Create a new pattern node with accumulated metric name. + pub fn new(client: Arc, acc: String) -> Self { + Self { + _2015: MetricPattern4::new(client.clone(), _m(&acc, "2015_returns")), + _2016: MetricPattern4::new(client.clone(), _m(&acc, "2016_returns")), + _2017: MetricPattern4::new(client.clone(), _m(&acc, "2017_returns")), + _2018: MetricPattern4::new(client.clone(), _m(&acc, "2018_returns")), + _2019: MetricPattern4::new(client.clone(), _m(&acc, "2019_returns")), + _2020: MetricPattern4::new(client.clone(), _m(&acc, "2020_returns")), + _2021: MetricPattern4::new(client.clone(), _m(&acc, "2021_returns")), + _2022: MetricPattern4::new(client.clone(), _m(&acc, "2022_returns")), + _2023: MetricPattern4::new(client.clone(), _m(&acc, "2023_returns")), + _2024: MetricPattern4::new(client.clone(), _m(&acc, "2024_returns")), + _2025: MetricPattern4::new(client.clone(), _m(&acc, "2025_returns")), + _2026: MetricPattern4::new(client.clone(), _m(&acc, "2026_returns")), + } + } +} + /// Pattern struct for repeated tree structure. pub struct PeriodAveragePricePattern { pub _10y: MetricPattern4, @@ -1568,42 +1604,6 @@ impl PeriodAveragePricePattern { } } -/// Pattern struct for repeated tree structure. -pub struct ClassAveragePricePattern { - pub _2015: MetricPattern4, - pub _2016: MetricPattern4, - pub _2017: MetricPattern4, - pub _2018: MetricPattern4, - pub _2019: MetricPattern4, - pub _2020: MetricPattern4, - pub _2021: MetricPattern4, - pub _2022: MetricPattern4, - pub _2023: MetricPattern4, - pub _2024: MetricPattern4, - pub _2025: MetricPattern4, - pub _2026: MetricPattern4, -} - -impl ClassAveragePricePattern { - /// Create a new pattern node with accumulated metric name. - pub fn new(client: Arc, acc: String) -> Self { - Self { - _2015: MetricPattern4::new(client.clone(), _m(&acc, "2015_average_price")), - _2016: MetricPattern4::new(client.clone(), _m(&acc, "2016_average_price")), - _2017: MetricPattern4::new(client.clone(), _m(&acc, "2017_average_price")), - _2018: MetricPattern4::new(client.clone(), _m(&acc, "2018_average_price")), - _2019: MetricPattern4::new(client.clone(), _m(&acc, "2019_average_price")), - _2020: MetricPattern4::new(client.clone(), _m(&acc, "2020_average_price")), - _2021: MetricPattern4::new(client.clone(), _m(&acc, "2021_average_price")), - _2022: MetricPattern4::new(client.clone(), _m(&acc, "2022_average_price")), - _2023: MetricPattern4::new(client.clone(), _m(&acc, "2023_average_price")), - _2024: MetricPattern4::new(client.clone(), _m(&acc, "2024_average_price")), - _2025: MetricPattern4::new(client.clone(), _m(&acc, "2025_average_price")), - _2026: MetricPattern4::new(client.clone(), _m(&acc, "2026_average_price")), - } - } -} - /// Pattern struct for repeated tree structure. pub struct BitcoinPattern { pub average: MetricPattern2, @@ -1672,38 +1672,6 @@ impl DollarsPattern { } } -/// Pattern struct for repeated tree structure. -pub struct RelativePattern2 { - pub neg_unrealized_loss_rel_to_own_market_cap: MetricPattern1, - pub neg_unrealized_loss_rel_to_own_total_unrealized_pnl: MetricPattern1, - pub net_unrealized_pnl_rel_to_own_market_cap: MetricPattern1, - pub net_unrealized_pnl_rel_to_own_total_unrealized_pnl: MetricPattern1, - pub supply_in_loss_rel_to_own_supply: MetricPattern1, - pub supply_in_profit_rel_to_own_supply: MetricPattern1, - pub unrealized_loss_rel_to_own_market_cap: MetricPattern1, - pub unrealized_loss_rel_to_own_total_unrealized_pnl: MetricPattern1, - pub unrealized_profit_rel_to_own_market_cap: MetricPattern1, - pub unrealized_profit_rel_to_own_total_unrealized_pnl: MetricPattern1, -} - -impl RelativePattern2 { - /// Create a new pattern node with accumulated metric name. - pub fn new(client: Arc, acc: String) -> Self { - Self { - neg_unrealized_loss_rel_to_own_market_cap: MetricPattern1::new(client.clone(), _m(&acc, "neg_unrealized_loss_rel_to_own_market_cap")), - neg_unrealized_loss_rel_to_own_total_unrealized_pnl: MetricPattern1::new(client.clone(), _m(&acc, "neg_unrealized_loss_rel_to_own_total_unrealized_pnl")), - net_unrealized_pnl_rel_to_own_market_cap: MetricPattern1::new(client.clone(), _m(&acc, "net_unrealized_pnl_rel_to_own_market_cap")), - net_unrealized_pnl_rel_to_own_total_unrealized_pnl: MetricPattern1::new(client.clone(), _m(&acc, "net_unrealized_pnl_rel_to_own_total_unrealized_pnl")), - supply_in_loss_rel_to_own_supply: MetricPattern1::new(client.clone(), _m(&acc, "supply_in_loss_rel_to_own_supply")), - supply_in_profit_rel_to_own_supply: MetricPattern1::new(client.clone(), _m(&acc, "supply_in_profit_rel_to_own_supply")), - unrealized_loss_rel_to_own_market_cap: MetricPattern1::new(client.clone(), _m(&acc, "unrealized_loss_rel_to_own_market_cap")), - unrealized_loss_rel_to_own_total_unrealized_pnl: MetricPattern1::new(client.clone(), _m(&acc, "unrealized_loss_rel_to_own_total_unrealized_pnl")), - unrealized_profit_rel_to_own_market_cap: MetricPattern1::new(client.clone(), _m(&acc, "unrealized_profit_rel_to_own_market_cap")), - unrealized_profit_rel_to_own_total_unrealized_pnl: MetricPattern1::new(client.clone(), _m(&acc, "unrealized_profit_rel_to_own_total_unrealized_pnl")), - } - } -} - /// Pattern struct for repeated tree structure. pub struct RelativePattern { pub neg_unrealized_loss_rel_to_market_cap: MetricPattern1, @@ -1736,6 +1704,38 @@ impl RelativePattern { } } +/// Pattern struct for repeated tree structure. +pub struct RelativePattern2 { + pub neg_unrealized_loss_rel_to_own_market_cap: MetricPattern1, + pub neg_unrealized_loss_rel_to_own_total_unrealized_pnl: MetricPattern1, + pub net_unrealized_pnl_rel_to_own_market_cap: MetricPattern1, + pub net_unrealized_pnl_rel_to_own_total_unrealized_pnl: MetricPattern1, + pub supply_in_loss_rel_to_own_supply: MetricPattern1, + pub supply_in_profit_rel_to_own_supply: MetricPattern1, + pub unrealized_loss_rel_to_own_market_cap: MetricPattern1, + pub unrealized_loss_rel_to_own_total_unrealized_pnl: MetricPattern1, + pub unrealized_profit_rel_to_own_market_cap: MetricPattern1, + pub unrealized_profit_rel_to_own_total_unrealized_pnl: MetricPattern1, +} + +impl RelativePattern2 { + /// Create a new pattern node with accumulated metric name. + pub fn new(client: Arc, acc: String) -> Self { + Self { + neg_unrealized_loss_rel_to_own_market_cap: MetricPattern1::new(client.clone(), _m(&acc, "neg_unrealized_loss_rel_to_own_market_cap")), + neg_unrealized_loss_rel_to_own_total_unrealized_pnl: MetricPattern1::new(client.clone(), _m(&acc, "neg_unrealized_loss_rel_to_own_total_unrealized_pnl")), + net_unrealized_pnl_rel_to_own_market_cap: MetricPattern1::new(client.clone(), _m(&acc, "net_unrealized_pnl_rel_to_own_market_cap")), + net_unrealized_pnl_rel_to_own_total_unrealized_pnl: MetricPattern1::new(client.clone(), _m(&acc, "net_unrealized_pnl_rel_to_own_total_unrealized_pnl")), + supply_in_loss_rel_to_own_supply: MetricPattern1::new(client.clone(), _m(&acc, "supply_in_loss_rel_to_own_supply")), + supply_in_profit_rel_to_own_supply: MetricPattern1::new(client.clone(), _m(&acc, "supply_in_profit_rel_to_own_supply")), + unrealized_loss_rel_to_own_market_cap: MetricPattern1::new(client.clone(), _m(&acc, "unrealized_loss_rel_to_own_market_cap")), + unrealized_loss_rel_to_own_total_unrealized_pnl: MetricPattern1::new(client.clone(), _m(&acc, "unrealized_loss_rel_to_own_total_unrealized_pnl")), + unrealized_profit_rel_to_own_market_cap: MetricPattern1::new(client.clone(), _m(&acc, "unrealized_profit_rel_to_own_market_cap")), + unrealized_profit_rel_to_own_total_unrealized_pnl: MetricPattern1::new(client.clone(), _m(&acc, "unrealized_profit_rel_to_own_total_unrealized_pnl")), + } + } +} + /// Pattern struct for repeated tree structure. pub struct CountPattern2 { pub average: MetricPattern1, @@ -1798,36 +1798,6 @@ impl AddrCountPattern { } } -/// Pattern struct for repeated tree structure. -pub struct FeeRatePattern { - pub average: MetricPattern1, - pub max: MetricPattern1, - pub median: MetricPattern11, - pub min: MetricPattern1, - pub pct10: MetricPattern11, - pub pct25: MetricPattern11, - pub pct75: MetricPattern11, - pub pct90: MetricPattern11, - pub txindex: MetricPattern27, -} - -impl FeeRatePattern { - /// Create a new pattern node with accumulated metric name. - pub fn new(client: Arc, acc: String) -> Self { - Self { - average: MetricPattern1::new(client.clone(), _m(&acc, "average")), - max: MetricPattern1::new(client.clone(), _m(&acc, "max")), - median: MetricPattern11::new(client.clone(), _m(&acc, "median")), - min: MetricPattern1::new(client.clone(), _m(&acc, "min")), - pct10: MetricPattern11::new(client.clone(), _m(&acc, "pct10")), - pct25: MetricPattern11::new(client.clone(), _m(&acc, "pct25")), - pct75: MetricPattern11::new(client.clone(), _m(&acc, "pct75")), - pct90: MetricPattern11::new(client.clone(), _m(&acc, "pct90")), - txindex: MetricPattern27::new(client.clone(), acc.clone()), - } - } -} - /// Pattern struct for repeated tree structure. pub struct FullnessPattern { pub average: MetricPattern2, @@ -1858,6 +1828,36 @@ impl FullnessPattern { } } +/// Pattern struct for repeated tree structure. +pub struct FeeRatePattern { + pub average: MetricPattern1, + pub max: MetricPattern1, + pub median: MetricPattern11, + pub min: MetricPattern1, + pub pct10: MetricPattern11, + pub pct25: MetricPattern11, + pub pct75: MetricPattern11, + pub pct90: MetricPattern11, + pub txindex: MetricPattern27, +} + +impl FeeRatePattern { + /// Create a new pattern node with accumulated metric name. + pub fn new(client: Arc, acc: String) -> Self { + Self { + average: MetricPattern1::new(client.clone(), _m(&acc, "average")), + max: MetricPattern1::new(client.clone(), _m(&acc, "max")), + median: MetricPattern11::new(client.clone(), _m(&acc, "median")), + min: MetricPattern1::new(client.clone(), _m(&acc, "min")), + pct10: MetricPattern11::new(client.clone(), _m(&acc, "pct10")), + pct25: MetricPattern11::new(client.clone(), _m(&acc, "pct25")), + pct75: MetricPattern11::new(client.clone(), _m(&acc, "pct75")), + pct90: MetricPattern11::new(client.clone(), _m(&acc, "pct90")), + txindex: MetricPattern27::new(client.clone(), acc.clone()), + } + } +} + /// Pattern struct for repeated tree structure. pub struct _0satsPattern { pub activity: ActivityPattern2, @@ -1914,58 +1914,6 @@ impl PhaseDailyCentsPattern { } } -/// Pattern struct for repeated tree structure. -pub struct _10yTo12yPattern { - pub activity: ActivityPattern2, - pub cost_basis: CostBasisPattern2, - pub outputs: OutputsPattern, - pub realized: RealizedPattern2, - pub relative: RelativePattern2, - pub supply: SupplyPattern2, - pub unrealized: UnrealizedPattern, -} - -impl _10yTo12yPattern { - /// Create a new pattern node with accumulated metric name. - pub fn new(client: Arc, acc: String) -> Self { - Self { - activity: ActivityPattern2::new(client.clone(), acc.clone()), - cost_basis: CostBasisPattern2::new(client.clone(), acc.clone()), - outputs: OutputsPattern::new(client.clone(), _m(&acc, "utxo_count")), - realized: RealizedPattern2::new(client.clone(), acc.clone()), - relative: RelativePattern2::new(client.clone(), acc.clone()), - supply: SupplyPattern2::new(client.clone(), _m(&acc, "supply")), - unrealized: UnrealizedPattern::new(client.clone(), acc.clone()), - } - } -} - -/// Pattern struct for repeated tree structure. -pub struct UnrealizedPattern { - pub neg_unrealized_loss: MetricPattern1, - pub net_unrealized_pnl: MetricPattern1, - pub supply_in_loss: ActiveSupplyPattern, - pub supply_in_profit: ActiveSupplyPattern, - pub total_unrealized_pnl: MetricPattern1, - pub unrealized_loss: MetricPattern1, - pub unrealized_profit: MetricPattern1, -} - -impl UnrealizedPattern { - /// Create a new pattern node with accumulated metric name. - pub fn new(client: Arc, acc: String) -> Self { - Self { - neg_unrealized_loss: MetricPattern1::new(client.clone(), _m(&acc, "neg_unrealized_loss")), - net_unrealized_pnl: MetricPattern1::new(client.clone(), _m(&acc, "net_unrealized_pnl")), - supply_in_loss: ActiveSupplyPattern::new(client.clone(), _m(&acc, "supply_in_loss")), - supply_in_profit: ActiveSupplyPattern::new(client.clone(), _m(&acc, "supply_in_profit")), - total_unrealized_pnl: MetricPattern1::new(client.clone(), _m(&acc, "total_unrealized_pnl")), - unrealized_loss: MetricPattern1::new(client.clone(), _m(&acc, "unrealized_loss")), - unrealized_profit: MetricPattern1::new(client.clone(), _m(&acc, "unrealized_profit")), - } - } -} - /// Pattern struct for repeated tree structure. pub struct _0satsPattern2 { pub activity: ActivityPattern2, @@ -1992,6 +1940,32 @@ impl _0satsPattern2 { } } +/// Pattern struct for repeated tree structure. +pub struct _10yTo12yPattern { + pub activity: ActivityPattern2, + pub cost_basis: CostBasisPattern2, + pub outputs: OutputsPattern, + pub realized: RealizedPattern2, + pub relative: RelativePattern2, + pub supply: SupplyPattern2, + pub unrealized: UnrealizedPattern, +} + +impl _10yTo12yPattern { + /// Create a new pattern node with accumulated metric name. + pub fn new(client: Arc, acc: String) -> Self { + Self { + activity: ActivityPattern2::new(client.clone(), acc.clone()), + cost_basis: CostBasisPattern2::new(client.clone(), acc.clone()), + outputs: OutputsPattern::new(client.clone(), _m(&acc, "utxo_count")), + realized: RealizedPattern2::new(client.clone(), acc.clone()), + relative: RelativePattern2::new(client.clone(), acc.clone()), + supply: SupplyPattern2::new(client.clone(), _m(&acc, "supply")), + unrealized: UnrealizedPattern::new(client.clone(), acc.clone()), + } + } +} + /// Pattern struct for repeated tree structure. pub struct PeriodCagrPattern { pub _10y: MetricPattern4, @@ -2018,6 +1992,32 @@ impl PeriodCagrPattern { } } +/// Pattern struct for repeated tree structure. +pub struct UnrealizedPattern { + pub neg_unrealized_loss: MetricPattern1, + pub net_unrealized_pnl: MetricPattern1, + pub supply_in_loss: ActiveSupplyPattern, + pub supply_in_profit: ActiveSupplyPattern, + pub total_unrealized_pnl: MetricPattern1, + pub unrealized_loss: MetricPattern1, + pub unrealized_profit: MetricPattern1, +} + +impl UnrealizedPattern { + /// Create a new pattern node with accumulated metric name. + pub fn new(client: Arc, acc: String) -> Self { + Self { + neg_unrealized_loss: MetricPattern1::new(client.clone(), _m(&acc, "neg_unrealized_loss")), + net_unrealized_pnl: MetricPattern1::new(client.clone(), _m(&acc, "net_unrealized_pnl")), + supply_in_loss: ActiveSupplyPattern::new(client.clone(), _m(&acc, "supply_in_loss")), + supply_in_profit: ActiveSupplyPattern::new(client.clone(), _m(&acc, "supply_in_profit")), + total_unrealized_pnl: MetricPattern1::new(client.clone(), _m(&acc, "total_unrealized_pnl")), + unrealized_loss: MetricPattern1::new(client.clone(), _m(&acc, "unrealized_loss")), + unrealized_profit: MetricPattern1::new(client.clone(), _m(&acc, "unrealized_profit")), + } + } +} + /// Pattern struct for repeated tree structure. pub struct _10yPattern { pub activity: ActivityPattern2, @@ -2113,55 +2113,19 @@ impl SplitPattern2 { } /// Pattern struct for repeated tree structure. -pub struct UnclaimedRewardsPattern { - pub bitcoin: BitcoinPattern2, - pub dollars: BlockCountPattern, - pub sats: BlockCountPattern, +pub struct _2015Pattern { + pub bitcoin: MetricPattern4, + pub dollars: MetricPattern4, + pub sats: MetricPattern4, } -impl UnclaimedRewardsPattern { +impl _2015Pattern { /// Create a new pattern node with accumulated metric name. pub fn new(client: Arc, acc: String) -> Self { Self { - bitcoin: BitcoinPattern2::new(client.clone(), _m(&acc, "btc")), - dollars: BlockCountPattern::new(client.clone(), _m(&acc, "usd")), - sats: BlockCountPattern::new(client.clone(), acc.clone()), - } - } -} - -/// Pattern struct for repeated tree structure. -pub struct CoinbasePattern { - pub bitcoin: BitcoinPattern, - pub dollars: DollarsPattern, - pub sats: DollarsPattern, -} - -impl CoinbasePattern { - /// Create a new pattern node with accumulated metric name. - pub fn new(client: Arc, acc: String) -> Self { - Self { - bitcoin: BitcoinPattern::new(client.clone(), _m(&acc, "btc")), - dollars: DollarsPattern::new(client.clone(), _m(&acc, "usd")), - sats: DollarsPattern::new(client.clone(), acc.clone()), - } - } -} - -/// Pattern struct for repeated tree structure. -pub struct SegwitAdoptionPattern { - pub base: MetricPattern11, - pub cumulative: MetricPattern2, - pub sum: MetricPattern2, -} - -impl SegwitAdoptionPattern { - /// Create a new pattern node with accumulated metric name. - pub fn new(client: Arc, acc: String) -> Self { - Self { - base: MetricPattern11::new(client.clone(), acc.clone()), - cumulative: MetricPattern2::new(client.clone(), _m(&acc, "cumulative")), - sum: MetricPattern2::new(client.clone(), _m(&acc, "sum")), + bitcoin: MetricPattern4::new(client.clone(), _m(&acc, "btc")), + dollars: MetricPattern4::new(client.clone(), _m(&acc, "usd")), + sats: MetricPattern4::new(client.clone(), acc.clone()), } } } @@ -2185,37 +2149,19 @@ impl CostBasisPattern2 { } /// Pattern struct for repeated tree structure. -pub struct ActiveSupplyPattern { - pub bitcoin: MetricPattern1, - pub dollars: MetricPattern1, - pub sats: MetricPattern1, +pub struct CoinbasePattern { + pub bitcoin: BitcoinPattern, + pub dollars: DollarsPattern, + pub sats: DollarsPattern, } -impl ActiveSupplyPattern { +impl CoinbasePattern { /// Create a new pattern node with accumulated metric name. pub fn new(client: Arc, acc: String) -> Self { Self { - bitcoin: MetricPattern1::new(client.clone(), _m(&acc, "btc")), - dollars: MetricPattern1::new(client.clone(), _m(&acc, "usd")), - sats: MetricPattern1::new(client.clone(), acc.clone()), - } - } -} - -/// Pattern struct for repeated tree structure. -pub struct _2015Pattern { - pub bitcoin: MetricPattern4, - pub dollars: MetricPattern4, - pub sats: MetricPattern4, -} - -impl _2015Pattern { - /// Create a new pattern node with accumulated metric name. - pub fn new(client: Arc, acc: String) -> Self { - Self { - bitcoin: MetricPattern4::new(client.clone(), _m(&acc, "btc")), - dollars: MetricPattern4::new(client.clone(), _m(&acc, "usd")), - sats: MetricPattern4::new(client.clone(), acc.clone()), + bitcoin: BitcoinPattern::new(client.clone(), _m(&acc, "btc")), + dollars: DollarsPattern::new(client.clone(), _m(&acc, "usd")), + sats: DollarsPattern::new(client.clone(), acc.clone()), } } } @@ -2239,33 +2185,55 @@ impl CoinbasePattern2 { } /// Pattern struct for repeated tree structure. -pub struct _1dReturns1mSdPattern { - pub sd: MetricPattern4, - pub sma: MetricPattern4, +pub struct SegwitAdoptionPattern { + pub base: MetricPattern11, + pub cumulative: MetricPattern2, + pub sum: MetricPattern2, } -impl _1dReturns1mSdPattern { +impl SegwitAdoptionPattern { /// Create a new pattern node with accumulated metric name. pub fn new(client: Arc, acc: String) -> Self { Self { - sd: MetricPattern4::new(client.clone(), _m(&acc, "sd")), - sma: MetricPattern4::new(client.clone(), _m(&acc, "sma")), + base: MetricPattern11::new(client.clone(), acc.clone()), + cumulative: MetricPattern2::new(client.clone(), _m(&acc, "cumulative")), + sum: MetricPattern2::new(client.clone(), _m(&acc, "sum")), } } } /// Pattern struct for repeated tree structure. -pub struct CostBasisPattern { - pub max: MetricPattern1, - pub min: MetricPattern1, +pub struct ActiveSupplyPattern { + pub bitcoin: MetricPattern1, + pub dollars: MetricPattern1, + pub sats: MetricPattern1, } -impl CostBasisPattern { +impl ActiveSupplyPattern { /// Create a new pattern node with accumulated metric name. pub fn new(client: Arc, acc: String) -> Self { Self { - max: MetricPattern1::new(client.clone(), _m(&acc, "max_cost_basis")), - min: MetricPattern1::new(client.clone(), _m(&acc, "min_cost_basis")), + bitcoin: MetricPattern1::new(client.clone(), _m(&acc, "btc")), + dollars: MetricPattern1::new(client.clone(), _m(&acc, "usd")), + sats: MetricPattern1::new(client.clone(), acc.clone()), + } + } +} + +/// Pattern struct for repeated tree structure. +pub struct UnclaimedRewardsPattern { + pub bitcoin: BitcoinPattern2, + pub dollars: BlockCountPattern, + pub sats: BlockCountPattern, +} + +impl UnclaimedRewardsPattern { + /// Create a new pattern node with accumulated metric name. + pub fn new(client: Arc, acc: String) -> Self { + Self { + bitcoin: BitcoinPattern2::new(client.clone(), _m(&acc, "btc")), + dollars: BlockCountPattern::new(client.clone(), _m(&acc, "usd")), + sats: BlockCountPattern::new(client.clone(), acc.clone()), } } } @@ -2303,17 +2271,33 @@ impl RelativePattern4 { } /// Pattern struct for repeated tree structure. -pub struct BitcoinPattern2 { - pub cumulative: MetricPattern2, - pub sum: MetricPattern1, +pub struct CostBasisPattern { + pub max: MetricPattern1, + pub min: MetricPattern1, } -impl BitcoinPattern2 { +impl CostBasisPattern { /// Create a new pattern node with accumulated metric name. pub fn new(client: Arc, acc: String) -> Self { Self { - cumulative: MetricPattern2::new(client.clone(), _m(&acc, "cumulative")), - sum: MetricPattern1::new(client.clone(), acc.clone()), + max: MetricPattern1::new(client.clone(), _m(&acc, "max_cost_basis")), + min: MetricPattern1::new(client.clone(), _m(&acc, "min_cost_basis")), + } + } +} + +/// Pattern struct for repeated tree structure. +pub struct _1dReturns1mSdPattern { + pub sd: MetricPattern4, + pub sma: MetricPattern4, +} + +impl _1dReturns1mSdPattern { + /// Create a new pattern node with accumulated metric name. + pub fn new(client: Arc, acc: String) -> Self { + Self { + sd: MetricPattern4::new(client.clone(), _m(&acc, "sd")), + sma: MetricPattern4::new(client.clone(), _m(&acc, "sma")), } } } @@ -2351,15 +2335,17 @@ impl BlockCountPattern { } /// Pattern struct for repeated tree structure. -pub struct OutputsPattern { - pub utxo_count: MetricPattern1, +pub struct BitcoinPattern2 { + pub cumulative: MetricPattern2, + pub sum: MetricPattern1, } -impl OutputsPattern { +impl BitcoinPattern2 { /// Create a new pattern node with accumulated metric name. pub fn new(client: Arc, acc: String) -> Self { Self { - utxo_count: MetricPattern1::new(client.clone(), acc.clone()), + cumulative: MetricPattern2::new(client.clone(), _m(&acc, "cumulative")), + sum: MetricPattern1::new(client.clone(), acc.clone()), } } } @@ -2378,6 +2364,20 @@ impl RealizedPriceExtraPattern { } } +/// Pattern struct for repeated tree structure. +pub struct OutputsPattern { + pub utxo_count: MetricPattern1, +} + +impl OutputsPattern { + /// Create a new pattern node with accumulated metric name. + pub fn new(client: Arc, acc: String) -> Self { + Self { + utxo_count: MetricPattern1::new(client.clone(), acc.clone()), + } + } +} + // Metrics tree /// Metrics tree node. @@ -4204,8 +4204,8 @@ impl MetricsTree_Market_Ath { /// Metrics tree node. pub struct MetricsTree_Market_Dca { - pub class_average_price: ClassAveragePricePattern, - pub class_returns: MetricsTree_Market_Dca_ClassReturns, + pub class_average_price: MetricsTree_Market_Dca_ClassAveragePrice, + pub class_returns: ClassAveragePricePattern, pub class_stack: MetricsTree_Market_Dca_ClassStack, pub period_average_price: PeriodAveragePricePattern, pub period_cagr: PeriodCagrPattern, @@ -4217,8 +4217,8 @@ pub struct MetricsTree_Market_Dca { impl MetricsTree_Market_Dca { pub fn new(client: Arc, base_path: String) -> Self { Self { - class_average_price: ClassAveragePricePattern::new(client.clone(), "dca_class".to_string()), - class_returns: MetricsTree_Market_Dca_ClassReturns::new(client.clone(), format!("{base_path}_class_returns")), + class_average_price: MetricsTree_Market_Dca_ClassAveragePrice::new(client.clone(), format!("{base_path}_class_average_price")), + class_returns: ClassAveragePricePattern::new(client.clone(), "dca_class".to_string()), class_stack: MetricsTree_Market_Dca_ClassStack::new(client.clone(), format!("{base_path}_class_stack")), period_average_price: PeriodAveragePricePattern::new(client.clone(), "dca_average_price".to_string()), period_cagr: PeriodCagrPattern::new(client.clone(), "dca_cagr".to_string()), @@ -4230,36 +4230,36 @@ impl MetricsTree_Market_Dca { } /// Metrics tree node. -pub struct MetricsTree_Market_Dca_ClassReturns { - pub _2015: MetricPattern4, - pub _2016: MetricPattern4, - pub _2017: MetricPattern4, - pub _2018: MetricPattern4, - pub _2019: MetricPattern4, - pub _2020: MetricPattern4, - pub _2021: MetricPattern4, - pub _2022: MetricPattern4, - pub _2023: MetricPattern4, - pub _2024: MetricPattern4, - pub _2025: MetricPattern4, - pub _2026: MetricPattern4, +pub struct MetricsTree_Market_Dca_ClassAveragePrice { + pub _2015: MetricPattern4, + pub _2016: MetricPattern4, + pub _2017: MetricPattern4, + pub _2018: MetricPattern4, + pub _2019: MetricPattern4, + pub _2020: MetricPattern4, + pub _2021: MetricPattern4, + pub _2022: MetricPattern4, + pub _2023: MetricPattern4, + pub _2024: MetricPattern4, + pub _2025: MetricPattern4, + pub _2026: MetricPattern4, } -impl MetricsTree_Market_Dca_ClassReturns { +impl MetricsTree_Market_Dca_ClassAveragePrice { pub fn new(client: Arc, base_path: String) -> Self { Self { - _2015: MetricPattern4::new(client.clone(), "dca_class_2015_returns".to_string()), - _2016: MetricPattern4::new(client.clone(), "dca_class_2016_returns".to_string()), - _2017: MetricPattern4::new(client.clone(), "dca_class_2017_returns".to_string()), - _2018: MetricPattern4::new(client.clone(), "dca_class_2018_returns".to_string()), - _2019: MetricPattern4::new(client.clone(), "dca_class_2019_returns".to_string()), - _2020: MetricPattern4::new(client.clone(), "dca_class_2020_returns".to_string()), - _2021: MetricPattern4::new(client.clone(), "dca_class_2021_returns".to_string()), - _2022: MetricPattern4::new(client.clone(), "dca_class_2022_returns".to_string()), - _2023: MetricPattern4::new(client.clone(), "dca_class_2023_returns".to_string()), - _2024: MetricPattern4::new(client.clone(), "dca_class_2024_returns".to_string()), - _2025: MetricPattern4::new(client.clone(), "dca_class_2025_returns".to_string()), - _2026: MetricPattern4::new(client.clone(), "dca_class_2026_returns".to_string()), + _2015: MetricPattern4::new(client.clone(), "dca_class_2015_average_price".to_string()), + _2016: MetricPattern4::new(client.clone(), "dca_class_2016_average_price".to_string()), + _2017: MetricPattern4::new(client.clone(), "dca_class_2017_average_price".to_string()), + _2018: MetricPattern4::new(client.clone(), "dca_class_2018_average_price".to_string()), + _2019: MetricPattern4::new(client.clone(), "dca_class_2019_average_price".to_string()), + _2020: MetricPattern4::new(client.clone(), "dca_class_2020_average_price".to_string()), + _2021: MetricPattern4::new(client.clone(), "dca_class_2021_average_price".to_string()), + _2022: MetricPattern4::new(client.clone(), "dca_class_2022_average_price".to_string()), + _2023: MetricPattern4::new(client.clone(), "dca_class_2023_average_price".to_string()), + _2024: MetricPattern4::new(client.clone(), "dca_class_2024_average_price".to_string()), + _2025: MetricPattern4::new(client.clone(), "dca_class_2025_average_price".to_string()), + _2026: MetricPattern4::new(client.clone(), "dca_class_2026_average_price".to_string()), } } } @@ -5449,7 +5449,7 @@ pub struct BrkClient { impl BrkClient { /// Client version. - pub const VERSION: &'static str = "v0.1.0-alpha.6"; + pub const VERSION: &'static str = "v0.1.0-beta.0"; /// Create a new client with the given base URL. pub fn new(base_url: impl Into) -> Self { diff --git a/crates/brk_cohort/src/by_age_range.rs b/crates/brk_cohort/src/by_age_range.rs index 2a4fdf3e0..ce5c4a039 100644 --- a/crates/brk_cohort/src/by_age_range.rs +++ b/crates/brk_cohort/src/by_age_range.rs @@ -89,27 +89,27 @@ pub const AGE_RANGE_FILTERS: ByAgeRange = ByAgeRange { /// Age range names pub const AGE_RANGE_NAMES: ByAgeRange = ByAgeRange { - up_to_1h: CohortName::new("up_to_1h_old", "<1h", "Up to 1 Hour Old"), - _1h_to_1d: CohortName::new("at_least_1h_up_to_1d_old", "1h-1d", "1 Hour to 1 Day Old"), - _1d_to_1w: CohortName::new("at_least_1d_up_to_1w_old", "1d-1w", "1 Day to 1 Week Old"), - _1w_to_1m: CohortName::new("at_least_1w_up_to_1m_old", "1w-1m", "1 Week to 1 Month Old"), - _1m_to_2m: CohortName::new("at_least_1m_up_to_2m_old", "1m-2m", "1 to 2 Months Old"), - _2m_to_3m: CohortName::new("at_least_2m_up_to_3m_old", "2m-3m", "2 to 3 Months Old"), - _3m_to_4m: CohortName::new("at_least_3m_up_to_4m_old", "3m-4m", "3 to 4 Months Old"), - _4m_to_5m: CohortName::new("at_least_4m_up_to_5m_old", "4m-5m", "4 to 5 Months Old"), - _5m_to_6m: CohortName::new("at_least_5m_up_to_6m_old", "5m-6m", "5 to 6 Months Old"), - _6m_to_1y: CohortName::new("at_least_6m_up_to_1y_old", "6m-1y", "6 Months to 1 Year Old"), - _1y_to_2y: CohortName::new("at_least_1y_up_to_2y_old", "1y-2y", "1 to 2 Years Old"), - _2y_to_3y: CohortName::new("at_least_2y_up_to_3y_old", "2y-3y", "2 to 3 Years Old"), - _3y_to_4y: CohortName::new("at_least_3y_up_to_4y_old", "3y-4y", "3 to 4 Years Old"), - _4y_to_5y: CohortName::new("at_least_4y_up_to_5y_old", "4y-5y", "4 to 5 Years Old"), - _5y_to_6y: CohortName::new("at_least_5y_up_to_6y_old", "5y-6y", "5 to 6 Years Old"), - _6y_to_7y: CohortName::new("at_least_6y_up_to_7y_old", "6y-7y", "6 to 7 Years Old"), - _7y_to_8y: CohortName::new("at_least_7y_up_to_8y_old", "7y-8y", "7 to 8 Years Old"), - _8y_to_10y: CohortName::new("at_least_8y_up_to_10y_old", "8y-10y", "8 to 10 Years Old"), - _10y_to_12y: CohortName::new("at_least_10y_up_to_12y_old", "10y-12y", "10 to 12 Years Old"), - _12y_to_15y: CohortName::new("at_least_12y_up_to_15y_old", "12y-15y", "12 to 15 Years Old"), - from_15y: CohortName::new("at_least_15y_old", "15y+", "15+ Years Old"), + up_to_1h: CohortName::new("under_1h_old", "<1h", "Under 1 Hour Old"), + _1h_to_1d: CohortName::new("1h_to_1d_old", "1h-1d", "1 Hour to 1 Day Old"), + _1d_to_1w: CohortName::new("1d_to_1w_old", "1d-1w", "1 Day to 1 Week Old"), + _1w_to_1m: CohortName::new("1w_to_1m_old", "1w-1m", "1 Week to 1 Month Old"), + _1m_to_2m: CohortName::new("1m_to_2m_old", "1m-2m", "1 to 2 Months Old"), + _2m_to_3m: CohortName::new("2m_to_3m_old", "2m-3m", "2 to 3 Months Old"), + _3m_to_4m: CohortName::new("3m_to_4m_old", "3m-4m", "3 to 4 Months Old"), + _4m_to_5m: CohortName::new("4m_to_5m_old", "4m-5m", "4 to 5 Months Old"), + _5m_to_6m: CohortName::new("5m_to_6m_old", "5m-6m", "5 to 6 Months Old"), + _6m_to_1y: CohortName::new("6m_to_1y_old", "6m-1y", "6 Months to 1 Year Old"), + _1y_to_2y: CohortName::new("1y_to_2y_old", "1y-2y", "1 to 2 Years Old"), + _2y_to_3y: CohortName::new("2y_to_3y_old", "2y-3y", "2 to 3 Years Old"), + _3y_to_4y: CohortName::new("3y_to_4y_old", "3y-4y", "3 to 4 Years Old"), + _4y_to_5y: CohortName::new("4y_to_5y_old", "4y-5y", "4 to 5 Years Old"), + _5y_to_6y: CohortName::new("5y_to_6y_old", "5y-6y", "5 to 6 Years Old"), + _6y_to_7y: CohortName::new("6y_to_7y_old", "6y-7y", "6 to 7 Years Old"), + _7y_to_8y: CohortName::new("7y_to_8y_old", "7y-8y", "7 to 8 Years Old"), + _8y_to_10y: CohortName::new("8y_to_10y_old", "8y-10y", "8 to 10 Years Old"), + _10y_to_12y: CohortName::new("10y_to_12y_old", "10y-12y", "10 to 12 Years Old"), + _12y_to_15y: CohortName::new("12y_to_15y_old", "12y-15y", "12 to 15 Years Old"), + from_15y: CohortName::new("over_15y_old", "15y+", "15+ Years Old"), }; impl ByAgeRange { diff --git a/crates/brk_cohort/src/by_amount_range.rs b/crates/brk_cohort/src/by_amount_range.rs index 61da5e5a3..7d918c3d0 100644 --- a/crates/brk_cohort/src/by_amount_range.rs +++ b/crates/brk_cohort/src/by_amount_range.rs @@ -80,50 +80,50 @@ pub const AMOUNT_RANGE_BOUNDS: ByAmountRange> = ByAmountRange { /// Amount range names pub const AMOUNT_RANGE_NAMES: ByAmountRange = ByAmountRange { _0sats: CohortName::new("with_0sats", "0 sats", "0 Sats"), - _1sat_to_10sats: CohortName::new("above_1sat_under_10sats", "1-10 sats", "1 to 10 Sats"), + _1sat_to_10sats: CohortName::new("above_1sat_under_10sats", "1-10 sats", "1-10 Sats"), _10sats_to_100sats: CohortName::new( "above_10sats_under_100sats", "10-100 sats", - "10 to 100 Sats", + "10-100 Sats", ), _100sats_to_1k_sats: CohortName::new( "above_100sats_under_1k_sats", "100-1k sats", - "100 to 1K Sats", + "100-1K Sats", ), _1k_sats_to_10k_sats: CohortName::new( "above_1k_sats_under_10k_sats", "1k-10k sats", - "1K to 10K Sats", + "1K-10K Sats", ), _10k_sats_to_100k_sats: CohortName::new( "above_10k_sats_under_100k_sats", "10k-100k sats", - "10K to 100K Sats", + "10K-100K Sats", ), _100k_sats_to_1m_sats: CohortName::new( "above_100k_sats_under_1m_sats", "100k-1M sats", - "100K to 1M Sats", + "100K-1M Sats", ), _1m_sats_to_10m_sats: CohortName::new( "above_1m_sats_under_10m_sats", "1M-10M sats", - "1M to 10M Sats", + "1M-10M Sats", ), - _10m_sats_to_1btc: CohortName::new("above_10m_sats_under_1btc", "0.1-1 BTC", "0.1 to 1 BTC"), - _1btc_to_10btc: CohortName::new("above_1btc_under_10btc", "1-10 BTC", "1 to 10 BTC"), - _10btc_to_100btc: CohortName::new("above_10btc_under_100btc", "10-100 BTC", "10 to 100 BTC"), - _100btc_to_1k_btc: CohortName::new("above_100btc_under_1k_btc", "100-1k BTC", "100 to 1K BTC"), + _10m_sats_to_1btc: CohortName::new("above_10m_sats_under_1btc", "0.1-1 BTC", "0.1-1 BTC"), + _1btc_to_10btc: CohortName::new("above_1btc_under_10btc", "1-10 BTC", "1-10 BTC"), + _10btc_to_100btc: CohortName::new("above_10btc_under_100btc", "10-100 BTC", "10-100 BTC"), + _100btc_to_1k_btc: CohortName::new("above_100btc_under_1k_btc", "100-1k BTC", "100-1K BTC"), _1k_btc_to_10k_btc: CohortName::new( "above_1k_btc_under_10k_btc", "1k-10k BTC", - "1K to 10K BTC", + "1K-10K BTC", ), _10k_btc_to_100k_btc: CohortName::new( "above_10k_btc_under_100k_btc", "10k-100k BTC", - "10K to 100K BTC", + "10K-100K BTC", ), _100k_btc_or_more: CohortName::new("above_100k_btc", "100k+ BTC", "100K+ BTC"), }; diff --git a/crates/brk_cohort/src/by_epoch.rs b/crates/brk_cohort/src/by_epoch.rs index 5bf52e939..1ec1c2ec5 100644 --- a/crates/brk_cohort/src/by_epoch.rs +++ b/crates/brk_cohort/src/by_epoch.rs @@ -25,11 +25,11 @@ pub const EPOCH_FILTERS: ByEpoch = ByEpoch { /// Epoch names pub const EPOCH_NAMES: ByEpoch = ByEpoch { - _0: CohortName::new("epoch_0", "Epoch 0", "Epoch 0"), - _1: CohortName::new("epoch_1", "Epoch 1", "Epoch 1"), - _2: CohortName::new("epoch_2", "Epoch 2", "Epoch 2"), - _3: CohortName::new("epoch_3", "Epoch 3", "Epoch 3"), - _4: CohortName::new("epoch_4", "Epoch 4", "Epoch 4"), + _0: CohortName::new("epoch_0", "0", "Epoch 0"), + _1: CohortName::new("epoch_1", "1", "Epoch 1"), + _2: CohortName::new("epoch_2", "2", "Epoch 2"), + _3: CohortName::new("epoch_3", "3", "Epoch 3"), + _4: CohortName::new("epoch_4", "4", "Epoch 4"), }; #[derive(Default, Clone, Traversable, Serialize)] diff --git a/crates/brk_cohort/src/by_ge_amount.rs b/crates/brk_cohort/src/by_ge_amount.rs index 57c0af53c..b135222bb 100644 --- a/crates/brk_cohort/src/by_ge_amount.rs +++ b/crates/brk_cohort/src/by_ge_amount.rs @@ -24,19 +24,19 @@ pub const GE_AMOUNT_THRESHOLDS: ByGreatEqualAmount = ByGreatEqualAmount { /// Greater-or-equal amount names pub const GE_AMOUNT_NAMES: ByGreatEqualAmount = ByGreatEqualAmount { - _1sat: CohortName::new("above_1sat", "1+ sats", "Above 1 Sat"), - _10sats: CohortName::new("above_10sats", "10+ sats", "Above 10 Sats"), - _100sats: CohortName::new("above_100sats", "100+ sats", "Above 100 Sats"), - _1k_sats: CohortName::new("above_1k_sats", "1k+ sats", "Above 1K Sats"), - _10k_sats: CohortName::new("above_10k_sats", "10k+ sats", "Above 10K Sats"), - _100k_sats: CohortName::new("above_100k_sats", "100k+ sats", "Above 100K Sats"), - _1m_sats: CohortName::new("above_1m_sats", "1M+ sats", "Above 1M Sats"), - _10m_sats: CohortName::new("above_10m_sats", "0.1+ BTC", "Above 0.1 BTC"), - _1btc: CohortName::new("above_1btc", "1+ BTC", "Above 1 BTC"), - _10btc: CohortName::new("above_10btc", "10+ BTC", "Above 10 BTC"), - _100btc: CohortName::new("above_100btc", "100+ BTC", "Above 100 BTC"), - _1k_btc: CohortName::new("above_1k_btc", "1k+ BTC", "Above 1K BTC"), - _10k_btc: CohortName::new("above_10k_btc", "10k+ BTC", "Above 10K BTC"), + _1sat: CohortName::new("over_1sat", "1+ sats", "Over 1 Sat"), + _10sats: CohortName::new("over_10sats", "10+ sats", "Over 10 Sats"), + _100sats: CohortName::new("over_100sats", "100+ sats", "Over 100 Sats"), + _1k_sats: CohortName::new("over_1k_sats", "1k+ sats", "Over 1K Sats"), + _10k_sats: CohortName::new("over_10k_sats", "10k+ sats", "Over 10K Sats"), + _100k_sats: CohortName::new("over_100k_sats", "100k+ sats", "Over 100K Sats"), + _1m_sats: CohortName::new("over_1m_sats", "1M+ sats", "Over 1M Sats"), + _10m_sats: CohortName::new("over_10m_sats", "0.1+ BTC", "Over 0.1 BTC"), + _1btc: CohortName::new("over_1btc", "1+ BTC", "Over 1 BTC"), + _10btc: CohortName::new("over_10btc", "10+ BTC", "Over 10 BTC"), + _100btc: CohortName::new("over_100btc", "100+ BTC", "Over 100 BTC"), + _1k_btc: CohortName::new("over_1k_btc", "1k+ BTC", "Over 1K BTC"), + _10k_btc: CohortName::new("over_10k_btc", "10k+ BTC", "Over 10K BTC"), }; /// Greater-or-equal amount filters diff --git a/crates/brk_cohort/src/by_max_age.rs b/crates/brk_cohort/src/by_max_age.rs index abbc62008..cc7c584ab 100644 --- a/crates/brk_cohort/src/by_max_age.rs +++ b/crates/brk_cohort/src/by_max_age.rs @@ -54,24 +54,24 @@ pub const MAX_AGE_FILTERS: ByMaxAge = ByMaxAge { /// Max age names pub const MAX_AGE_NAMES: ByMaxAge = ByMaxAge { - _1w: CohortName::new("up_to_1w_old", "<1w", "Up to 1 Week Old"), - _1m: CohortName::new("up_to_1m_old", "<1m", "Up to 1 Month Old"), - _2m: CohortName::new("up_to_2m_old", "<2m", "Up to 2 Months Old"), - _3m: CohortName::new("up_to_3m_old", "<3m", "Up to 3 Months Old"), - _4m: CohortName::new("up_to_4m_old", "<4m", "Up to 4 Months Old"), - _5m: CohortName::new("up_to_5m_old", "<5m", "Up to 5 Months Old"), - _6m: CohortName::new("up_to_6m_old", "<6m", "Up to 6 Months Old"), - _1y: CohortName::new("up_to_1y_old", "<1y", "Up to 1 Year Old"), - _2y: CohortName::new("up_to_2y_old", "<2y", "Up to 2 Years Old"), - _3y: CohortName::new("up_to_3y_old", "<3y", "Up to 3 Years Old"), - _4y: CohortName::new("up_to_4y_old", "<4y", "Up to 4 Years Old"), - _5y: CohortName::new("up_to_5y_old", "<5y", "Up to 5 Years Old"), - _6y: CohortName::new("up_to_6y_old", "<6y", "Up to 6 Years Old"), - _7y: CohortName::new("up_to_7y_old", "<7y", "Up to 7 Years Old"), - _8y: CohortName::new("up_to_8y_old", "<8y", "Up to 8 Years Old"), - _10y: CohortName::new("up_to_10y_old", "<10y", "Up to 10 Years Old"), - _12y: CohortName::new("up_to_12y_old", "<12y", "Up to 12 Years Old"), - _15y: CohortName::new("up_to_15y_old", "<15y", "Up to 15 Years Old"), + _1w: CohortName::new("under_1w_old", "<1w", "Under 1 Week Old"), + _1m: CohortName::new("under_1m_old", "<1m", "Under 1 Month Old"), + _2m: CohortName::new("under_2m_old", "<2m", "Under 2 Months Old"), + _3m: CohortName::new("under_3m_old", "<3m", "Under 3 Months Old"), + _4m: CohortName::new("under_4m_old", "<4m", "Under 4 Months Old"), + _5m: CohortName::new("under_5m_old", "<5m", "Under 5 Months Old"), + _6m: CohortName::new("under_6m_old", "<6m", "Under 6 Months Old"), + _1y: CohortName::new("under_1y_old", "<1y", "Under 1 Year Old"), + _2y: CohortName::new("under_2y_old", "<2y", "Under 2 Years Old"), + _3y: CohortName::new("under_3y_old", "<3y", "Under 3 Years Old"), + _4y: CohortName::new("under_4y_old", "<4y", "Under 4 Years Old"), + _5y: CohortName::new("under_5y_old", "<5y", "Under 5 Years Old"), + _6y: CohortName::new("under_6y_old", "<6y", "Under 6 Years Old"), + _7y: CohortName::new("under_7y_old", "<7y", "Under 7 Years Old"), + _8y: CohortName::new("under_8y_old", "<8y", "Under 8 Years Old"), + _10y: CohortName::new("under_10y_old", "<10y", "Under 10 Years Old"), + _12y: CohortName::new("under_12y_old", "<12y", "Under 12 Years Old"), + _15y: CohortName::new("under_15y_old", "<15y", "Under 15 Years Old"), }; #[derive(Default, Clone, Traversable, Serialize)] diff --git a/crates/brk_cohort/src/by_min_age.rs b/crates/brk_cohort/src/by_min_age.rs index 676e0fe03..1cd444b3e 100644 --- a/crates/brk_cohort/src/by_min_age.rs +++ b/crates/brk_cohort/src/by_min_age.rs @@ -54,24 +54,24 @@ pub const MIN_AGE_FILTERS: ByMinAge = ByMinAge { /// Min age names pub const MIN_AGE_NAMES: ByMinAge = ByMinAge { - _1d: CohortName::new("at_least_1d_old", "1d+", "At Least 1 Day Old"), - _1w: CohortName::new("at_least_1w_old", "1w+", "At Least 1 Week Old"), - _1m: CohortName::new("at_least_1m_old", "1m+", "At Least 1 Month Old"), - _2m: CohortName::new("at_least_2m_old", "2m+", "At Least 2 Months Old"), - _3m: CohortName::new("at_least_3m_old", "3m+", "At Least 3 Months Old"), - _4m: CohortName::new("at_least_4m_old", "4m+", "At Least 4 Months Old"), - _5m: CohortName::new("at_least_5m_old", "5m+", "At Least 5 Months Old"), - _6m: CohortName::new("at_least_6m_old", "6m+", "At Least 6 Months Old"), - _1y: CohortName::new("at_least_1y_old", "1y+", "At Least 1 Year Old"), - _2y: CohortName::new("at_least_2y_old", "2y+", "At Least 2 Years Old"), - _3y: CohortName::new("at_least_3y_old", "3y+", "At Least 3 Years Old"), - _4y: CohortName::new("at_least_4y_old", "4y+", "At Least 4 Years Old"), - _5y: CohortName::new("at_least_5y_old", "5y+", "At Least 5 Years Old"), - _6y: CohortName::new("at_least_6y_old", "6y+", "At Least 6 Years Old"), - _7y: CohortName::new("at_least_7y_old", "7y+", "At Least 7 Years Old"), - _8y: CohortName::new("at_least_8y_old", "8y+", "At Least 8 Years Old"), - _10y: CohortName::new("at_least_10y_old", "10y+", "At Least 10 Years Old"), - _12y: CohortName::new("at_least_12y_old", "12y+", "At Least 12 Years Old"), + _1d: CohortName::new("over_1d_old", "1d+", "Over 1 Day Old"), + _1w: CohortName::new("over_1w_old", "1w+", "Over 1 Week Old"), + _1m: CohortName::new("over_1m_old", "1m+", "Over 1 Month Old"), + _2m: CohortName::new("over_2m_old", "2m+", "Over 2 Months Old"), + _3m: CohortName::new("over_3m_old", "3m+", "Over 3 Months Old"), + _4m: CohortName::new("over_4m_old", "4m+", "Over 4 Months Old"), + _5m: CohortName::new("over_5m_old", "5m+", "Over 5 Months Old"), + _6m: CohortName::new("over_6m_old", "6m+", "Over 6 Months Old"), + _1y: CohortName::new("over_1y_old", "1y+", "Over 1 Year Old"), + _2y: CohortName::new("over_2y_old", "2y+", "Over 2 Years Old"), + _3y: CohortName::new("over_3y_old", "3y+", "Over 3 Years Old"), + _4y: CohortName::new("over_4y_old", "4y+", "Over 4 Years Old"), + _5y: CohortName::new("over_5y_old", "5y+", "Over 5 Years Old"), + _6y: CohortName::new("over_6y_old", "6y+", "Over 6 Years Old"), + _7y: CohortName::new("over_7y_old", "7y+", "Over 7 Years Old"), + _8y: CohortName::new("over_8y_old", "8y+", "Over 8 Years Old"), + _10y: CohortName::new("over_10y_old", "10y+", "Over 10 Years Old"), + _12y: CohortName::new("over_12y_old", "12y+", "Over 12 Years Old"), }; #[derive(Default, Clone, Traversable, Serialize)] diff --git a/crates/brk_playground/.gitignore b/crates/brk_playground/.gitignore deleted file mode 100644 index cab784871..000000000 --- a/crates/brk_playground/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -# Ignore everything in playground except essential files -* -!.gitignore -!Cargo.toml -!src/ -!src/lib.rs -!README.md diff --git a/crates/brk_playground/Cargo.toml b/crates/brk_playground/Cargo.toml deleted file mode 100644 index e796904e4..000000000 --- a/crates/brk_playground/Cargo.toml +++ /dev/null @@ -1,57 +0,0 @@ -[package] -name = "brk_playground" -description = "Experimental playground for brk development" -version.workspace = true -edition.workspace = true -license.workspace = true -homepage.workspace = true -repository.workspace = true -publish = false - -[package.metadata.release] -release = false - -[dependencies] -brk_computer = { workspace = true } -brk_error = { workspace = true } -brk_fetcher = { workspace = true } -brk_indexer = { workspace = true } -brk_logger = { workspace = true } -brk_types = { workspace = true } -jiff = { workspace = true } -memmap2 = "0.9" -plotters = "0.3" -tracing = { workspace = true } -vecdb = { workspace = true } - -[[example]] -name = "heatmap" -path = "examples/heatmap.rs" - -[[example]] -name = "oracle" -path = "examples/oracle.rs" - -[[example]] -name = "plot_oracle" -path = "examples/plot_oracle.rs" - -[[example]] -name = "tune_smooth" -path = "examples/tune_smooth.rs" - -[[example]] -name = "debug_oracle" -path = "examples/debug_oracle.rs" - -[[example]] -name = "filters" -path = "examples/filters.rs" - -[[example]] -name = "stencil" -path = "examples/stencil.rs" - -[[example]] -name = "epoch5_test" -path = "examples/epoch5_test.rs" diff --git a/crates/brk_playground/src/lib.rs b/crates/brk_playground/src/lib.rs deleted file mode 100644 index b2dc66ffa..000000000 --- a/crates/brk_playground/src/lib.rs +++ /dev/null @@ -1,28 +0,0 @@ -//! Playground library for Bitcoin on-chain analysis -//! -//! This crate provides tools for: -//! - Phase histogram analysis of UTXO patterns -//! - Filter-based output selection for price signal extraction -//! - On-chain OHLC price oracle derivation - -pub mod anchors; -pub mod conditions; -pub mod constants; -pub mod filters; -pub mod histogram; -pub mod oracle; -pub mod render; -pub mod signal; - -pub use anchors::{Ohlc, get_anchor_ohlc, get_anchor_range}; -pub use conditions::{MappedOutputConditions, out_bits, tx_bits}; -pub use constants::{NUM_BINS, OutputFilter, ROUND_USD_AMOUNTS, START_HEIGHT}; -pub use filters::FILTERS; -pub use histogram::load_or_compute_output_conditions; -pub use oracle::{ - HeightPriceResult, OracleConfig, OracleResult, derive_daily_ohlc, - derive_daily_ohlc_with_confidence, derive_height_price, derive_height_price_with_confidence, - derive_ohlc_from_height_prices, derive_ohlc_from_height_prices_with_confidence, - derive_price_fast, derive_price_fast_with_confidence, derive_price_from_histogram, -}; -pub use signal::{compute_expected_bins_per_day, usd_to_bin}; diff --git a/crates/brk_website/.gitignore b/crates/brk_website/.gitignore new file mode 100644 index 000000000..bfab07a1e --- /dev/null +++ b/crates/brk_website/.gitignore @@ -0,0 +1 @@ +website/* diff --git a/modules/brk-client/index.js b/modules/brk-client/index.js index 031b3b2ce..958e7150d 100644 --- a/modules/brk-client/index.js +++ b/modules/brk-client/index.js @@ -1931,6 +1931,47 @@ function createPeriodLumpSumStackPattern(client, acc) { }; } +/** + * @template T + * @typedef {Object} ClassAveragePricePattern + * @property {MetricPattern4} _2015 + * @property {MetricPattern4} _2016 + * @property {MetricPattern4} _2017 + * @property {MetricPattern4} _2018 + * @property {MetricPattern4} _2019 + * @property {MetricPattern4} _2020 + * @property {MetricPattern4} _2021 + * @property {MetricPattern4} _2022 + * @property {MetricPattern4} _2023 + * @property {MetricPattern4} _2024 + * @property {MetricPattern4} _2025 + * @property {MetricPattern4} _2026 + */ + +/** + * Create a ClassAveragePricePattern pattern node + * @template T + * @param {BrkClientBase} client + * @param {string} acc - Accumulated metric name + * @returns {ClassAveragePricePattern} + */ +function createClassAveragePricePattern(client, acc) { + return { + _2015: createMetricPattern4(client, _m(acc, '2015_returns')), + _2016: createMetricPattern4(client, _m(acc, '2016_returns')), + _2017: createMetricPattern4(client, _m(acc, '2017_returns')), + _2018: createMetricPattern4(client, _m(acc, '2018_returns')), + _2019: createMetricPattern4(client, _m(acc, '2019_returns')), + _2020: createMetricPattern4(client, _m(acc, '2020_returns')), + _2021: createMetricPattern4(client, _m(acc, '2021_returns')), + _2022: createMetricPattern4(client, _m(acc, '2022_returns')), + _2023: createMetricPattern4(client, _m(acc, '2023_returns')), + _2024: createMetricPattern4(client, _m(acc, '2024_returns')), + _2025: createMetricPattern4(client, _m(acc, '2025_returns')), + _2026: createMetricPattern4(client, _m(acc, '2026_returns')), + }; +} + /** * @template T * @typedef {Object} PeriodAveragePricePattern @@ -1972,47 +2013,6 @@ function createPeriodAveragePricePattern(client, acc) { }; } -/** - * @template T - * @typedef {Object} ClassAveragePricePattern - * @property {MetricPattern4} _2015 - * @property {MetricPattern4} _2016 - * @property {MetricPattern4} _2017 - * @property {MetricPattern4} _2018 - * @property {MetricPattern4} _2019 - * @property {MetricPattern4} _2020 - * @property {MetricPattern4} _2021 - * @property {MetricPattern4} _2022 - * @property {MetricPattern4} _2023 - * @property {MetricPattern4} _2024 - * @property {MetricPattern4} _2025 - * @property {MetricPattern4} _2026 - */ - -/** - * Create a ClassAveragePricePattern pattern node - * @template T - * @param {BrkClientBase} client - * @param {string} acc - Accumulated metric name - * @returns {ClassAveragePricePattern} - */ -function createClassAveragePricePattern(client, acc) { - return { - _2015: createMetricPattern4(client, _m(acc, '2015_average_price')), - _2016: createMetricPattern4(client, _m(acc, '2016_average_price')), - _2017: createMetricPattern4(client, _m(acc, '2017_average_price')), - _2018: createMetricPattern4(client, _m(acc, '2018_average_price')), - _2019: createMetricPattern4(client, _m(acc, '2019_average_price')), - _2020: createMetricPattern4(client, _m(acc, '2020_average_price')), - _2021: createMetricPattern4(client, _m(acc, '2021_average_price')), - _2022: createMetricPattern4(client, _m(acc, '2022_average_price')), - _2023: createMetricPattern4(client, _m(acc, '2023_average_price')), - _2024: createMetricPattern4(client, _m(acc, '2024_average_price')), - _2025: createMetricPattern4(client, _m(acc, '2025_average_price')), - _2026: createMetricPattern4(client, _m(acc, '2026_average_price')), - }; -} - /** * @typedef {Object} BitcoinPattern * @property {MetricPattern2} average @@ -2089,41 +2089,6 @@ function createDollarsPattern(client, acc) { }; } -/** - * @typedef {Object} RelativePattern2 - * @property {MetricPattern1} negUnrealizedLossRelToOwnMarketCap - * @property {MetricPattern1} negUnrealizedLossRelToOwnTotalUnrealizedPnl - * @property {MetricPattern1} netUnrealizedPnlRelToOwnMarketCap - * @property {MetricPattern1} netUnrealizedPnlRelToOwnTotalUnrealizedPnl - * @property {MetricPattern1} supplyInLossRelToOwnSupply - * @property {MetricPattern1} supplyInProfitRelToOwnSupply - * @property {MetricPattern1} unrealizedLossRelToOwnMarketCap - * @property {MetricPattern1} unrealizedLossRelToOwnTotalUnrealizedPnl - * @property {MetricPattern1} unrealizedProfitRelToOwnMarketCap - * @property {MetricPattern1} unrealizedProfitRelToOwnTotalUnrealizedPnl - */ - -/** - * Create a RelativePattern2 pattern node - * @param {BrkClientBase} client - * @param {string} acc - Accumulated metric name - * @returns {RelativePattern2} - */ -function createRelativePattern2(client, acc) { - return { - negUnrealizedLossRelToOwnMarketCap: createMetricPattern1(client, _m(acc, 'neg_unrealized_loss_rel_to_own_market_cap')), - negUnrealizedLossRelToOwnTotalUnrealizedPnl: createMetricPattern1(client, _m(acc, 'neg_unrealized_loss_rel_to_own_total_unrealized_pnl')), - netUnrealizedPnlRelToOwnMarketCap: createMetricPattern1(client, _m(acc, 'net_unrealized_pnl_rel_to_own_market_cap')), - netUnrealizedPnlRelToOwnTotalUnrealizedPnl: createMetricPattern1(client, _m(acc, 'net_unrealized_pnl_rel_to_own_total_unrealized_pnl')), - supplyInLossRelToOwnSupply: createMetricPattern1(client, _m(acc, 'supply_in_loss_rel_to_own_supply')), - supplyInProfitRelToOwnSupply: createMetricPattern1(client, _m(acc, 'supply_in_profit_rel_to_own_supply')), - unrealizedLossRelToOwnMarketCap: createMetricPattern1(client, _m(acc, 'unrealized_loss_rel_to_own_market_cap')), - unrealizedLossRelToOwnTotalUnrealizedPnl: createMetricPattern1(client, _m(acc, 'unrealized_loss_rel_to_own_total_unrealized_pnl')), - unrealizedProfitRelToOwnMarketCap: createMetricPattern1(client, _m(acc, 'unrealized_profit_rel_to_own_market_cap')), - unrealizedProfitRelToOwnTotalUnrealizedPnl: createMetricPattern1(client, _m(acc, 'unrealized_profit_rel_to_own_total_unrealized_pnl')), - }; -} - /** * @typedef {Object} RelativePattern * @property {MetricPattern1} negUnrealizedLossRelToMarketCap @@ -2159,6 +2124,41 @@ function createRelativePattern(client, acc) { }; } +/** + * @typedef {Object} RelativePattern2 + * @property {MetricPattern1} negUnrealizedLossRelToOwnMarketCap + * @property {MetricPattern1} negUnrealizedLossRelToOwnTotalUnrealizedPnl + * @property {MetricPattern1} netUnrealizedPnlRelToOwnMarketCap + * @property {MetricPattern1} netUnrealizedPnlRelToOwnTotalUnrealizedPnl + * @property {MetricPattern1} supplyInLossRelToOwnSupply + * @property {MetricPattern1} supplyInProfitRelToOwnSupply + * @property {MetricPattern1} unrealizedLossRelToOwnMarketCap + * @property {MetricPattern1} unrealizedLossRelToOwnTotalUnrealizedPnl + * @property {MetricPattern1} unrealizedProfitRelToOwnMarketCap + * @property {MetricPattern1} unrealizedProfitRelToOwnTotalUnrealizedPnl + */ + +/** + * Create a RelativePattern2 pattern node + * @param {BrkClientBase} client + * @param {string} acc - Accumulated metric name + * @returns {RelativePattern2} + */ +function createRelativePattern2(client, acc) { + return { + negUnrealizedLossRelToOwnMarketCap: createMetricPattern1(client, _m(acc, 'neg_unrealized_loss_rel_to_own_market_cap')), + negUnrealizedLossRelToOwnTotalUnrealizedPnl: createMetricPattern1(client, _m(acc, 'neg_unrealized_loss_rel_to_own_total_unrealized_pnl')), + netUnrealizedPnlRelToOwnMarketCap: createMetricPattern1(client, _m(acc, 'net_unrealized_pnl_rel_to_own_market_cap')), + netUnrealizedPnlRelToOwnTotalUnrealizedPnl: createMetricPattern1(client, _m(acc, 'net_unrealized_pnl_rel_to_own_total_unrealized_pnl')), + supplyInLossRelToOwnSupply: createMetricPattern1(client, _m(acc, 'supply_in_loss_rel_to_own_supply')), + supplyInProfitRelToOwnSupply: createMetricPattern1(client, _m(acc, 'supply_in_profit_rel_to_own_supply')), + unrealizedLossRelToOwnMarketCap: createMetricPattern1(client, _m(acc, 'unrealized_loss_rel_to_own_market_cap')), + unrealizedLossRelToOwnTotalUnrealizedPnl: createMetricPattern1(client, _m(acc, 'unrealized_loss_rel_to_own_total_unrealized_pnl')), + unrealizedProfitRelToOwnMarketCap: createMetricPattern1(client, _m(acc, 'unrealized_profit_rel_to_own_market_cap')), + unrealizedProfitRelToOwnTotalUnrealizedPnl: createMetricPattern1(client, _m(acc, 'unrealized_profit_rel_to_own_total_unrealized_pnl')), + }; +} + /** * @template T * @typedef {Object} CountPattern2 @@ -2229,41 +2229,6 @@ function createAddrCountPattern(client, acc) { }; } -/** - * @template T - * @typedef {Object} FeeRatePattern - * @property {MetricPattern1} average - * @property {MetricPattern1} max - * @property {MetricPattern11} median - * @property {MetricPattern1} min - * @property {MetricPattern11} pct10 - * @property {MetricPattern11} pct25 - * @property {MetricPattern11} pct75 - * @property {MetricPattern11} pct90 - * @property {MetricPattern27} txindex - */ - -/** - * Create a FeeRatePattern pattern node - * @template T - * @param {BrkClientBase} client - * @param {string} acc - Accumulated metric name - * @returns {FeeRatePattern} - */ -function createFeeRatePattern(client, acc) { - return { - average: createMetricPattern1(client, _m(acc, 'average')), - max: createMetricPattern1(client, _m(acc, 'max')), - median: createMetricPattern11(client, _m(acc, 'median')), - min: createMetricPattern1(client, _m(acc, 'min')), - pct10: createMetricPattern11(client, _m(acc, 'pct10')), - pct25: createMetricPattern11(client, _m(acc, 'pct25')), - pct75: createMetricPattern11(client, _m(acc, 'pct75')), - pct90: createMetricPattern11(client, _m(acc, 'pct90')), - txindex: createMetricPattern27(client, acc), - }; -} - /** * @template T * @typedef {Object} FullnessPattern @@ -2299,6 +2264,41 @@ function createFullnessPattern(client, acc) { }; } +/** + * @template T + * @typedef {Object} FeeRatePattern + * @property {MetricPattern1} average + * @property {MetricPattern1} max + * @property {MetricPattern11} median + * @property {MetricPattern1} min + * @property {MetricPattern11} pct10 + * @property {MetricPattern11} pct25 + * @property {MetricPattern11} pct75 + * @property {MetricPattern11} pct90 + * @property {MetricPattern27} txindex + */ + +/** + * Create a FeeRatePattern pattern node + * @template T + * @param {BrkClientBase} client + * @param {string} acc - Accumulated metric name + * @returns {FeeRatePattern} + */ +function createFeeRatePattern(client, acc) { + return { + average: createMetricPattern1(client, _m(acc, 'average')), + max: createMetricPattern1(client, _m(acc, 'max')), + median: createMetricPattern11(client, _m(acc, 'median')), + min: createMetricPattern1(client, _m(acc, 'min')), + pct10: createMetricPattern11(client, _m(acc, 'pct10')), + pct25: createMetricPattern11(client, _m(acc, 'pct25')), + pct75: createMetricPattern11(client, _m(acc, 'pct75')), + pct90: createMetricPattern11(client, _m(acc, 'pct90')), + txindex: createMetricPattern27(client, acc), + }; +} + /** * @typedef {Object} _0satsPattern * @property {ActivityPattern2} activity @@ -2363,64 +2363,6 @@ function createPhaseDailyCentsPattern(client, acc) { }; } -/** - * @typedef {Object} _10yTo12yPattern - * @property {ActivityPattern2} activity - * @property {CostBasisPattern2} costBasis - * @property {OutputsPattern} outputs - * @property {RealizedPattern2} realized - * @property {RelativePattern2} relative - * @property {SupplyPattern2} supply - * @property {UnrealizedPattern} unrealized - */ - -/** - * Create a _10yTo12yPattern pattern node - * @param {BrkClientBase} client - * @param {string} acc - Accumulated metric name - * @returns {_10yTo12yPattern} - */ -function create_10yTo12yPattern(client, acc) { - return { - activity: createActivityPattern2(client, acc), - costBasis: createCostBasisPattern2(client, acc), - outputs: createOutputsPattern(client, _m(acc, 'utxo_count')), - realized: createRealizedPattern2(client, acc), - relative: createRelativePattern2(client, acc), - supply: createSupplyPattern2(client, _m(acc, 'supply')), - unrealized: createUnrealizedPattern(client, acc), - }; -} - -/** - * @typedef {Object} UnrealizedPattern - * @property {MetricPattern1} negUnrealizedLoss - * @property {MetricPattern1} netUnrealizedPnl - * @property {ActiveSupplyPattern} supplyInLoss - * @property {ActiveSupplyPattern} supplyInProfit - * @property {MetricPattern1} totalUnrealizedPnl - * @property {MetricPattern1} unrealizedLoss - * @property {MetricPattern1} unrealizedProfit - */ - -/** - * Create a UnrealizedPattern pattern node - * @param {BrkClientBase} client - * @param {string} acc - Accumulated metric name - * @returns {UnrealizedPattern} - */ -function createUnrealizedPattern(client, acc) { - return { - negUnrealizedLoss: createMetricPattern1(client, _m(acc, 'neg_unrealized_loss')), - netUnrealizedPnl: createMetricPattern1(client, _m(acc, 'net_unrealized_pnl')), - supplyInLoss: createActiveSupplyPattern(client, _m(acc, 'supply_in_loss')), - supplyInProfit: createActiveSupplyPattern(client, _m(acc, 'supply_in_profit')), - totalUnrealizedPnl: createMetricPattern1(client, _m(acc, 'total_unrealized_pnl')), - unrealizedLoss: createMetricPattern1(client, _m(acc, 'unrealized_loss')), - unrealizedProfit: createMetricPattern1(client, _m(acc, 'unrealized_profit')), - }; -} - /** * @typedef {Object} _0satsPattern2 * @property {ActivityPattern2} activity @@ -2450,6 +2392,35 @@ function create_0satsPattern2(client, acc) { }; } +/** + * @typedef {Object} _10yTo12yPattern + * @property {ActivityPattern2} activity + * @property {CostBasisPattern2} costBasis + * @property {OutputsPattern} outputs + * @property {RealizedPattern2} realized + * @property {RelativePattern2} relative + * @property {SupplyPattern2} supply + * @property {UnrealizedPattern} unrealized + */ + +/** + * Create a _10yTo12yPattern pattern node + * @param {BrkClientBase} client + * @param {string} acc - Accumulated metric name + * @returns {_10yTo12yPattern} + */ +function create_10yTo12yPattern(client, acc) { + return { + activity: createActivityPattern2(client, acc), + costBasis: createCostBasisPattern2(client, acc), + outputs: createOutputsPattern(client, _m(acc, 'utxo_count')), + realized: createRealizedPattern2(client, acc), + relative: createRelativePattern2(client, acc), + supply: createSupplyPattern2(client, _m(acc, 'supply')), + unrealized: createUnrealizedPattern(client, acc), + }; +} + /** * @typedef {Object} PeriodCagrPattern * @property {MetricPattern4} _10y @@ -2479,6 +2450,35 @@ function createPeriodCagrPattern(client, acc) { }; } +/** + * @typedef {Object} UnrealizedPattern + * @property {MetricPattern1} negUnrealizedLoss + * @property {MetricPattern1} netUnrealizedPnl + * @property {ActiveSupplyPattern} supplyInLoss + * @property {ActiveSupplyPattern} supplyInProfit + * @property {MetricPattern1} totalUnrealizedPnl + * @property {MetricPattern1} unrealizedLoss + * @property {MetricPattern1} unrealizedProfit + */ + +/** + * Create a UnrealizedPattern pattern node + * @param {BrkClientBase} client + * @param {string} acc - Accumulated metric name + * @returns {UnrealizedPattern} + */ +function createUnrealizedPattern(client, acc) { + return { + negUnrealizedLoss: createMetricPattern1(client, _m(acc, 'neg_unrealized_loss')), + netUnrealizedPnl: createMetricPattern1(client, _m(acc, 'net_unrealized_pnl')), + supplyInLoss: createActiveSupplyPattern(client, _m(acc, 'supply_in_loss')), + supplyInProfit: createActiveSupplyPattern(client, _m(acc, 'supply_in_profit')), + totalUnrealizedPnl: createMetricPattern1(client, _m(acc, 'total_unrealized_pnl')), + unrealizedLoss: createMetricPattern1(client, _m(acc, 'unrealized_loss')), + unrealizedProfit: createMetricPattern1(client, _m(acc, 'unrealized_profit')), + }; +} + /** * @typedef {Object} _10yPattern * @property {ActivityPattern2} activity @@ -2588,65 +2588,23 @@ function createSplitPattern2(client, acc) { } /** - * @typedef {Object} UnclaimedRewardsPattern - * @property {BitcoinPattern2} bitcoin - * @property {BlockCountPattern} dollars - * @property {BlockCountPattern} sats + * @typedef {Object} _2015Pattern + * @property {MetricPattern4} bitcoin + * @property {MetricPattern4} dollars + * @property {MetricPattern4} sats */ /** - * Create a UnclaimedRewardsPattern pattern node + * Create a _2015Pattern pattern node * @param {BrkClientBase} client * @param {string} acc - Accumulated metric name - * @returns {UnclaimedRewardsPattern} + * @returns {_2015Pattern} */ -function createUnclaimedRewardsPattern(client, acc) { +function create_2015Pattern(client, acc) { return { - bitcoin: createBitcoinPattern2(client, _m(acc, 'btc')), - dollars: createBlockCountPattern(client, _m(acc, 'usd')), - sats: createBlockCountPattern(client, acc), - }; -} - -/** - * @typedef {Object} CoinbasePattern - * @property {BitcoinPattern} bitcoin - * @property {DollarsPattern} dollars - * @property {DollarsPattern} sats - */ - -/** - * Create a CoinbasePattern pattern node - * @param {BrkClientBase} client - * @param {string} acc - Accumulated metric name - * @returns {CoinbasePattern} - */ -function createCoinbasePattern(client, acc) { - return { - bitcoin: createBitcoinPattern(client, _m(acc, 'btc')), - dollars: createDollarsPattern(client, _m(acc, 'usd')), - sats: createDollarsPattern(client, acc), - }; -} - -/** - * @typedef {Object} SegwitAdoptionPattern - * @property {MetricPattern11} base - * @property {MetricPattern2} cumulative - * @property {MetricPattern2} sum - */ - -/** - * Create a SegwitAdoptionPattern pattern node - * @param {BrkClientBase} client - * @param {string} acc - Accumulated metric name - * @returns {SegwitAdoptionPattern} - */ -function createSegwitAdoptionPattern(client, acc) { - return { - base: createMetricPattern11(client, acc), - cumulative: createMetricPattern2(client, _m(acc, 'cumulative')), - sum: createMetricPattern2(client, _m(acc, 'sum')), + bitcoin: createMetricPattern4(client, _m(acc, 'btc')), + dollars: createMetricPattern4(client, _m(acc, 'usd')), + sats: createMetricPattern4(client, acc), }; } @@ -2672,44 +2630,23 @@ function createCostBasisPattern2(client, acc) { } /** - * @typedef {Object} ActiveSupplyPattern - * @property {MetricPattern1} bitcoin - * @property {MetricPattern1} dollars - * @property {MetricPattern1} sats + * @typedef {Object} CoinbasePattern + * @property {BitcoinPattern} bitcoin + * @property {DollarsPattern} dollars + * @property {DollarsPattern} sats */ /** - * Create a ActiveSupplyPattern pattern node + * Create a CoinbasePattern pattern node * @param {BrkClientBase} client * @param {string} acc - Accumulated metric name - * @returns {ActiveSupplyPattern} + * @returns {CoinbasePattern} */ -function createActiveSupplyPattern(client, acc) { +function createCoinbasePattern(client, acc) { return { - bitcoin: createMetricPattern1(client, _m(acc, 'btc')), - dollars: createMetricPattern1(client, _m(acc, 'usd')), - sats: createMetricPattern1(client, acc), - }; -} - -/** - * @typedef {Object} _2015Pattern - * @property {MetricPattern4} bitcoin - * @property {MetricPattern4} dollars - * @property {MetricPattern4} sats - */ - -/** - * Create a _2015Pattern pattern node - * @param {BrkClientBase} client - * @param {string} acc - Accumulated metric name - * @returns {_2015Pattern} - */ -function create_2015Pattern(client, acc) { - return { - bitcoin: createMetricPattern4(client, _m(acc, 'btc')), - dollars: createMetricPattern4(client, _m(acc, 'usd')), - sats: createMetricPattern4(client, acc), + bitcoin: createBitcoinPattern(client, _m(acc, 'btc')), + dollars: createDollarsPattern(client, _m(acc, 'usd')), + sats: createDollarsPattern(client, acc), }; } @@ -2735,40 +2672,65 @@ function createCoinbasePattern2(client, acc) { } /** - * @typedef {Object} _1dReturns1mSdPattern - * @property {MetricPattern4} sd - * @property {MetricPattern4} sma + * @typedef {Object} SegwitAdoptionPattern + * @property {MetricPattern11} base + * @property {MetricPattern2} cumulative + * @property {MetricPattern2} sum */ /** - * Create a _1dReturns1mSdPattern pattern node + * Create a SegwitAdoptionPattern pattern node * @param {BrkClientBase} client * @param {string} acc - Accumulated metric name - * @returns {_1dReturns1mSdPattern} + * @returns {SegwitAdoptionPattern} */ -function create_1dReturns1mSdPattern(client, acc) { +function createSegwitAdoptionPattern(client, acc) { return { - sd: createMetricPattern4(client, _m(acc, 'sd')), - sma: createMetricPattern4(client, _m(acc, 'sma')), + base: createMetricPattern11(client, acc), + cumulative: createMetricPattern2(client, _m(acc, 'cumulative')), + sum: createMetricPattern2(client, _m(acc, 'sum')), }; } /** - * @typedef {Object} CostBasisPattern - * @property {MetricPattern1} max - * @property {MetricPattern1} min + * @typedef {Object} ActiveSupplyPattern + * @property {MetricPattern1} bitcoin + * @property {MetricPattern1} dollars + * @property {MetricPattern1} sats */ /** - * Create a CostBasisPattern pattern node + * Create a ActiveSupplyPattern pattern node * @param {BrkClientBase} client * @param {string} acc - Accumulated metric name - * @returns {CostBasisPattern} + * @returns {ActiveSupplyPattern} */ -function createCostBasisPattern(client, acc) { +function createActiveSupplyPattern(client, acc) { return { - max: createMetricPattern1(client, _m(acc, 'max_cost_basis')), - min: createMetricPattern1(client, _m(acc, 'min_cost_basis')), + bitcoin: createMetricPattern1(client, _m(acc, 'btc')), + dollars: createMetricPattern1(client, _m(acc, 'usd')), + sats: createMetricPattern1(client, acc), + }; +} + +/** + * @typedef {Object} UnclaimedRewardsPattern + * @property {BitcoinPattern2} bitcoin + * @property {BlockCountPattern} dollars + * @property {BlockCountPattern} sats + */ + +/** + * Create a UnclaimedRewardsPattern pattern node + * @param {BrkClientBase} client + * @param {string} acc - Accumulated metric name + * @returns {UnclaimedRewardsPattern} + */ +function createUnclaimedRewardsPattern(client, acc) { + return { + bitcoin: createBitcoinPattern2(client, _m(acc, 'btc')), + dollars: createBlockCountPattern(client, _m(acc, 'usd')), + sats: createBlockCountPattern(client, acc), }; } @@ -2811,23 +2773,40 @@ function createRelativePattern4(client, acc) { } /** - * @template T - * @typedef {Object} BitcoinPattern2 - * @property {MetricPattern2} cumulative - * @property {MetricPattern1} sum + * @typedef {Object} CostBasisPattern + * @property {MetricPattern1} max + * @property {MetricPattern1} min */ /** - * Create a BitcoinPattern2 pattern node - * @template T + * Create a CostBasisPattern pattern node * @param {BrkClientBase} client * @param {string} acc - Accumulated metric name - * @returns {BitcoinPattern2} + * @returns {CostBasisPattern} */ -function createBitcoinPattern2(client, acc) { +function createCostBasisPattern(client, acc) { return { - cumulative: createMetricPattern2(client, _m(acc, 'cumulative')), - sum: createMetricPattern1(client, acc), + max: createMetricPattern1(client, _m(acc, 'max_cost_basis')), + min: createMetricPattern1(client, _m(acc, 'min_cost_basis')), + }; +} + +/** + * @typedef {Object} _1dReturns1mSdPattern + * @property {MetricPattern4} sd + * @property {MetricPattern4} sma + */ + +/** + * Create a _1dReturns1mSdPattern pattern node + * @param {BrkClientBase} client + * @param {string} acc - Accumulated metric name + * @returns {_1dReturns1mSdPattern} + */ +function create_1dReturns1mSdPattern(client, acc) { + return { + sd: createMetricPattern4(client, _m(acc, 'sd')), + sma: createMetricPattern4(client, _m(acc, 'sma')), }; } @@ -2874,19 +2853,23 @@ function createBlockCountPattern(client, acc) { } /** - * @typedef {Object} OutputsPattern - * @property {MetricPattern1} utxoCount + * @template T + * @typedef {Object} BitcoinPattern2 + * @property {MetricPattern2} cumulative + * @property {MetricPattern1} sum */ /** - * Create a OutputsPattern pattern node + * Create a BitcoinPattern2 pattern node + * @template T * @param {BrkClientBase} client * @param {string} acc - Accumulated metric name - * @returns {OutputsPattern} + * @returns {BitcoinPattern2} */ -function createOutputsPattern(client, acc) { +function createBitcoinPattern2(client, acc) { return { - utxoCount: createMetricPattern1(client, acc), + cumulative: createMetricPattern2(client, _m(acc, 'cumulative')), + sum: createMetricPattern1(client, acc), }; } @@ -2907,6 +2890,23 @@ function createRealizedPriceExtraPattern(client, acc) { }; } +/** + * @typedef {Object} OutputsPattern + * @property {MetricPattern1} utxoCount + */ + +/** + * Create a OutputsPattern pattern node + * @param {BrkClientBase} client + * @param {string} acc - Accumulated metric name + * @returns {OutputsPattern} + */ +function createOutputsPattern(client, acc) { + return { + utxoCount: createMetricPattern1(client, acc), + }; +} + // Catalog tree typedefs /** @@ -3711,8 +3711,8 @@ function createRealizedPriceExtraPattern(client, acc) { /** * @typedef {Object} MetricsTree_Market_Dca - * @property {ClassAveragePricePattern} classAveragePrice - * @property {MetricsTree_Market_Dca_ClassReturns} classReturns + * @property {MetricsTree_Market_Dca_ClassAveragePrice} classAveragePrice + * @property {ClassAveragePricePattern} classReturns * @property {MetricsTree_Market_Dca_ClassStack} classStack * @property {PeriodAveragePricePattern} periodAveragePrice * @property {PeriodCagrPattern} periodCagr @@ -3722,19 +3722,19 @@ function createRealizedPriceExtraPattern(client, acc) { */ /** - * @typedef {Object} MetricsTree_Market_Dca_ClassReturns - * @property {MetricPattern4} _2015 - * @property {MetricPattern4} _2016 - * @property {MetricPattern4} _2017 - * @property {MetricPattern4} _2018 - * @property {MetricPattern4} _2019 - * @property {MetricPattern4} _2020 - * @property {MetricPattern4} _2021 - * @property {MetricPattern4} _2022 - * @property {MetricPattern4} _2023 - * @property {MetricPattern4} _2024 - * @property {MetricPattern4} _2025 - * @property {MetricPattern4} _2026 + * @typedef {Object} MetricsTree_Market_Dca_ClassAveragePrice + * @property {MetricPattern4} _2015 + * @property {MetricPattern4} _2016 + * @property {MetricPattern4} _2017 + * @property {MetricPattern4} _2018 + * @property {MetricPattern4} _2019 + * @property {MetricPattern4} _2020 + * @property {MetricPattern4} _2021 + * @property {MetricPattern4} _2022 + * @property {MetricPattern4} _2023 + * @property {MetricPattern4} _2024 + * @property {MetricPattern4} _2025 + * @property {MetricPattern4} _2026 */ /** @@ -4281,7 +4281,7 @@ function createRealizedPriceExtraPattern(client, acc) { * @extends BrkClientBase */ class BrkClient extends BrkClientBase { - VERSION = "v0.1.0-alpha.6"; + VERSION = "v0.1.0-beta.0"; INDEXES = /** @type {const} */ ([ "dateindex", @@ -4491,27 +4491,27 @@ class BrkClient extends BrkClientBase { EPOCH_NAMES = /** @type {const} */ ({ "_0": { "id": "epoch_0", - "short": "Epoch 0", + "short": "0", "long": "Epoch 0" }, "_1": { "id": "epoch_1", - "short": "Epoch 1", + "short": "1", "long": "Epoch 1" }, "_2": { "id": "epoch_2", - "short": "Epoch 2", + "short": "2", "long": "Epoch 2" }, "_3": { "id": "epoch_3", - "short": "Epoch 3", + "short": "3", "long": "Epoch 3" }, "_4": { "id": "epoch_4", - "short": "Epoch 4", + "short": "4", "long": "Epoch 4" } }); @@ -4970,67 +4970,67 @@ class BrkClient extends BrkClientBase { "_1satTo10sats": { "id": "above_1sat_under_10sats", "short": "1-10 sats", - "long": "1 to 10 Sats" + "long": "1-10 Sats" }, "_10satsTo100sats": { "id": "above_10sats_under_100sats", "short": "10-100 sats", - "long": "10 to 100 Sats" + "long": "10-100 Sats" }, "_100satsTo1kSats": { "id": "above_100sats_under_1k_sats", "short": "100-1k sats", - "long": "100 to 1K Sats" + "long": "100-1K Sats" }, "_1kSatsTo10kSats": { "id": "above_1k_sats_under_10k_sats", "short": "1k-10k sats", - "long": "1K to 10K Sats" + "long": "1K-10K Sats" }, "_10kSatsTo100kSats": { "id": "above_10k_sats_under_100k_sats", "short": "10k-100k sats", - "long": "10K to 100K Sats" + "long": "10K-100K Sats" }, "_100kSatsTo1mSats": { "id": "above_100k_sats_under_1m_sats", "short": "100k-1M sats", - "long": "100K to 1M Sats" + "long": "100K-1M Sats" }, "_1mSatsTo10mSats": { "id": "above_1m_sats_under_10m_sats", "short": "1M-10M sats", - "long": "1M to 10M Sats" + "long": "1M-10M Sats" }, "_10mSatsTo1btc": { "id": "above_10m_sats_under_1btc", "short": "0.1-1 BTC", - "long": "0.1 to 1 BTC" + "long": "0.1-1 BTC" }, "_1btcTo10btc": { "id": "above_1btc_under_10btc", "short": "1-10 BTC", - "long": "1 to 10 BTC" + "long": "1-10 BTC" }, "_10btcTo100btc": { "id": "above_10btc_under_100btc", "short": "10-100 BTC", - "long": "10 to 100 BTC" + "long": "10-100 BTC" }, "_100btcTo1kBtc": { "id": "above_100btc_under_1k_btc", "short": "100-1k BTC", - "long": "100 to 1K BTC" + "long": "100-1K BTC" }, "_1kBtcTo10kBtc": { "id": "above_1k_btc_under_10k_btc", "short": "1k-10k BTC", - "long": "1K to 10K BTC" + "long": "1K-10K BTC" }, "_10kBtcTo100kBtc": { "id": "above_10k_btc_under_100k_btc", "short": "10k-100k BTC", - "long": "10K to 100K BTC" + "long": "10K-100K BTC" }, "_100kBtcOrMore": { "id": "above_100k_btc", @@ -5754,21 +5754,21 @@ class BrkClient extends BrkClientBase { yearsSincePriceAth: createMetricPattern4(this, 'years_since_price_ath'), }, dca: { - classAveragePrice: createClassAveragePricePattern(this, 'dca_class'), - classReturns: { - _2015: createMetricPattern4(this, 'dca_class_2015_returns'), - _2016: createMetricPattern4(this, 'dca_class_2016_returns'), - _2017: createMetricPattern4(this, 'dca_class_2017_returns'), - _2018: createMetricPattern4(this, 'dca_class_2018_returns'), - _2019: createMetricPattern4(this, 'dca_class_2019_returns'), - _2020: createMetricPattern4(this, 'dca_class_2020_returns'), - _2021: createMetricPattern4(this, 'dca_class_2021_returns'), - _2022: createMetricPattern4(this, 'dca_class_2022_returns'), - _2023: createMetricPattern4(this, 'dca_class_2023_returns'), - _2024: createMetricPattern4(this, 'dca_class_2024_returns'), - _2025: createMetricPattern4(this, 'dca_class_2025_returns'), - _2026: createMetricPattern4(this, 'dca_class_2026_returns'), + classAveragePrice: { + _2015: createMetricPattern4(this, 'dca_class_2015_average_price'), + _2016: createMetricPattern4(this, 'dca_class_2016_average_price'), + _2017: createMetricPattern4(this, 'dca_class_2017_average_price'), + _2018: createMetricPattern4(this, 'dca_class_2018_average_price'), + _2019: createMetricPattern4(this, 'dca_class_2019_average_price'), + _2020: createMetricPattern4(this, 'dca_class_2020_average_price'), + _2021: createMetricPattern4(this, 'dca_class_2021_average_price'), + _2022: createMetricPattern4(this, 'dca_class_2022_average_price'), + _2023: createMetricPattern4(this, 'dca_class_2023_average_price'), + _2024: createMetricPattern4(this, 'dca_class_2024_average_price'), + _2025: createMetricPattern4(this, 'dca_class_2025_average_price'), + _2026: createMetricPattern4(this, 'dca_class_2026_average_price'), }, + classReturns: createClassAveragePricePattern(this, 'dca_class'), classStack: { _2015: create_2015Pattern(this, 'dca_class_2015_stack'), _2016: create_2015Pattern(this, 'dca_class_2016_stack'), diff --git a/packages/brk_client/brk_client/__init__.py b/packages/brk_client/brk_client/__init__.py index 884c33813..68cb3db5c 100644 --- a/packages/brk_client/brk_client/__init__.py +++ b/packages/brk_client/brk_client/__init__.py @@ -2054,6 +2054,24 @@ class PeriodLumpSumStackPattern: self._6y: _2015Pattern = _2015Pattern(client, _p('6y', acc)) self._8y: _2015Pattern = _2015Pattern(client, _p('8y', acc)) +class ClassAveragePricePattern(Generic[T]): + """Pattern struct for repeated tree structure.""" + + def __init__(self, client: BrkClientBase, acc: str): + """Create pattern node with accumulated metric name.""" + self._2015: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2015_returns')) + self._2016: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2016_returns')) + self._2017: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2017_returns')) + self._2018: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2018_returns')) + self._2019: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2019_returns')) + self._2020: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2020_returns')) + self._2021: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2021_returns')) + self._2022: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2022_returns')) + self._2023: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2023_returns')) + self._2024: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2024_returns')) + self._2025: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2025_returns')) + self._2026: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2026_returns')) + class PeriodAveragePricePattern(Generic[T]): """Pattern struct for repeated tree structure.""" @@ -2072,24 +2090,6 @@ class PeriodAveragePricePattern(Generic[T]): self._6y: MetricPattern4[T] = MetricPattern4(client, _p('6y', acc)) self._8y: MetricPattern4[T] = MetricPattern4(client, _p('8y', acc)) -class ClassAveragePricePattern(Generic[T]): - """Pattern struct for repeated tree structure.""" - - def __init__(self, client: BrkClientBase, acc: str): - """Create pattern node with accumulated metric name.""" - self._2015: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2015_average_price')) - self._2016: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2016_average_price')) - self._2017: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2017_average_price')) - self._2018: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2018_average_price')) - self._2019: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2019_average_price')) - self._2020: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2020_average_price')) - self._2021: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2021_average_price')) - self._2022: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2022_average_price')) - self._2023: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2023_average_price')) - self._2024: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2024_average_price')) - self._2025: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2025_average_price')) - self._2026: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2026_average_price')) - class BitcoinPattern: """Pattern struct for repeated tree structure.""" @@ -2124,22 +2124,6 @@ class DollarsPattern(Generic[T]): self.pct90: MetricPattern6[T] = MetricPattern6(client, _m(acc, 'pct90')) self.sum: MetricPattern2[T] = MetricPattern2(client, _m(acc, 'sum')) -class RelativePattern2: - """Pattern struct for repeated tree structure.""" - - def __init__(self, client: BrkClientBase, acc: str): - """Create pattern node with accumulated metric name.""" - self.neg_unrealized_loss_rel_to_own_market_cap: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'neg_unrealized_loss_rel_to_own_market_cap')) - self.neg_unrealized_loss_rel_to_own_total_unrealized_pnl: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'neg_unrealized_loss_rel_to_own_total_unrealized_pnl')) - self.net_unrealized_pnl_rel_to_own_market_cap: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'net_unrealized_pnl_rel_to_own_market_cap')) - self.net_unrealized_pnl_rel_to_own_total_unrealized_pnl: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'net_unrealized_pnl_rel_to_own_total_unrealized_pnl')) - self.supply_in_loss_rel_to_own_supply: MetricPattern1[StoredF64] = MetricPattern1(client, _m(acc, 'supply_in_loss_rel_to_own_supply')) - self.supply_in_profit_rel_to_own_supply: MetricPattern1[StoredF64] = MetricPattern1(client, _m(acc, 'supply_in_profit_rel_to_own_supply')) - self.unrealized_loss_rel_to_own_market_cap: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'unrealized_loss_rel_to_own_market_cap')) - self.unrealized_loss_rel_to_own_total_unrealized_pnl: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'unrealized_loss_rel_to_own_total_unrealized_pnl')) - self.unrealized_profit_rel_to_own_market_cap: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'unrealized_profit_rel_to_own_market_cap')) - self.unrealized_profit_rel_to_own_total_unrealized_pnl: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'unrealized_profit_rel_to_own_total_unrealized_pnl')) - class RelativePattern: """Pattern struct for repeated tree structure.""" @@ -2156,6 +2140,22 @@ class RelativePattern: self.unrealized_loss_rel_to_market_cap: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'unrealized_loss_rel_to_market_cap')) self.unrealized_profit_rel_to_market_cap: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'unrealized_profit_rel_to_market_cap')) +class RelativePattern2: + """Pattern struct for repeated tree structure.""" + + def __init__(self, client: BrkClientBase, acc: str): + """Create pattern node with accumulated metric name.""" + self.neg_unrealized_loss_rel_to_own_market_cap: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'neg_unrealized_loss_rel_to_own_market_cap')) + self.neg_unrealized_loss_rel_to_own_total_unrealized_pnl: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'neg_unrealized_loss_rel_to_own_total_unrealized_pnl')) + self.net_unrealized_pnl_rel_to_own_market_cap: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'net_unrealized_pnl_rel_to_own_market_cap')) + self.net_unrealized_pnl_rel_to_own_total_unrealized_pnl: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'net_unrealized_pnl_rel_to_own_total_unrealized_pnl')) + self.supply_in_loss_rel_to_own_supply: MetricPattern1[StoredF64] = MetricPattern1(client, _m(acc, 'supply_in_loss_rel_to_own_supply')) + self.supply_in_profit_rel_to_own_supply: MetricPattern1[StoredF64] = MetricPattern1(client, _m(acc, 'supply_in_profit_rel_to_own_supply')) + self.unrealized_loss_rel_to_own_market_cap: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'unrealized_loss_rel_to_own_market_cap')) + self.unrealized_loss_rel_to_own_total_unrealized_pnl: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'unrealized_loss_rel_to_own_total_unrealized_pnl')) + self.unrealized_profit_rel_to_own_market_cap: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'unrealized_profit_rel_to_own_market_cap')) + self.unrealized_profit_rel_to_own_total_unrealized_pnl: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'unrealized_profit_rel_to_own_total_unrealized_pnl')) + class CountPattern2(Generic[T]): """Pattern struct for repeated tree structure.""" @@ -2187,21 +2187,6 @@ class AddrCountPattern: self.p2wpkh: MetricPattern1[StoredU64] = MetricPattern1(client, _p('p2wpkh', acc)) self.p2wsh: MetricPattern1[StoredU64] = MetricPattern1(client, _p('p2wsh', acc)) -class FeeRatePattern(Generic[T]): - """Pattern struct for repeated tree structure.""" - - def __init__(self, client: BrkClientBase, acc: str): - """Create pattern node with accumulated metric name.""" - self.average: MetricPattern1[T] = MetricPattern1(client, _m(acc, 'average')) - self.max: MetricPattern1[T] = MetricPattern1(client, _m(acc, 'max')) - self.median: MetricPattern11[T] = MetricPattern11(client, _m(acc, 'median')) - self.min: MetricPattern1[T] = MetricPattern1(client, _m(acc, 'min')) - self.pct10: MetricPattern11[T] = MetricPattern11(client, _m(acc, 'pct10')) - self.pct25: MetricPattern11[T] = MetricPattern11(client, _m(acc, 'pct25')) - self.pct75: MetricPattern11[T] = MetricPattern11(client, _m(acc, 'pct75')) - self.pct90: MetricPattern11[T] = MetricPattern11(client, _m(acc, 'pct90')) - self.txindex: MetricPattern27[T] = MetricPattern27(client, acc) - class FullnessPattern(Generic[T]): """Pattern struct for repeated tree structure.""" @@ -2217,6 +2202,21 @@ class FullnessPattern(Generic[T]): self.pct75: MetricPattern6[T] = MetricPattern6(client, _m(acc, 'pct75')) self.pct90: MetricPattern6[T] = MetricPattern6(client, _m(acc, 'pct90')) +class FeeRatePattern(Generic[T]): + """Pattern struct for repeated tree structure.""" + + def __init__(self, client: BrkClientBase, acc: str): + """Create pattern node with accumulated metric name.""" + self.average: MetricPattern1[T] = MetricPattern1(client, _m(acc, 'average')) + self.max: MetricPattern1[T] = MetricPattern1(client, _m(acc, 'max')) + self.median: MetricPattern11[T] = MetricPattern11(client, _m(acc, 'median')) + self.min: MetricPattern1[T] = MetricPattern1(client, _m(acc, 'min')) + self.pct10: MetricPattern11[T] = MetricPattern11(client, _m(acc, 'pct10')) + self.pct25: MetricPattern11[T] = MetricPattern11(client, _m(acc, 'pct25')) + self.pct75: MetricPattern11[T] = MetricPattern11(client, _m(acc, 'pct75')) + self.pct90: MetricPattern11[T] = MetricPattern11(client, _m(acc, 'pct90')) + self.txindex: MetricPattern27[T] = MetricPattern27(client, acc) + class _0satsPattern: """Pattern struct for repeated tree structure.""" @@ -2245,32 +2245,6 @@ class PhaseDailyCentsPattern(Generic[T]): self.pct75: MetricPattern6[T] = MetricPattern6(client, _m(acc, 'pct75')) self.pct90: MetricPattern6[T] = MetricPattern6(client, _m(acc, 'pct90')) -class _10yTo12yPattern: - """Pattern struct for repeated tree structure.""" - - def __init__(self, client: BrkClientBase, acc: str): - """Create pattern node with accumulated metric name.""" - self.activity: ActivityPattern2 = ActivityPattern2(client, acc) - self.cost_basis: CostBasisPattern2 = CostBasisPattern2(client, acc) - self.outputs: OutputsPattern = OutputsPattern(client, _m(acc, 'utxo_count')) - self.realized: RealizedPattern2 = RealizedPattern2(client, acc) - self.relative: RelativePattern2 = RelativePattern2(client, acc) - self.supply: SupplyPattern2 = SupplyPattern2(client, _m(acc, 'supply')) - self.unrealized: UnrealizedPattern = UnrealizedPattern(client, acc) - -class UnrealizedPattern: - """Pattern struct for repeated tree structure.""" - - def __init__(self, client: BrkClientBase, acc: str): - """Create pattern node with accumulated metric name.""" - self.neg_unrealized_loss: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'neg_unrealized_loss')) - self.net_unrealized_pnl: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'net_unrealized_pnl')) - self.supply_in_loss: ActiveSupplyPattern = ActiveSupplyPattern(client, _m(acc, 'supply_in_loss')) - self.supply_in_profit: ActiveSupplyPattern = ActiveSupplyPattern(client, _m(acc, 'supply_in_profit')) - self.total_unrealized_pnl: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'total_unrealized_pnl')) - self.unrealized_loss: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'unrealized_loss')) - self.unrealized_profit: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'unrealized_profit')) - class _0satsPattern2: """Pattern struct for repeated tree structure.""" @@ -2284,6 +2258,19 @@ class _0satsPattern2: self.supply: SupplyPattern2 = SupplyPattern2(client, _m(acc, 'supply')) self.unrealized: UnrealizedPattern = UnrealizedPattern(client, acc) +class _10yTo12yPattern: + """Pattern struct for repeated tree structure.""" + + def __init__(self, client: BrkClientBase, acc: str): + """Create pattern node with accumulated metric name.""" + self.activity: ActivityPattern2 = ActivityPattern2(client, acc) + self.cost_basis: CostBasisPattern2 = CostBasisPattern2(client, acc) + self.outputs: OutputsPattern = OutputsPattern(client, _m(acc, 'utxo_count')) + self.realized: RealizedPattern2 = RealizedPattern2(client, acc) + self.relative: RelativePattern2 = RelativePattern2(client, acc) + self.supply: SupplyPattern2 = SupplyPattern2(client, _m(acc, 'supply')) + self.unrealized: UnrealizedPattern = UnrealizedPattern(client, acc) + class PeriodCagrPattern: """Pattern struct for repeated tree structure.""" @@ -2297,6 +2284,19 @@ class PeriodCagrPattern: self._6y: MetricPattern4[StoredF32] = MetricPattern4(client, _p('6y', acc)) self._8y: MetricPattern4[StoredF32] = MetricPattern4(client, _p('8y', acc)) +class UnrealizedPattern: + """Pattern struct for repeated tree structure.""" + + def __init__(self, client: BrkClientBase, acc: str): + """Create pattern node with accumulated metric name.""" + self.neg_unrealized_loss: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'neg_unrealized_loss')) + self.net_unrealized_pnl: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'net_unrealized_pnl')) + self.supply_in_loss: ActiveSupplyPattern = ActiveSupplyPattern(client, _m(acc, 'supply_in_loss')) + self.supply_in_profit: ActiveSupplyPattern = ActiveSupplyPattern(client, _m(acc, 'supply_in_profit')) + self.total_unrealized_pnl: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'total_unrealized_pnl')) + self.unrealized_loss: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'unrealized_loss')) + self.unrealized_profit: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'unrealized_profit')) + class _10yPattern: """Pattern struct for repeated tree structure.""" @@ -2344,32 +2344,14 @@ class SplitPattern2(Generic[T]): self.low: MetricPattern1[T] = MetricPattern1(client, _m(acc, 'low')) self.open: MetricPattern1[T] = MetricPattern1(client, _m(acc, 'open')) -class UnclaimedRewardsPattern: +class _2015Pattern: """Pattern struct for repeated tree structure.""" def __init__(self, client: BrkClientBase, acc: str): """Create pattern node with accumulated metric name.""" - self.bitcoin: BitcoinPattern2[Bitcoin] = BitcoinPattern2(client, _m(acc, 'btc')) - self.dollars: BlockCountPattern[Dollars] = BlockCountPattern(client, _m(acc, 'usd')) - self.sats: BlockCountPattern[Sats] = BlockCountPattern(client, acc) - -class CoinbasePattern: - """Pattern struct for repeated tree structure.""" - - def __init__(self, client: BrkClientBase, acc: str): - """Create pattern node with accumulated metric name.""" - self.bitcoin: BitcoinPattern = BitcoinPattern(client, _m(acc, 'btc')) - self.dollars: DollarsPattern[Dollars] = DollarsPattern(client, _m(acc, 'usd')) - self.sats: DollarsPattern[Sats] = DollarsPattern(client, acc) - -class SegwitAdoptionPattern: - """Pattern struct for repeated tree structure.""" - - def __init__(self, client: BrkClientBase, acc: str): - """Create pattern node with accumulated metric name.""" - self.base: MetricPattern11[StoredF32] = MetricPattern11(client, acc) - self.cumulative: MetricPattern2[StoredF32] = MetricPattern2(client, _m(acc, 'cumulative')) - self.sum: MetricPattern2[StoredF32] = MetricPattern2(client, _m(acc, 'sum')) + self.bitcoin: MetricPattern4[Bitcoin] = MetricPattern4(client, _m(acc, 'btc')) + self.dollars: MetricPattern4[Dollars] = MetricPattern4(client, _m(acc, 'usd')) + self.sats: MetricPattern4[Sats] = MetricPattern4(client, acc) class CostBasisPattern2: """Pattern struct for repeated tree structure.""" @@ -2380,23 +2362,14 @@ class CostBasisPattern2: self.min: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'min_cost_basis')) self.percentiles: PercentilesPattern = PercentilesPattern(client, _m(acc, 'cost_basis')) -class ActiveSupplyPattern: +class CoinbasePattern: """Pattern struct for repeated tree structure.""" def __init__(self, client: BrkClientBase, acc: str): """Create pattern node with accumulated metric name.""" - self.bitcoin: MetricPattern1[Bitcoin] = MetricPattern1(client, _m(acc, 'btc')) - self.dollars: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'usd')) - self.sats: MetricPattern1[Sats] = MetricPattern1(client, acc) - -class _2015Pattern: - """Pattern struct for repeated tree structure.""" - - def __init__(self, client: BrkClientBase, acc: str): - """Create pattern node with accumulated metric name.""" - self.bitcoin: MetricPattern4[Bitcoin] = MetricPattern4(client, _m(acc, 'btc')) - self.dollars: MetricPattern4[Dollars] = MetricPattern4(client, _m(acc, 'usd')) - self.sats: MetricPattern4[Sats] = MetricPattern4(client, acc) + self.bitcoin: BitcoinPattern = BitcoinPattern(client, _m(acc, 'btc')) + self.dollars: DollarsPattern[Dollars] = DollarsPattern(client, _m(acc, 'usd')) + self.sats: DollarsPattern[Sats] = DollarsPattern(client, acc) class CoinbasePattern2: """Pattern struct for repeated tree structure.""" @@ -2407,21 +2380,32 @@ class CoinbasePattern2: self.dollars: BlockCountPattern[Dollars] = BlockCountPattern(client, _m(acc, 'usd')) self.sats: BlockCountPattern[Sats] = BlockCountPattern(client, acc) -class _1dReturns1mSdPattern: +class SegwitAdoptionPattern: """Pattern struct for repeated tree structure.""" def __init__(self, client: BrkClientBase, acc: str): """Create pattern node with accumulated metric name.""" - self.sd: MetricPattern4[StoredF32] = MetricPattern4(client, _m(acc, 'sd')) - self.sma: MetricPattern4[StoredF32] = MetricPattern4(client, _m(acc, 'sma')) + self.base: MetricPattern11[StoredF32] = MetricPattern11(client, acc) + self.cumulative: MetricPattern2[StoredF32] = MetricPattern2(client, _m(acc, 'cumulative')) + self.sum: MetricPattern2[StoredF32] = MetricPattern2(client, _m(acc, 'sum')) -class CostBasisPattern: +class ActiveSupplyPattern: """Pattern struct for repeated tree structure.""" def __init__(self, client: BrkClientBase, acc: str): """Create pattern node with accumulated metric name.""" - self.max: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'max_cost_basis')) - self.min: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'min_cost_basis')) + self.bitcoin: MetricPattern1[Bitcoin] = MetricPattern1(client, _m(acc, 'btc')) + self.dollars: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'usd')) + self.sats: MetricPattern1[Sats] = MetricPattern1(client, acc) + +class UnclaimedRewardsPattern: + """Pattern struct for repeated tree structure.""" + + def __init__(self, client: BrkClientBase, acc: str): + """Create pattern node with accumulated metric name.""" + self.bitcoin: BitcoinPattern2[Bitcoin] = BitcoinPattern2(client, _m(acc, 'btc')) + self.dollars: BlockCountPattern[Dollars] = BlockCountPattern(client, _m(acc, 'usd')) + self.sats: BlockCountPattern[Sats] = BlockCountPattern(client, acc) class SupplyPattern2: """Pattern struct for repeated tree structure.""" @@ -2439,13 +2423,21 @@ class RelativePattern4: self.supply_in_loss_rel_to_own_supply: MetricPattern1[StoredF64] = MetricPattern1(client, _m(acc, 'loss_rel_to_own_supply')) self.supply_in_profit_rel_to_own_supply: MetricPattern1[StoredF64] = MetricPattern1(client, _m(acc, 'profit_rel_to_own_supply')) -class BitcoinPattern2(Generic[T]): +class CostBasisPattern: """Pattern struct for repeated tree structure.""" def __init__(self, client: BrkClientBase, acc: str): """Create pattern node with accumulated metric name.""" - self.cumulative: MetricPattern2[T] = MetricPattern2(client, _m(acc, 'cumulative')) - self.sum: MetricPattern1[T] = MetricPattern1(client, acc) + self.max: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'max_cost_basis')) + self.min: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'min_cost_basis')) + +class _1dReturns1mSdPattern: + """Pattern struct for repeated tree structure.""" + + def __init__(self, client: BrkClientBase, acc: str): + """Create pattern node with accumulated metric name.""" + self.sd: MetricPattern4[StoredF32] = MetricPattern4(client, _m(acc, 'sd')) + self.sma: MetricPattern4[StoredF32] = MetricPattern4(client, _m(acc, 'sma')) class SatsPattern(Generic[T]): """Pattern struct for repeated tree structure.""" @@ -2463,12 +2455,13 @@ class BlockCountPattern(Generic[T]): self.cumulative: MetricPattern1[T] = MetricPattern1(client, _m(acc, 'cumulative')) self.sum: MetricPattern1[T] = MetricPattern1(client, acc) -class OutputsPattern: +class BitcoinPattern2(Generic[T]): """Pattern struct for repeated tree structure.""" def __init__(self, client: BrkClientBase, acc: str): """Create pattern node with accumulated metric name.""" - self.utxo_count: MetricPattern1[StoredU64] = MetricPattern1(client, acc) + self.cumulative: MetricPattern2[T] = MetricPattern2(client, _m(acc, 'cumulative')) + self.sum: MetricPattern1[T] = MetricPattern1(client, acc) class RealizedPriceExtraPattern: """Pattern struct for repeated tree structure.""" @@ -2477,6 +2470,13 @@ class RealizedPriceExtraPattern: """Create pattern node with accumulated metric name.""" self.ratio: MetricPattern4[StoredF32] = MetricPattern4(client, acc) +class OutputsPattern: + """Pattern struct for repeated tree structure.""" + + def __init__(self, client: BrkClientBase, acc: str): + """Create pattern node with accumulated metric name.""" + self.utxo_count: MetricPattern1[StoredU64] = MetricPattern1(client, acc) + # Metrics tree classes class MetricsTree_Addresses: @@ -3320,22 +3320,22 @@ class MetricsTree_Market_Ath: self.price_drawdown: MetricPattern3[StoredF32] = MetricPattern3(client, 'price_drawdown') self.years_since_price_ath: MetricPattern4[StoredF32] = MetricPattern4(client, 'years_since_price_ath') -class MetricsTree_Market_Dca_ClassReturns: +class MetricsTree_Market_Dca_ClassAveragePrice: """Metrics tree node.""" def __init__(self, client: BrkClientBase, base_path: str = ''): - self._2015: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2015_returns') - self._2016: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2016_returns') - self._2017: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2017_returns') - self._2018: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2018_returns') - self._2019: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2019_returns') - self._2020: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2020_returns') - self._2021: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2021_returns') - self._2022: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2022_returns') - self._2023: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2023_returns') - self._2024: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2024_returns') - self._2025: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2025_returns') - self._2026: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2026_returns') + self._2015: MetricPattern4[Dollars] = MetricPattern4(client, 'dca_class_2015_average_price') + self._2016: MetricPattern4[Dollars] = MetricPattern4(client, 'dca_class_2016_average_price') + self._2017: MetricPattern4[Dollars] = MetricPattern4(client, 'dca_class_2017_average_price') + self._2018: MetricPattern4[Dollars] = MetricPattern4(client, 'dca_class_2018_average_price') + self._2019: MetricPattern4[Dollars] = MetricPattern4(client, 'dca_class_2019_average_price') + self._2020: MetricPattern4[Dollars] = MetricPattern4(client, 'dca_class_2020_average_price') + self._2021: MetricPattern4[Dollars] = MetricPattern4(client, 'dca_class_2021_average_price') + self._2022: MetricPattern4[Dollars] = MetricPattern4(client, 'dca_class_2022_average_price') + self._2023: MetricPattern4[Dollars] = MetricPattern4(client, 'dca_class_2023_average_price') + self._2024: MetricPattern4[Dollars] = MetricPattern4(client, 'dca_class_2024_average_price') + self._2025: MetricPattern4[Dollars] = MetricPattern4(client, 'dca_class_2025_average_price') + self._2026: MetricPattern4[Dollars] = MetricPattern4(client, 'dca_class_2026_average_price') class MetricsTree_Market_Dca_ClassStack: """Metrics tree node.""" @@ -3358,8 +3358,8 @@ class MetricsTree_Market_Dca: """Metrics tree node.""" def __init__(self, client: BrkClientBase, base_path: str = ''): - self.class_average_price: ClassAveragePricePattern[Dollars] = ClassAveragePricePattern(client, 'dca_class') - self.class_returns: MetricsTree_Market_Dca_ClassReturns = MetricsTree_Market_Dca_ClassReturns(client) + self.class_average_price: MetricsTree_Market_Dca_ClassAveragePrice = MetricsTree_Market_Dca_ClassAveragePrice(client) + self.class_returns: ClassAveragePricePattern[StoredF32] = ClassAveragePricePattern(client, 'dca_class') self.class_stack: MetricsTree_Market_Dca_ClassStack = MetricsTree_Market_Dca_ClassStack(client) self.period_average_price: PeriodAveragePricePattern[Dollars] = PeriodAveragePricePattern(client, 'dca_average_price') self.period_cagr: PeriodCagrPattern = PeriodCagrPattern(client, 'dca_cagr') @@ -3958,7 +3958,7 @@ class MetricsTree: class BrkClient(BrkClientBase): """Main BRK client with metrics tree and API methods.""" - VERSION = "v0.1.0-alpha.6" + VERSION = "v0.1.0-beta.0" INDEXES = [ "dateindex", @@ -4168,27 +4168,27 @@ class BrkClient(BrkClientBase): EPOCH_NAMES = { "_0": { "id": "epoch_0", - "short": "Epoch 0", + "short": "0", "long": "Epoch 0" }, "_1": { "id": "epoch_1", - "short": "Epoch 1", + "short": "1", "long": "Epoch 1" }, "_2": { "id": "epoch_2", - "short": "Epoch 2", + "short": "2", "long": "Epoch 2" }, "_3": { "id": "epoch_3", - "short": "Epoch 3", + "short": "3", "long": "Epoch 3" }, "_4": { "id": "epoch_4", - "short": "Epoch 4", + "short": "4", "long": "Epoch 4" } } @@ -4647,67 +4647,67 @@ class BrkClient(BrkClientBase): "_1sat_to_10sats": { "id": "above_1sat_under_10sats", "short": "1-10 sats", - "long": "1 to 10 Sats" + "long": "1-10 Sats" }, "_10sats_to_100sats": { "id": "above_10sats_under_100sats", "short": "10-100 sats", - "long": "10 to 100 Sats" + "long": "10-100 Sats" }, "_100sats_to_1k_sats": { "id": "above_100sats_under_1k_sats", "short": "100-1k sats", - "long": "100 to 1K Sats" + "long": "100-1K Sats" }, "_1k_sats_to_10k_sats": { "id": "above_1k_sats_under_10k_sats", "short": "1k-10k sats", - "long": "1K to 10K Sats" + "long": "1K-10K Sats" }, "_10k_sats_to_100k_sats": { "id": "above_10k_sats_under_100k_sats", "short": "10k-100k sats", - "long": "10K to 100K Sats" + "long": "10K-100K Sats" }, "_100k_sats_to_1m_sats": { "id": "above_100k_sats_under_1m_sats", "short": "100k-1M sats", - "long": "100K to 1M Sats" + "long": "100K-1M Sats" }, "_1m_sats_to_10m_sats": { "id": "above_1m_sats_under_10m_sats", "short": "1M-10M sats", - "long": "1M to 10M Sats" + "long": "1M-10M Sats" }, "_10m_sats_to_1btc": { "id": "above_10m_sats_under_1btc", "short": "0.1-1 BTC", - "long": "0.1 to 1 BTC" + "long": "0.1-1 BTC" }, "_1btc_to_10btc": { "id": "above_1btc_under_10btc", "short": "1-10 BTC", - "long": "1 to 10 BTC" + "long": "1-10 BTC" }, "_10btc_to_100btc": { "id": "above_10btc_under_100btc", "short": "10-100 BTC", - "long": "10 to 100 BTC" + "long": "10-100 BTC" }, "_100btc_to_1k_btc": { "id": "above_100btc_under_1k_btc", "short": "100-1k BTC", - "long": "100 to 1K BTC" + "long": "100-1K BTC" }, "_1k_btc_to_10k_btc": { "id": "above_1k_btc_under_10k_btc", "short": "1k-10k BTC", - "long": "1K to 10K BTC" + "long": "1K-10K BTC" }, "_10k_btc_to_100k_btc": { "id": "above_10k_btc_under_100k_btc", "short": "10k-100k BTC", - "long": "10K to 100K BTC" + "long": "10K-100K BTC" }, "_100k_btc_or_more": { "id": "above_100k_btc", diff --git a/scripts/release.sh b/scripts/release.sh index ca5dfb034..3dca53968 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -55,7 +55,7 @@ echo "" echo "--- Rust ---" cd "$ROOT_DIR" -cargo test --workspace --exclude brk_playground +cargo test --workspace echo "" echo "--- JavaScript ---" @@ -125,7 +125,7 @@ cd "$ROOT_DIR" # Verify all crates package correctly # Note: --no-verify skips rebuild check due to version collision with crates.io # The cargo build --workspace --release step above already verified compilation -cargo package --workspace --allow-dirty --exclude brk_playground --no-verify +cargo package --workspace --allow-dirty --no-verify # Version bump, commit, and tag (but don't publish yet) cargo release "$RELEASE_ARG" --execute --no-publish --no-confirm diff --git a/website/assets/fonts/GeistMono-Italic[wght]-v1_5.woff2 b/website/assets/fonts/GeistMono-Italic[wght]-v1_5.woff2 deleted file mode 100644 index 8795c26c4..000000000 Binary files a/website/assets/fonts/GeistMono-Italic[wght]-v1_5.woff2 and /dev/null differ diff --git a/website/assets/fonts/GeistMono[wght]-v1_5.woff2 b/website/assets/fonts/GeistMono[wght]-v1_5.woff2 deleted file mode 100644 index b96b7d486..000000000 Binary files a/website/assets/fonts/GeistMono[wght]-v1_5.woff2 and /dev/null differ diff --git a/website/assets/fonts/Lilex-Italic[wght]-v2_601.woff2 b/website/assets/fonts/Lilex-Italic[wght]-v2_601.woff2 deleted file mode 100644 index 58fa6dc9e..000000000 Binary files a/website/assets/fonts/Lilex-Italic[wght]-v2_601.woff2 and /dev/null differ diff --git a/website/assets/fonts/Lilex-Italic[wght]-v2_620.woff2.woff2 b/website/assets/fonts/Lilex-Italic[wght]-v2_620.woff2.woff2 new file mode 100644 index 000000000..463d9fece Binary files /dev/null and b/website/assets/fonts/Lilex-Italic[wght]-v2_620.woff2.woff2 differ diff --git a/website/assets/fonts/Lilex[wght]-v2_601.woff2 b/website/assets/fonts/Lilex[wght]-v2_601.woff2 deleted file mode 100644 index 4e27df5c6..000000000 Binary files a/website/assets/fonts/Lilex[wght]-v2_601.woff2 and /dev/null differ diff --git a/website/assets/fonts/Lilex[wght]-v2_620.woff2 b/website/assets/fonts/Lilex[wght]-v2_620.woff2 new file mode 100644 index 000000000..b74e3e197 Binary files /dev/null and b/website/assets/fonts/Lilex[wght]-v2_620.woff2 differ diff --git a/website/scripts/options/chain.js b/website/scripts/options/chain.js index 4497ade1e..afba66ee7 100644 --- a/website/scripts/options/chain.js +++ b/website/scripts/options/chain.js @@ -189,6 +189,13 @@ export function createChainSection(ctx) { unit: Unit.count, options: { lineStyle: 4 }, }), + line({ + metric: blocks.count._24hBlockCount, + name: "24h sum", + color: colors.pink, + unit: Unit.count, + defaultActive: false, + }), line({ metric: blocks.count._1wBlockCount, name: "1w sum", @@ -199,7 +206,7 @@ export function createChainSection(ctx) { line({ metric: blocks.count._1mBlockCount, name: "1m sum", - color: colors.pink, + color: colors.orange, unit: Unit.count, defaultActive: false, }), @@ -225,10 +232,36 @@ export function createChainSection(ctx) { title: "Block Size", bottom: [ ...fromSizePattern(blocks.size, Unit.bytes), + line({ + metric: blocks.totalSize, + name: "total", + color: colors.purple, + unit: Unit.bytes, + defaultActive: false, + }), ...fromFullnessPattern(blocks.vbytes, Unit.vb), ...fromFullnessPattern(blocks.weight, Unit.wu), + line({ + metric: blocks.weight.sum, + name: "sum", + color: colors.stat.sum, + unit: Unit.wu, + defaultActive: false, + }), + line({ + metric: blocks.weight.cumulative, + name: "cumulative", + color: colors.stat.cumulative, + unit: Unit.wu, + defaultActive: false, + }), ], }, + { + name: "Fullness", + title: "Block Fullness", + bottom: fromFullnessPattern(blocks.fullness, Unit.percentage), + }, ], }, @@ -380,11 +413,6 @@ export function createChainSection(ctx) { title: "Output Count", bottom: [...fromSizePattern(outputs.count.totalCount, Unit.count)], }, - { - name: "OP_RETURN", - title: "OP_RETURN Outputs", - bottom: fromFullnessPattern(scripts.count.opreturn, Unit.count), - }, { name: "Speed", title: "Outputs Per Second", @@ -416,6 +444,60 @@ export function createChainSection(ctx) { ], }, + // Scripts + { + name: "Scripts", + tree: [ + { + name: "Count", + tree: [ + { name: "P2PKH", title: "P2PKH Output Count", bottom: fromDollarsPattern(scripts.count.p2pkh, Unit.count) }, + { name: "P2SH", title: "P2SH Output Count", bottom: fromDollarsPattern(scripts.count.p2sh, Unit.count) }, + { name: "P2WPKH", title: "P2WPKH Output Count", bottom: fromDollarsPattern(scripts.count.p2wpkh, Unit.count) }, + { name: "P2WSH", title: "P2WSH Output Count", bottom: fromDollarsPattern(scripts.count.p2wsh, Unit.count) }, + { name: "P2TR", title: "P2TR Output Count", bottom: fromDollarsPattern(scripts.count.p2tr, Unit.count) }, + { name: "P2PK33", title: "P2PK33 Output Count", bottom: fromDollarsPattern(scripts.count.p2pk33, Unit.count) }, + { name: "P2PK65", title: "P2PK65 Output Count", bottom: fromDollarsPattern(scripts.count.p2pk65, Unit.count) }, + { name: "P2MS", title: "P2MS Output Count", bottom: fromDollarsPattern(scripts.count.p2ms, Unit.count) }, + { name: "P2A", title: "P2A Output Count", bottom: fromDollarsPattern(scripts.count.p2a, Unit.count) }, + { name: "OP_RETURN", title: "OP_RETURN Output Count", bottom: fromDollarsPattern(scripts.count.opreturn, Unit.count) }, + { name: "SegWit", title: "SegWit Output Count", bottom: fromDollarsPattern(scripts.count.segwit, Unit.count) }, + { name: "Empty", title: "Empty Output Count", bottom: fromDollarsPattern(scripts.count.emptyoutput, Unit.count) }, + { name: "Unknown", title: "Unknown Output Count", bottom: fromDollarsPattern(scripts.count.unknownoutput, Unit.count) }, + ], + }, + { + name: "Adoption", + tree: [ + { + name: "SegWit", + title: "SegWit Adoption", + bottom: [ + line({ metric: scripts.count.segwitAdoption.base, name: "base", unit: Unit.percentage }), + line({ metric: scripts.count.segwitAdoption.sum, name: "sum", color: colors.stat.sum, unit: Unit.percentage }), + line({ metric: scripts.count.segwitAdoption.cumulative, name: "cumulative", color: colors.stat.cumulative, unit: Unit.percentage, defaultActive: false }), + ], + }, + { + name: "Taproot", + title: "Taproot Adoption", + bottom: [ + line({ metric: scripts.count.taprootAdoption.base, name: "base", unit: Unit.percentage }), + line({ metric: scripts.count.taprootAdoption.sum, name: "sum", color: colors.stat.sum, unit: Unit.percentage }), + line({ metric: scripts.count.taprootAdoption.cumulative, name: "cumulative", color: colors.stat.cumulative, unit: Unit.percentage, defaultActive: false }), + ], + }, + ], + }, + { + name: "Value", + tree: [ + { name: "OP_RETURN", title: "OP_RETURN Value", bottom: fromCoinbasePattern(scripts.value.opreturn) }, + ], + }, + ], + }, + // Supply { name: "Supply", @@ -456,7 +538,10 @@ export function createChainSection(ctx) { { name: "Coinbase", title: "Coinbase Rewards", - bottom: fromCoinbasePattern(blocks.rewards.coinbase), + bottom: [ + ...fromCoinbasePattern(blocks.rewards.coinbase), + ...satsBtcUsd(blocks.rewards._24hCoinbaseSum, "24h sum", colors.pink, { defaultActive: false }), + ], }, { name: "Subsidy", @@ -470,6 +555,13 @@ export function createChainSection(ctx) { unit: Unit.percentage, defaultActive: false, }), + line({ + metric: blocks.rewards.subsidyUsd1ySma, + name: "1y SMA", + color: colors.lime, + unit: Unit.usd, + defaultActive: false, + }), ], }, { @@ -557,6 +649,63 @@ export function createChainSection(ctx) { }), ], }, + { + name: "Empty by Type", + title: "Empty Address Count by Type", + bottom: [ + line({ + metric: distribution.emptyAddrCount.p2pkh, + name: "P2PKH", + color: colors.orange, + unit: Unit.count, + }), + line({ + metric: distribution.emptyAddrCount.p2sh, + name: "P2SH", + color: colors.yellow, + unit: Unit.count, + }), + line({ + metric: distribution.emptyAddrCount.p2wpkh, + name: "P2WPKH", + color: colors.green, + unit: Unit.count, + }), + line({ + metric: distribution.emptyAddrCount.p2wsh, + name: "P2WSH", + color: colors.teal, + unit: Unit.count, + }), + line({ + metric: distribution.emptyAddrCount.p2tr, + name: "P2TR", + color: colors.purple, + unit: Unit.count, + }), + line({ + metric: distribution.emptyAddrCount.p2pk65, + name: "P2PK65", + color: colors.pink, + unit: Unit.count, + defaultActive: false, + }), + line({ + metric: distribution.emptyAddrCount.p2pk33, + name: "P2PK33", + color: colors.red, + unit: Unit.count, + defaultActive: false, + }), + line({ + metric: distribution.emptyAddrCount.p2a, + name: "P2A", + color: colors.blue, + unit: Unit.count, + defaultActive: false, + }), + ], + }, { name: "By Type", title: "Address Count by Type", @@ -678,6 +827,12 @@ export function createChainSection(ctx) { name: "Difficulty", unit: Unit.difficulty, }), + line({ + metric: blocks.difficulty.epoch, + name: "Epoch", + color: colors.teal, + unit: Unit.epoch, + }), line({ metric: blocks.difficulty.blocksBeforeNextAdjustment, name: "before next", diff --git a/website/scripts/options/cointime.js b/website/scripts/options/cointime.js index 2bf9600bd..59df148f8 100644 --- a/website/scripts/options/cointime.js +++ b/website/scripts/options/cointime.js @@ -4,6 +4,7 @@ import { satsBtcUsd, createRatioChart, createZScoresFolder, + formatCohortTitle, } from "./shared.js"; /** @@ -27,7 +28,7 @@ function createCointimePriceWithRatioOptions( title, top: [line({ metric: price, name: legend, color, unit: Unit.usd })], }, - createRatioChart(ctx, { title, price, ratio, color }), + createRatioChart(ctx, { title: formatCohortTitle(title), price, ratio, color }), createZScoresFolder(ctx, { title, legend, price, ratio, color }), ]; } @@ -40,7 +41,15 @@ function createCointimePriceWithRatioOptions( export function createCointimeSection(ctx) { const { colors, brk } = ctx; const { cointime, distribution, supply } = brk.metrics; - const { pricing, cap, activity, supply: cointimeSupply, adjusted, reserveRisk, value } = cointime; + const { + pricing, + cap, + activity, + supply: cointimeSupply, + adjusted, + reserveRisk, + value, + } = cointime; const { all } = distribution.utxoCohorts; // Cointime prices data diff --git a/website/scripts/options/context.js b/website/scripts/options/context.js index 2378c9128..236f14d80 100644 --- a/website/scripts/options/context.js +++ b/website/scripts/options/context.js @@ -1,7 +1,4 @@ import { - fromBlockCount, - fromBitcoin, - fromBlockSize, fromSizePattern, fromFullnessPattern, fromDollarsPattern, @@ -32,15 +29,6 @@ export function createContext({ brk }) { colors, brk, - /** @type {OmitFirstArg} */ - fromBlockCount: (pattern, title, color) => - fromBlockCount(colors, pattern, title, color), - /** @type {OmitFirstArg} */ - fromBitcoin: (pattern, title, color) => - fromBitcoin(colors, pattern, title, color), - /** @type {OmitFirstArg} */ - fromBlockSize: (pattern, title, color) => - fromBlockSize(colors, pattern, title, color), /** @type {OmitFirstArg} */ fromSizePattern: (pattern, unit, title) => fromSizePattern(colors, pattern, unit, title), diff --git a/website/scripts/options/distribution/address.js b/website/scripts/options/distribution/address.js index fe266faf5..3dd291704 100644 --- a/website/scripts/options/distribution/address.js +++ b/website/scripts/options/distribution/address.js @@ -7,11 +7,10 @@ import { Unit } from "../../utils/units.js"; import { priceLine } from "../constants.js"; import { line, baseline } from "../series.js"; +import { formatCohortTitle } from "../shared.js"; import { createSingleSupplySeries, - createGroupedSupplyTotalSeries, - createGroupedSupplyInProfitSeries, - createGroupedSupplyInLossSeries, + createGroupedSupplySection, createUtxoCountSeries, createAddressCountSeries, createRealizedPriceSeries, @@ -25,6 +24,8 @@ import { createGroupedSentSatsSeries, createGroupedSentBitcoinSeries, createGroupedSentDollarsSeries, + groupedSupplyRelativeGenerators, + createSingleSupplyRelativeOptions, } from "./shared.js"; /** @@ -39,7 +40,7 @@ export function createAddressCohortFolder(ctx, args) { const useGroupName = "list" in args; const isSingle = !("list" in args); - const title = args.title ? `${useGroupName ? "by" : "of"} ${args.title}` : ""; + const title = formatCohortTitle(args.title); return { name: args.name || "all", @@ -48,44 +49,26 @@ export function createAddressCohortFolder(ctx, args) { isSingle ? { name: "supply", - title: `Supply ${title}`, + title: title("Supply"), bottom: createSingleSupplySeries( ctx, /** @type {AddressCohortObject} */ (args), + createSingleSupplyRelativeOptions(ctx, /** @type {AddressCohortObject} */ (args)), ), } - : { - name: "supply", - tree: [ - { - name: "total", - title: `Supply ${title}`, - bottom: createGroupedSupplyTotalSeries(list), - }, - { - name: "in profit", - title: `Supply In Profit ${title}`, - bottom: createGroupedSupplyInProfitSeries(list), - }, - { - name: "in loss", - title: `Supply In Loss ${title}`, - bottom: createGroupedSupplyInLossSeries(list), - }, - ], - }, + : createGroupedSupplySection(list, title, groupedSupplyRelativeGenerators), // UTXO count { name: "utxo count", - title: `UTXO Count ${title}`, + title: title("UTXO Count"), bottom: createUtxoCountSeries(list, useGroupName), }, // Address count (ADDRESS COHORTS ONLY - fully type safe!) { name: "address count", - title: `Address Count ${title}`, + title: title("Address Count"), bottom: createAddressCountSeries(ctx, list, useGroupName), }, @@ -97,12 +80,12 @@ export function createAddressCohortFolder(ctx, args) { ? [ { name: "Price", - title: `Realized Price ${title}`, + title: title("Realized Price"), top: createRealizedPriceSeries(list), }, { name: "Ratio", - title: `Realized Price Ratio ${title}`, + title: title("Realized Price Ratio"), bottom: createRealizedPriceRatioSeries(ctx, list), }, ] @@ -112,9 +95,21 @@ export function createAddressCohortFolder(ctx, args) { )), { name: "capitalization", - title: `Realized Cap ${title}`, + title: title("Realized Cap"), bottom: createRealizedCapWithExtras(ctx, list, args, useGroupName), }, + { + name: "value", + title: title("Realized Value"), + bottom: list.map(({ color, name, tree }) => + line({ + metric: tree.realized.realizedValue, + name: useGroupName ? name : "Realized Value", + color, + unit: Unit.usd, + }), + ), + }, ...(!useGroupName ? createRealizedPnlSection( ctx, @@ -140,7 +135,7 @@ export function createAddressCohortFolder(ctx, args) { /** * Create realized price options for single cohort * @param {AddressCohortObject} args - * @param {string} title + * @param {(metric: string) => string} title * @returns {PartialOptionsTree} */ function createRealizedPriceOptions(args, title) { @@ -149,7 +144,7 @@ function createRealizedPriceOptions(args, title) { return [ { name: "price", - title: `Realized Price ${title}`, + title: title("Realized Price"), top: [ line({ metric: tree.realized.realizedPrice, @@ -199,7 +194,7 @@ function createRealizedCapWithExtras(ctx, list, args, useGroupName) { * Create realized PnL section for single cohort * @param {PartialContext} ctx * @param {AddressCohortObject} args - * @param {string} title + * @param {(metric: string) => string} title * @returns {PartialOptionsTree} */ function createRealizedPnlSection(ctx, args, title) { @@ -209,7 +204,7 @@ function createRealizedPnlSection(ctx, args, title) { return [ { name: "pnl", - title: `Realized P&L ${title}`, + title: title("Realized P&L"), bottom: [ line({ metric: realized.realizedProfit.sum, @@ -287,7 +282,7 @@ function createRealizedPnlSection(ctx, args, title) { }, { name: "Net pnl", - title: `Net Realized P&L ${title}`, + title: title("Net Realized P&L"), bottom: [ baseline({ metric: realized.netRealizedPnl.sum, @@ -345,7 +340,7 @@ function createRealizedPnlSection(ctx, args, title) { }, { name: "sopr", - title: `SOPR ${title}`, + title: title("SOPR"), bottom: [ baseline({ metric: realized.sopr, @@ -378,7 +373,7 @@ function createRealizedPnlSection(ctx, args, title) { }, { name: "Sell Side Risk", - title: `Sell Side Risk Ratio ${title}`, + title: title("Sell Side Risk Ratio"), bottom: [ line({ metric: realized.sellSideRiskRatio, @@ -404,7 +399,7 @@ function createRealizedPnlSection(ctx, args, title) { }, { name: "value", - title: `Value Created & Destroyed ${title}`, + title: title("Value Created & Destroyed"), bottom: [ line({ metric: realized.valueCreated, @@ -428,7 +423,7 @@ function createRealizedPnlSection(ctx, args, title) { * @param {PartialContext} ctx * @param {readonly AddressCohortObject[]} list * @param {boolean} useGroupName - * @param {string} title + * @param {(metric: string) => string} title * @returns {PartialOptionsTree} */ function createUnrealizedSection(ctx, list, useGroupName, title) { @@ -440,7 +435,7 @@ function createUnrealizedSection(ctx, list, useGroupName, title) { tree: [ { name: "profit", - title: `Unrealized Profit ${title}`, + title: title("Unrealized Profit"), bottom: list.flatMap(({ color, name, tree }) => [ line({ metric: tree.unrealized.unrealizedProfit, @@ -452,7 +447,7 @@ function createUnrealizedSection(ctx, list, useGroupName, title) { }, { name: "loss", - title: `Unrealized Loss ${title}`, + title: title("Unrealized Loss"), bottom: list.flatMap(({ color, name, tree }) => [ line({ metric: tree.unrealized.unrealizedLoss, @@ -464,7 +459,7 @@ function createUnrealizedSection(ctx, list, useGroupName, title) { }, { name: "total pnl", - title: `Total Unrealized P&L ${title}`, + title: title("Total Unrealized P&L"), bottom: list.flatMap(({ color, name, tree }) => [ baseline({ metric: tree.unrealized.totalUnrealizedPnl, @@ -476,7 +471,7 @@ function createUnrealizedSection(ctx, list, useGroupName, title) { }, { name: "negative loss", - title: `Negative Unrealized Loss ${title}`, + title: title("Negative Unrealized Loss"), bottom: list.flatMap(({ color, name, tree }) => [ line({ metric: tree.unrealized.negUnrealizedLoss, @@ -491,7 +486,7 @@ function createUnrealizedSection(ctx, list, useGroupName, title) { tree: [ { name: "nupl", - title: `NUPL (Rel to Market Cap) ${title}`, + title: title("NUPL (Rel to Market Cap)"), bottom: list.flatMap(({ color, name, tree }) => [ baseline({ metric: tree.relative.nupl, @@ -504,7 +499,7 @@ function createUnrealizedSection(ctx, list, useGroupName, title) { }, { name: "profit", - title: `Unrealized Profit (% of Market Cap) ${title}`, + title: title("Unrealized Profit (% of Market Cap)"), bottom: list.flatMap(({ color, name, tree }) => [ line({ metric: tree.relative.unrealizedProfitRelToMarketCap, @@ -516,7 +511,7 @@ function createUnrealizedSection(ctx, list, useGroupName, title) { }, { name: "loss", - title: `Unrealized Loss (% of Market Cap) ${title}`, + title: title("Unrealized Loss (% of Market Cap)"), bottom: list.flatMap(({ color, name, tree }) => [ line({ metric: tree.relative.unrealizedLossRelToMarketCap, @@ -528,7 +523,7 @@ function createUnrealizedSection(ctx, list, useGroupName, title) { }, { name: "net pnl", - title: `Net Unrealized P&L (% of Market Cap) ${title}`, + title: title("Net Unrealized P&L (% of Market Cap)"), bottom: list.flatMap(({ color, name, tree }) => [ baseline({ metric: tree.relative.netUnrealizedPnlRelToMarketCap, @@ -540,7 +535,7 @@ function createUnrealizedSection(ctx, list, useGroupName, title) { }, { name: "negative loss", - title: `Negative Unrealized Loss (% of Market Cap) ${title}`, + title: title("Negative Unrealized Loss (% of Market Cap)"), bottom: list.flatMap(({ color, name, tree }) => [ line({ metric: tree.relative.negUnrealizedLossRelToMarketCap, @@ -554,7 +549,7 @@ function createUnrealizedSection(ctx, list, useGroupName, title) { }, { name: "nupl", - title: `Net Unrealized P&L ${title}`, + title: title("Net Unrealized P&L"), bottom: list.flatMap(({ color, name, tree }) => [ baseline({ metric: tree.unrealized.netUnrealizedPnl, @@ -577,7 +572,7 @@ function createUnrealizedSection(ctx, list, useGroupName, title) { * Create cost basis section (no percentiles for address cohorts) * @param {readonly AddressCohortObject[]} list * @param {boolean} useGroupName - * @param {string} title + * @param {(metric: string) => string} title * @returns {PartialOptionsTree} */ function createCostBasisSection(list, useGroupName, title) { @@ -587,7 +582,7 @@ function createCostBasisSection(list, useGroupName, title) { tree: [ { name: "min", - title: `Min Cost Basis ${title}`, + title: title("Min Cost Basis"), top: list.map(({ color, name, tree }) => line({ metric: tree.costBasis.min, @@ -599,7 +594,7 @@ function createCostBasisSection(list, useGroupName, title) { }, { name: "max", - title: `Max Cost Basis ${title}`, + title: title("Max Cost Basis"), top: list.map(({ color, name, tree }) => line({ metric: tree.costBasis.max, @@ -617,7 +612,7 @@ function createCostBasisSection(list, useGroupName, title) { /** * Create activity section * @param {AddressCohortObject | AddressCohortGroupObject} args - * @param {string} title + * @param {(metric: string) => string} title * @returns {PartialOptionsTree} */ function createActivitySection(args, title) { @@ -633,12 +628,12 @@ function createActivitySection(args, title) { tree: [ { name: "Coins Destroyed", - title: `Coins Destroyed ${title}`, + title: title("Coins Destroyed"), bottom: createSingleCoinsDestroyedSeries(cohort), }, { name: "Sent", - title: `Sent ${title}`, + title: title("Sent"), bottom: createSingleSentSeries(cohort), }, ], @@ -653,22 +648,22 @@ function createActivitySection(args, title) { tree: [ { name: "coinblocks destroyed", - title: `Coinblocks Destroyed ${title}`, + title: title("Coinblocks Destroyed"), bottom: createGroupedCoinblocksDestroyedSeries(list), }, { name: "coindays destroyed", - title: `Coindays Destroyed ${title}`, + title: title("Coindays Destroyed"), bottom: createGroupedCoindaysDestroyedSeries(list), }, { name: "satblocks destroyed", - title: `Satblocks Destroyed ${title}`, + title: title("Satblocks Destroyed"), bottom: createGroupedSatblocksDestroyedSeries(list), }, { name: "satdays destroyed", - title: `Satdays Destroyed ${title}`, + title: title("Satdays Destroyed"), bottom: createGroupedSatdaysDestroyedSeries(list), }, { @@ -676,17 +671,17 @@ function createActivitySection(args, title) { tree: [ { name: "sats", - title: `Sent (Sats) ${title}`, + title: title("Sent (Sats)"), bottom: createGroupedSentSatsSeries(list), }, { name: "bitcoin", - title: `Sent (BTC) ${title}`, + title: title("Sent (BTC)"), bottom: createGroupedSentBitcoinSeries(list), }, { name: "dollars", - title: `Sent ($) ${title}`, + title: title("Sent ($)"), bottom: createGroupedSentDollarsSeries(list), }, ], diff --git a/website/scripts/options/distribution/data.js b/website/scripts/options/distribution/data.js index 62c8b3fe6..b375fb7d9 100644 --- a/website/scripts/options/distribution/data.js +++ b/website/scripts/options/distribution/data.js @@ -24,10 +24,20 @@ const entries = (obj) => ); /** @type {readonly AddressableType[]} */ -const ADDRESSABLE_TYPES = ["p2pk65", "p2pk33", "p2pkh", "p2sh", "p2wpkh", "p2wsh", "p2tr", "p2a"]; +const ADDRESSABLE_TYPES = [ + "p2pk65", + "p2pk33", + "p2pkh", + "p2sh", + "p2wpkh", + "p2wsh", + "p2tr", + "p2a", +]; /** @type {(key: SpendableType) => key is AddressableType} */ -const isAddressable = (key) => ADDRESSABLE_TYPES.includes(/** @type {any} */ (key)); +const isAddressable = (key) => + ADDRESSABLE_TYPES.includes(/** @type {any} */ (key)); /** * Build all cohort data from brk tree @@ -51,8 +61,7 @@ export function buildCohortData(colors, brk) { YEAR_NAMES, } = brk; - // Base cohort representing "all" - CohortAll (adjustedSopr + percentiles but no RelToMarketCap) - /** @type {CohortAll} */ + // Base cohort representing "all" const cohortAll = { name: "", title: "", @@ -61,9 +70,8 @@ export function buildCohortData(colors, brk) { addrCount: addrCount.all, }; - // Term cohorts - split because short is CohortFull, long is CohortWithPercentiles + // Term cohorts const shortNames = TERM_NAMES.short; - /** @type {CohortFull} */ const termShort = { name: shortNames.short, title: shortNames.long, @@ -79,43 +87,40 @@ export function buildCohortData(colors, brk) { tree: utxoCohorts.term.long, }; - // Max age cohorts (up to X time) - CohortWithAdjusted (adjustedSopr only) - /** @type {readonly CohortWithAdjusted[]} */ + // Max age cohorts (up to X time) const upToDate = entries(utxoCohorts.maxAge).map(([key, tree]) => { const names = MAX_AGE_NAMES[key]; return { name: names.short, - title: names.long, + title: `UTXOs ${names.long}`, color: colors[maxAgeColors[key]], tree, }; }); - // Min age cohorts (from X time) - CohortBasicWithMarketCap (has RelToMarketCap) - /** @type {readonly CohortBasicWithMarketCap[]} */ + // Min age cohorts (from X time) const fromDate = entries(utxoCohorts.minAge).map(([key, tree]) => { const names = MIN_AGE_NAMES[key]; return { name: names.short, - title: names.long, + title: `UTXOs ${names.long}`, color: colors[minAgeColors[key]], tree, }; }); - // Age range cohorts - CohortAgeRange (no nupl) + // Age range cohorts const dateRange = entries(utxoCohorts.ageRange).map(([key, tree]) => { const names = AGE_RANGE_NAMES[key]; return { name: names.short, - title: names.long, + title: `UTXOs ${names.long}`, color: colors[ageRangeColors[key]], tree, }; }); - // Epoch cohorts - CohortBasicWithoutMarketCap (no RelToMarketCap) - /** @type {readonly CohortBasicWithoutMarketCap[]} */ + // Epoch cohorts const epoch = entries(utxoCohorts.epoch).map(([key, tree]) => { const names = EPOCH_NAMES[key]; return { @@ -126,66 +131,61 @@ export function buildCohortData(colors, brk) { }; }); - // UTXOs above amount - CohortBasicWithMarketCap (has RelToMarketCap) - /** @type {readonly CohortBasicWithMarketCap[]} */ + // UTXOs above amount const utxosAboveAmount = entries(utxoCohorts.geAmount).map(([key, tree]) => { const names = GE_AMOUNT_NAMES[key]; return { name: names.short, - title: names.long, + title: `UTXOs ${names.long}`, color: colors[geAmountColors[key]], tree, }; }); // Addresses above amount - /** @type {readonly AddressCohortObject[]} */ const addressesAboveAmount = entries(addressCohorts.geAmount).map( ([key, tree]) => { const names = GE_AMOUNT_NAMES[key]; return { name: names.short, - title: names.long, + title: `Addresses ${names.long}`, color: colors[geAmountColors[key]], tree, }; }, ); - // UTXOs under amount - CohortBasicWithMarketCap (has RelToMarketCap) - /** @type {readonly CohortBasicWithMarketCap[]} */ + // UTXOs under amount const utxosUnderAmount = entries(utxoCohorts.ltAmount).map(([key, tree]) => { const names = LT_AMOUNT_NAMES[key]; return { name: names.short, - title: names.long, + title: `UTXOs ${names.long}`, color: colors[ltAmountColors[key]], tree, }; }); // Addresses under amount - /** @type {readonly AddressCohortObject[]} */ const addressesUnderAmount = entries(addressCohorts.ltAmount).map( ([key, tree]) => { const names = LT_AMOUNT_NAMES[key]; return { name: names.short, - title: names.long, + title: `Addresses ${names.long}`, color: colors[ltAmountColors[key]], tree, }; }, ); - // UTXOs amount ranges - CohortBasicWithoutMarketCap (no RelToMarketCap) - /** @type {readonly CohortBasicWithoutMarketCap[]} */ + // UTXOs amount ranges const utxosAmountRanges = entries(utxoCohorts.amountRange).map( ([key, tree]) => { const names = AMOUNT_RANGE_NAMES[key]; return { name: names.short, - title: names.long, + title: `UTXOs ${names.long}`, color: colors[amountRangeColors[key]], tree, }; @@ -193,13 +193,12 @@ export function buildCohortData(colors, brk) { ); // Addresses amount ranges - /** @type {readonly AddressCohortObject[]} */ const addressesAmountRanges = entries(addressCohorts.amountRange).map( ([key, tree]) => { const names = AMOUNT_RANGE_NAMES[key]; return { name: names.short, - title: names.long, + title: `Addresses ${names.long}`, color: colors[amountRangeColors[key]], tree, }; @@ -207,33 +206,30 @@ export function buildCohortData(colors, brk) { ); // Spendable type cohorts - split by addressability - /** @type {readonly CohortAddress[]} */ const typeAddressable = ADDRESSABLE_TYPES.map((key) => { const names = SPENDABLE_TYPE_NAMES[key]; return { name: names.short, - title: names.long, + title: names.short, color: colors[spendableTypeColors[key]], tree: utxoCohorts.type[key], addrCount: addrCount[key], }; }); - /** @type {readonly CohortBasicWithoutMarketCap[]} */ const typeOther = entries(utxoCohorts.type) .filter(([key]) => !isAddressable(key)) .map(([key, tree]) => { const names = SPENDABLE_TYPE_NAMES[key]; return { name: names.short, - title: names.long, + title: names.short, color: colors[spendableTypeColors[key]], tree, }; }); - // Year cohorts - CohortBasicWithoutMarketCap (no RelToMarketCap) - /** @type {readonly CohortBasicWithoutMarketCap[]} */ + // Year cohorts const year = entries(utxoCohorts.year).map(([key, tree]) => { const names = YEAR_NAMES[key]; return { diff --git a/website/scripts/options/distribution/index.js b/website/scripts/options/distribution/index.js index 6d5627927..2778f8d45 100644 --- a/website/scripts/options/distribution/index.js +++ b/website/scripts/options/distribution/index.js @@ -10,8 +10,7 @@ export { createCohortFolderAll, createCohortFolderFull, createCohortFolderWithAdjusted, - createCohortFolderWithPercentiles, - createCohortFolderLongTerm, + createCohortFolderWithNupl, createCohortFolderAgeRange, createCohortFolderBasicWithMarketCap, createCohortFolderBasicWithoutMarketCap, diff --git a/website/scripts/options/distribution/shared.js b/website/scripts/options/distribution/shared.js index 10f8a1b7a..e33f943a7 100644 --- a/website/scripts/options/distribution/shared.js +++ b/website/scripts/options/distribution/shared.js @@ -9,46 +9,25 @@ import { satsBtcUsd } from "../shared.js"; * Create supply section for a single cohort * @param {PartialContext} ctx * @param {CohortObject} cohort + * @param {Object} [options] + * @param {AnyFetchedSeriesBlueprint[]} [options.supplyRelative] - Supply relative to circulating supply metrics + * @param {AnyFetchedSeriesBlueprint[]} [options.pnlRelative] - Supply in profit/loss relative to circulating supply metrics * @returns {AnyFetchedSeriesBlueprint[]} */ -export function createSingleSupplySeries(ctx, cohort) { +export function createSingleSupplySeries(ctx, cohort, { supplyRelative = [], pnlRelative = [] } = {}) { const { colors } = ctx; const { tree } = cohort; return [ ...satsBtcUsd(tree.supply.total, "Supply", colors.default), - ...("supplyRelToCirculatingSupply" in tree.relative - ? [ - line({ - metric: tree.relative.supplyRelToCirculatingSupply, - name: "Supply", - color: colors.default, - unit: Unit.pctSupply, - }), - ] - : []), + ...supplyRelative, ...satsBtcUsd(tree.unrealized.supplyInProfit, "In Profit", colors.green), ...satsBtcUsd(tree.unrealized.supplyInLoss, "In Loss", colors.red), ...satsBtcUsd(tree.supply.halved, "half", colors.gray).map((s) => ({ ...s, options: { lineStyle: 4 }, })), - ...("supplyInProfitRelToCirculatingSupply" in tree.relative - ? [ - line({ - metric: tree.relative.supplyInProfitRelToCirculatingSupply, - name: "In Profit", - color: colors.green, - unit: Unit.pctSupply, - }), - line({ - metric: tree.relative.supplyInLossRelToCirculatingSupply, - name: "In Loss", - color: colors.red, - unit: Unit.pctSupply, - }), - ] - : []), + ...pnlRelative, line({ metric: tree.relative.supplyInProfitRelToOwnSupply, name: "In Profit", @@ -74,67 +53,198 @@ export function createSingleSupplySeries(ctx, cohort) { /** * Create supply total series for grouped cohorts - * @param {readonly CohortObject[]} list + * @template {readonly CohortObject[]} T + * @param {T} list + * @param {Object} [options] + * @param {(cohort: T[number]) => AnyFetchedSeriesBlueprint[]} [options.relativeMetrics] - Generator for relative supply metrics * @returns {AnyFetchedSeriesBlueprint[]} */ -export function createGroupedSupplyTotalSeries(list) { - return list.flatMap(({ color, name, tree }) => [ - ...satsBtcUsd(tree.supply.total, name, color), - ...("supplyRelToCirculatingSupply" in tree.relative - ? [ - line({ - metric: tree.relative.supplyRelToCirculatingSupply, - name, - color, - unit: Unit.pctSupply, - }), - ] - : []), +export function createGroupedSupplyTotalSeries(list, { relativeMetrics } = {}) { + return list.flatMap((cohort) => [ + ...satsBtcUsd(cohort.tree.supply.total, cohort.name, cohort.color), + ...(relativeMetrics ? relativeMetrics(cohort) : []), ]); } /** * Create supply in profit series for grouped cohorts - * @param {readonly CohortObject[]} list + * @template {readonly CohortObject[]} T + * @param {T} list + * @param {Object} [options] + * @param {(cohort: T[number]) => AnyFetchedSeriesBlueprint[]} [options.relativeMetrics] - Generator for relative supply metrics * @returns {AnyFetchedSeriesBlueprint[]} */ -export function createGroupedSupplyInProfitSeries(list) { - return list.flatMap(({ color, name, tree }) => [ - ...satsBtcUsd(tree.unrealized.supplyInProfit, name, color), - ...("supplyInProfitRelToCirculatingSupply" in tree.relative - ? [ - line({ - metric: tree.relative.supplyInProfitRelToCirculatingSupply, - name, - color, - unit: Unit.pctSupply, - }), - ] - : []), +export function createGroupedSupplyInProfitSeries(list, { relativeMetrics } = {}) { + return list.flatMap((cohort) => [ + ...satsBtcUsd(cohort.tree.unrealized.supplyInProfit, cohort.name, cohort.color), + ...(relativeMetrics ? relativeMetrics(cohort) : []), ]); } /** * Create supply in loss series for grouped cohorts - * @param {readonly CohortObject[]} list + * @template {readonly CohortObject[]} T + * @param {T} list + * @param {Object} [options] + * @param {(cohort: T[number]) => AnyFetchedSeriesBlueprint[]} [options.relativeMetrics] - Generator for relative supply metrics * @returns {AnyFetchedSeriesBlueprint[]} */ -export function createGroupedSupplyInLossSeries(list) { - return list.flatMap(({ color, name, tree }) => [ - ...satsBtcUsd(tree.unrealized.supplyInLoss, name, color), - ...("supplyInLossRelToCirculatingSupply" in tree.relative - ? [ - line({ - metric: tree.relative.supplyInLossRelToCirculatingSupply, - name, - color, - unit: Unit.pctSupply, - }), - ] - : []), +export function createGroupedSupplyInLossSeries(list, { relativeMetrics } = {}) { + return list.flatMap((cohort) => [ + ...satsBtcUsd(cohort.tree.unrealized.supplyInLoss, cohort.name, cohort.color), + ...(relativeMetrics ? relativeMetrics(cohort) : []), ]); } +/** + * Create supply section for grouped cohorts + * @template {readonly CohortObject[]} T + * @param {T} list + * @param {(metric: string) => string} title + * @param {Object} [options] + * @param {(cohort: T[number]) => AnyFetchedSeriesBlueprint[]} [options.supplyRelativeMetrics] - Generator for supply relative metrics + * @param {(cohort: T[number]) => AnyFetchedSeriesBlueprint[]} [options.profitRelativeMetrics] - Generator for supply in profit relative metrics + * @param {(cohort: T[number]) => AnyFetchedSeriesBlueprint[]} [options.lossRelativeMetrics] - Generator for supply in loss relative metrics + * @returns {PartialOptionsGroup} + */ +export function createGroupedSupplySection(list, title, { supplyRelativeMetrics, profitRelativeMetrics, lossRelativeMetrics } = {}) { + return { + name: "supply", + tree: [ + { + name: "total", + title: title("Supply"), + bottom: createGroupedSupplyTotalSeries(list, { relativeMetrics: supplyRelativeMetrics }), + }, + { + name: "in profit", + title: title("Supply In Profit"), + bottom: createGroupedSupplyInProfitSeries(list, { relativeMetrics: profitRelativeMetrics }), + }, + { + name: "in loss", + title: title("Supply In Loss"), + bottom: createGroupedSupplyInLossSeries(list, { relativeMetrics: lossRelativeMetrics }), + }, + ], + }; +} + +// ============================================================================ +// Circulating Supply Relative Metrics Generators +// ============================================================================ + +/** + * Create supply relative to circulating supply series for single cohort + * @param {PartialContext} ctx + * @param {CohortWithCirculatingSupplyRelative} cohort + * @returns {AnyFetchedSeriesBlueprint[]} + */ +export function createSupplyRelativeToCirculatingSeries(ctx, cohort) { + return [ + line({ + metric: cohort.tree.relative.supplyRelToCirculatingSupply, + name: "Supply", + color: ctx.colors.default, + unit: Unit.pctSupply, + }), + ]; +} + +/** + * Create supply in profit/loss relative to circulating supply series for single cohort + * @param {PartialContext} ctx + * @param {CohortWithCirculatingSupplyRelative} cohort + * @returns {AnyFetchedSeriesBlueprint[]} + */ +export function createSupplyPnlRelativeToCirculatingSeries(ctx, cohort) { + return [ + line({ + metric: cohort.tree.relative.supplyInProfitRelToCirculatingSupply, + name: "In Profit", + color: ctx.colors.green, + unit: Unit.pctSupply, + }), + line({ + metric: cohort.tree.relative.supplyInLossRelToCirculatingSupply, + name: "In Loss", + color: ctx.colors.red, + unit: Unit.pctSupply, + }), + ]; +} + +/** + * Create supply relative to circulating supply metrics generator for grouped cohorts + * @param {CohortWithCirculatingSupplyRelative} cohort + * @returns {AnyFetchedSeriesBlueprint[]} + */ +export function createGroupedSupplyRelativeMetrics(cohort) { + return [ + line({ + metric: cohort.tree.relative.supplyRelToCirculatingSupply, + name: cohort.name, + color: cohort.color, + unit: Unit.pctSupply, + }), + ]; +} + +/** + * Create supply in profit relative to circulating supply metrics generator for grouped cohorts + * @param {CohortWithCirculatingSupplyRelative} cohort + * @returns {AnyFetchedSeriesBlueprint[]} + */ +export function createGroupedSupplyInProfitRelativeMetrics(cohort) { + return [ + line({ + metric: cohort.tree.relative.supplyInProfitRelToCirculatingSupply, + name: cohort.name, + color: cohort.color, + unit: Unit.pctSupply, + }), + ]; +} + +/** + * Create supply in loss relative to circulating supply metrics generator for grouped cohorts + * @param {CohortWithCirculatingSupplyRelative} cohort + * @returns {AnyFetchedSeriesBlueprint[]} + */ +export function createGroupedSupplyInLossRelativeMetrics(cohort) { + return [ + line({ + metric: cohort.tree.relative.supplyInLossRelToCirculatingSupply, + name: cohort.name, + color: cohort.color, + unit: Unit.pctSupply, + }), + ]; +} + +/** + * Grouped supply relative generators object for cohorts with circulating supply relative + * @type {{ supplyRelativeMetrics: typeof createGroupedSupplyRelativeMetrics, profitRelativeMetrics: typeof createGroupedSupplyInProfitRelativeMetrics, lossRelativeMetrics: typeof createGroupedSupplyInLossRelativeMetrics }} + */ +export const groupedSupplyRelativeGenerators = { + supplyRelativeMetrics: createGroupedSupplyRelativeMetrics, + profitRelativeMetrics: createGroupedSupplyInProfitRelativeMetrics, + lossRelativeMetrics: createGroupedSupplyInLossRelativeMetrics, +}; + +/** + * Create single cohort supply relative options for cohorts with circulating supply relative + * @param {PartialContext} ctx + * @param {CohortWithCirculatingSupplyRelative} cohort + * @returns {{ supplyRelative: AnyFetchedSeriesBlueprint[], pnlRelative: AnyFetchedSeriesBlueprint[] }} + */ +export function createSingleSupplyRelativeOptions(ctx, cohort) { + return { + supplyRelative: createSupplyRelativeToCirculatingSeries(ctx, cohort), + pnlRelative: createSupplyPnlRelativeToCirculatingSeries(ctx, cohort), + }; +} + /** * Create UTXO count series * @param {readonly CohortObject[]} list diff --git a/website/scripts/options/distribution/utxo.js b/website/scripts/options/distribution/utxo.js index b7be421c2..a34cbf890 100644 --- a/website/scripts/options/distribution/utxo.js +++ b/website/scripts/options/distribution/utxo.js @@ -25,15 +25,19 @@ import { createSingleSupplySeries, - createGroupedSupplyTotalSeries, - createGroupedSupplyInProfitSeries, - createGroupedSupplyInLossSeries, + createGroupedSupplySection, createUtxoCountSeries, createRealizedPriceSeries, createRealizedPriceRatioSeries, createCostBasisPercentilesSeries, + groupedSupplyRelativeGenerators, + createSingleSupplyRelativeOptions, } from "./shared.js"; -import { createRatioChart, createZScoresFolder } from "../shared.js"; +import { + createRatioChart, + createZScoresFolder, + formatCohortTitle, +} from "../shared.js"; import { Unit } from "../../utils/units.js"; import { line, baseline } from "../series.js"; import { priceLine } from "../constants.js"; @@ -49,7 +53,7 @@ import { priceLine } from "../constants.js"; * @returns {PartialOptionsGroup} */ export function createCohortFolderAll(ctx, args) { - const title = args.title ? `of ${args.title}` : ""; + const title = formatCohortTitle(args.title); return { name: args.name || "all", tree: [ @@ -73,24 +77,26 @@ export function createCohortFolderAll(ctx, args) { export function createCohortFolderFull(ctx, args) { if ("list" in args) { const { list } = args; - const title = args.title ? `by ${args.title}` : ""; + const title = formatCohortTitle(args.title); return { name: args.name || "all", tree: [ - createGroupedSupplySection(list, title), + createGroupedSupplySection(list, title, groupedSupplyRelativeGenerators), createGroupedUtxoCountChart(list, title), - createGroupedRealizedSectionWithAdjusted(ctx, list, title), + createGroupedRealizedSectionWithAdjusted(ctx, list, title, { + ratioMetrics: createGroupedRealizedPnlRatioMetrics, + }), createGroupedUnrealizedSectionFull(ctx, list, title), createGroupedCostBasisSectionWithPercentiles(ctx, list, title), createGroupedActivitySectionWithAdjusted(list, title), ], }; } - const title = args.title ? `of ${args.title}` : ""; + const title = formatCohortTitle(args.title); return { name: args.name || "all", tree: [ - createSingleSupplyChart(ctx, args, title), + createSingleSupplyChart(ctx, args, title, createSingleSupplyRelativeOptions(ctx, args)), createSingleUtxoCountChart(args, title), createSingleRealizedSectionFull(ctx, args, title), createSingleUnrealizedSectionFull(ctx, args, title), @@ -109,69 +115,72 @@ export function createCohortFolderFull(ctx, args) { export function createCohortFolderWithAdjusted(ctx, args) { if ("list" in args) { const { list } = args; - const title = args.title ? `by ${args.title}` : ""; + const title = formatCohortTitle(args.title); return { name: args.name || "all", tree: [ - createGroupedSupplySection(list, title), + createGroupedSupplySection(list, title, groupedSupplyRelativeGenerators), createGroupedUtxoCountChart(list, title), createGroupedRealizedSectionWithAdjusted(ctx, list, title), createGroupedUnrealizedSectionWithMarketCap(ctx, list, title), - createGroupedCostBasisSection(list, title), + createGroupedCostBasisSection({ list, title }), createGroupedActivitySectionWithAdjusted(list, title), ], }; } - const title = args.title ? `of ${args.title}` : ""; + const title = formatCohortTitle(args.title); return { name: args.name || "all", tree: [ - createSingleSupplyChart(ctx, args, title), + createSingleSupplyChart(ctx, args, title, createSingleSupplyRelativeOptions(ctx, args)), createSingleUtxoCountChart(args, title), createSingleRealizedSectionWithAdjusted(ctx, args, title), createSingleUnrealizedSectionWithMarketCap(ctx, args, title), - createSingleCostBasisSection(args, title), + createCostBasisSection({ cohort: args, title }), createSingleActivitySectionWithAdjusted(ctx, args, title), ], }; } /** - * Long term folder: term.long (has nupl via RelativePattern5) + * Folder for cohorts with nupl + percentiles (term.short, term.long) * @param {PartialContext} ctx - * @param {CohortLongTerm | CohortGroupLongTerm} args + * @param {CohortWithNuplPercentiles | CohortGroupWithNuplPercentiles} args * @returns {PartialOptionsGroup} */ -export function createCohortFolderLongTerm(ctx, args) { +export function createCohortFolderWithNupl(ctx, args) { if ("list" in args) { const { list } = args; - const title = args.title ? `by ${args.title}` : ""; + const title = formatCohortTitle(args.title); return { name: args.name || "all", tree: [ - createGroupedSupplySection(list, title), + createGroupedSupplySection(list, title, groupedSupplyRelativeGenerators), createGroupedUtxoCountChart(list, title), - createGroupedRealizedSectionBasic(ctx, list, title), - createGroupedUnrealizedSectionLongTerm(ctx, list, title), + createGroupedRealizedSectionBasic(ctx, list, title, { + ratioMetrics: createGroupedRealizedPnlRatioMetrics, + }), + createGroupedUnrealizedSectionWithNupl({ ctx, list, title }), createGroupedCostBasisSectionWithPercentiles(ctx, list, title), - createGroupedActivitySectionBasic(list, title), + createGroupedActivitySection({ list, title }), ], }; } - const title = args.title ? `of ${args.title}` : ""; + const title = formatCohortTitle(args.title); return { name: args.name || "all", tree: [ - createSingleSupplyChart(ctx, args, title), + createSingleSupplyChart(ctx, args, title, createSingleSupplyRelativeOptions(ctx, args)), createSingleUtxoCountChart(args, title), createSingleRealizedSectionWithPercentiles(ctx, args, title), - createSingleUnrealizedSectionLongTerm(ctx, args, title), + createSingleUnrealizedSectionWithNupl({ ctx, cohort: args, title }), createSingleCostBasisSectionWithPercentiles(ctx, args, title), - createSingleActivitySectionBasic(ctx, args, title), + createActivitySection({ ctx, cohort: args, title }), ], }; } + /** * Age range folder: ageRange.* (no nupl via RelativePattern2) * @param {PartialContext} ctx @@ -181,20 +190,22 @@ export function createCohortFolderLongTerm(ctx, args) { export function createCohortFolderAgeRange(ctx, args) { if ("list" in args) { const { list } = args; - const title = args.title ? `by ${args.title}` : ""; + const title = formatCohortTitle(args.title); return { name: args.name || "all", tree: [ createGroupedSupplySection(list, title), createGroupedUtxoCountChart(list, title), - createGroupedRealizedSectionBasic(ctx, list, title), + createGroupedRealizedSectionBasic(ctx, list, title, { + ratioMetrics: createGroupedRealizedPnlRatioMetrics, + }), createGroupedUnrealizedSectionAgeRange(ctx, list, title), createGroupedCostBasisSectionWithPercentiles(ctx, list, title), - createGroupedActivitySectionBasic(list, title), + createGroupedActivitySection({ list, title }), ], }; } - const title = args.title ? `of ${args.title}` : ""; + const title = formatCohortTitle(args.title); return { name: args.name || "all", tree: [ @@ -203,44 +214,7 @@ export function createCohortFolderAgeRange(ctx, args) { createSingleRealizedSectionWithPercentiles(ctx, args, title), createSingleUnrealizedSectionAgeRange(ctx, args, title), createSingleCostBasisSectionWithPercentiles(ctx, args, title), - createSingleActivitySectionBasic(ctx, args, title), - ], - }; -} - -/** - * @deprecated Use createCohortFolderLongTerm or createCohortFolderAgeRange instead - * Percentiles folder: percentiles only, no adjustedSopr (term.long, ageRange.*) - * @param {PartialContext} ctx - * @param {CohortWithPercentiles | CohortGroupWithPercentiles} args - * @returns {PartialOptionsGroup} - */ -export function createCohortFolderWithPercentiles(ctx, args) { - if ("list" in args) { - const { list } = args; - const title = args.title ? `by ${args.title}` : ""; - return { - name: args.name || "all", - tree: [ - createGroupedSupplySection(list, title), - createGroupedUtxoCountChart(list, title), - createGroupedRealizedSectionBasic(ctx, list, title), - createGroupedUnrealizedSectionWithOwnCaps(ctx, list, title), - createGroupedCostBasisSectionWithPercentiles(ctx, list, title), - createGroupedActivitySectionBasic(list, title), - ], - }; - } - const title = args.title ? `of ${args.title}` : ""; - return { - name: args.name || "all", - tree: [ - createSingleSupplyChart(ctx, args, title), - createSingleUtxoCountChart(args, title), - createSingleRealizedSectionWithPercentiles(ctx, args, title), - createSingleUnrealizedSectionWithOwnCaps(ctx, args, title), - createSingleCostBasisSectionWithPercentiles(ctx, args, title), - createSingleActivitySectionBasic(ctx, args, title), + createActivitySection({ ctx, cohort: args, title }), ], }; } @@ -254,29 +228,29 @@ export function createCohortFolderWithPercentiles(ctx, args) { export function createCohortFolderBasicWithMarketCap(ctx, args) { if ("list" in args) { const { list } = args; - const title = args.title ? `by ${args.title}` : ""; + const title = formatCohortTitle(args.title); return { name: args.name || "all", tree: [ - createGroupedSupplySection(list, title), + createGroupedSupplySection(list, title, groupedSupplyRelativeGenerators), createGroupedUtxoCountChart(list, title), createGroupedRealizedSectionBasic(ctx, list, title), createGroupedUnrealizedSectionWithMarketCapOnly(ctx, list, title), - createGroupedCostBasisSection(list, title), - createGroupedActivitySectionBasic(list, title), + createGroupedCostBasisSection({ list, title }), + createGroupedActivitySection({ list, title }), ], }; } - const title = args.title ? `of ${args.title}` : ""; + const title = formatCohortTitle(args.title); return { name: args.name || "all", tree: [ - createSingleSupplyChart(ctx, args, title), + createSingleSupplyChart(ctx, args, title, createSingleSupplyRelativeOptions(ctx, args)), createSingleUtxoCountChart(args, title), createSingleRealizedSectionBasic(ctx, args, title), createSingleUnrealizedSectionWithMarketCapOnly(ctx, args, title), - createSingleCostBasisSection(args, title), - createSingleActivitySectionBasic(ctx, args, title), + createCostBasisSection({ cohort: args, title }), + createActivitySection({ ctx, cohort: args, title }), ], }; } @@ -290,7 +264,7 @@ export function createCohortFolderBasicWithMarketCap(ctx, args) { export function createCohortFolderBasicWithoutMarketCap(ctx, args) { if ("list" in args) { const { list } = args; - const title = args.title ? `by ${args.title}` : ""; + const title = formatCohortTitle(args.title); return { name: args.name || "all", tree: [ @@ -298,12 +272,12 @@ export function createCohortFolderBasicWithoutMarketCap(ctx, args) { createGroupedUtxoCountChart(list, title), createGroupedRealizedSectionBasic(ctx, list, title), createGroupedUnrealizedSectionBase(ctx, list, title), - createGroupedCostBasisSection(list, title), - createGroupedActivitySectionBasic(list, title), + createGroupedCostBasisSection({ list, title }), + createGroupedActivitySection({ list, title }), ], }; } - const title = args.title ? `of ${args.title}` : ""; + const title = formatCohortTitle(args.title); return { name: args.name || "all", tree: [ @@ -311,19 +285,12 @@ export function createCohortFolderBasicWithoutMarketCap(ctx, args) { createSingleUtxoCountChart(args, title), createSingleRealizedSectionBasic(ctx, args, title), createSingleUnrealizedSectionBase(ctx, args, title), - createSingleCostBasisSection(args, title), - createSingleActivitySectionBasic(ctx, args, title), + createCostBasisSection({ cohort: args, title }), + createActivitySection({ ctx, cohort: args, title }), ], }; } -/** - * @typedef {Object} CohortGroupAddress - * @property {string} name - * @property {string} title - * @property {readonly CohortAddress[]} list - */ - /** * Address folder: like basic but with address count (addressable type cohorts) * Uses base unrealized section (no RelToMarketCap since it extends CohortBasicWithoutMarketCap) @@ -334,7 +301,7 @@ export function createCohortFolderBasicWithoutMarketCap(ctx, args) { export function createCohortFolderAddress(ctx, args) { if ("list" in args) { const { list } = args; - const title = args.title ? `by ${args.title}` : ""; + const title = formatCohortTitle(args.title); return { name: args.name || "all", tree: [ @@ -343,12 +310,12 @@ export function createCohortFolderAddress(ctx, args) { createGroupedAddrCountChart(ctx, list, title), createGroupedRealizedSectionBasic(ctx, list, title), createGroupedUnrealizedSectionBase(ctx, list, title), - createGroupedCostBasisSection(list, title), - createGroupedActivitySectionBasic(list, title), + createGroupedCostBasisSection({ list, title }), + createGroupedActivitySection({ list, title }), ], }; } - const title = args.title ? `of ${args.title}` : ""; + const title = formatCohortTitle(args.title); return { name: args.name || "all", tree: [ @@ -357,8 +324,8 @@ export function createCohortFolderAddress(ctx, args) { createSingleAddrCountChart(ctx, args, title), createSingleRealizedSectionBasic(ctx, args, title), createSingleUnrealizedSectionBase(ctx, args, title), - createSingleCostBasisSection(args, title), - createSingleActivitySectionBasic(ctx, args, title), + createCostBasisSection({ cohort: args, title }), + createActivitySection({ ctx, cohort: args, title }), ], }; } @@ -367,56 +334,30 @@ export function createCohortFolderAddress(ctx, args) { * Create supply chart for single cohort * @param {PartialContext} ctx * @param {UtxoCohortObject} cohort - * @param {string} title + * @param {(metric: string) => string} title + * @param {Object} [options] + * @param {AnyFetchedSeriesBlueprint[]} [options.supplyRelative] - Supply relative to circulating supply + * @param {AnyFetchedSeriesBlueprint[]} [options.pnlRelative] - Supply in profit/loss relative to circulating supply * @returns {PartialChartOption} */ -function createSingleSupplyChart(ctx, cohort, title) { +function createSingleSupplyChart(ctx, cohort, title, options = {}) { return { name: "supply", - title: `Supply ${title}`, - bottom: createSingleSupplySeries(ctx, cohort), - }; -} - -/** - * Create supply section for grouped cohorts - * @param {readonly UtxoCohortObject[]} list - * @param {string} title - * @returns {PartialOptionsGroup} - */ -function createGroupedSupplySection(list, title) { - return { - name: "supply", - tree: [ - { - name: "total", - title: `Supply ${title}`, - bottom: createGroupedSupplyTotalSeries(list), - }, - { - name: "in profit", - title: `Supply In Profit ${title}`, - bottom: createGroupedSupplyInProfitSeries(list), - }, - { - name: "in loss", - title: `Supply In Loss ${title}`, - bottom: createGroupedSupplyInLossSeries(list), - }, - ], + title: title("Supply"), + bottom: createSingleSupplySeries(ctx, cohort, options), }; } /** * Create UTXO count chart for single cohort * @param {UtxoCohortObject} cohort - * @param {string} title + * @param {(metric: string) => string} title * @returns {PartialChartOption} */ function createSingleUtxoCountChart(cohort, title) { return { name: "utxo count", - title: `UTXO Count ${title}`, + title: title("UTXO Count"), bottom: createUtxoCountSeries([cohort], false), }; } @@ -424,13 +365,13 @@ function createSingleUtxoCountChart(cohort, title) { /** * Create UTXO count chart for grouped cohorts * @param {readonly UtxoCohortObject[]} list - * @param {string} title + * @param {(metric: string) => string} title * @returns {PartialChartOption} */ function createGroupedUtxoCountChart(list, title) { return { name: "utxo count", - title: `UTXO Count ${title}`, + title: title("UTXO Count"), bottom: createUtxoCountSeries(list, true), }; } @@ -439,13 +380,13 @@ function createGroupedUtxoCountChart(list, title) { * Create address count chart for single cohort with addrCount * @param {PartialContext} ctx * @param {CohortAll | CohortAddress} cohort - * @param {string} title + * @param {(metric: string) => string} title * @returns {PartialChartOption} */ function createSingleAddrCountChart(ctx, cohort, title) { return { name: "address count", - title: `Address Count ${title}`, + title: title("Address Count"), bottom: [ line({ metric: cohort.addrCount, @@ -461,13 +402,13 @@ function createSingleAddrCountChart(ctx, cohort, title) { * Create address count chart for grouped cohorts with addrCount * @param {PartialContext} _ctx * @param {readonly CohortAddress[]} list - * @param {string} title + * @param {(metric: string) => string} title * @returns {PartialChartOption} */ function createGroupedAddrCountChart(_ctx, list, title) { return { name: "address count", - title: `Address Count ${title}`, + title: title("Address Count"), bottom: list.map(({ color, name, addrCount }) => line({ metric: addrCount, name, color, unit: Unit.count }), ), @@ -478,7 +419,7 @@ function createGroupedAddrCountChart(_ctx, list, title) { * Create realized section for CohortAll/CohortFull (adjustedSopr + full ratio) * @param {PartialContext} ctx * @param {CohortAll | CohortFull} cohort - * @param {string} title + * @param {(metric: string) => string} title * @returns {PartialOptionsGroup} */ function createSingleRealizedSectionFull(ctx, cohort, title) { @@ -488,10 +429,14 @@ function createSingleRealizedSectionFull(ctx, cohort, title) { ...createSingleRealizedPriceChartsWithRatio(ctx, cohort, title), { name: "capitalization", - title: `Realized Cap ${title}`, - bottom: createSingleRealizedCapSeries(ctx, cohort), + title: title("Realized Cap"), + bottom: createSingleRealizedCapSeries(ctx, cohort, { + extra: createRealizedCapRatioSeries(ctx, cohort.tree), + }), }, - ...createSingleRealizedPnlSection(ctx, cohort, title), + ...createSingleRealizedPnlSection(ctx, cohort, title, { + extra: createRealizedPnlRatioSeries(ctx.colors, cohort.tree), + }), createSingleSoprSectionWithAdjusted(ctx, cohort, title), ], }; @@ -501,7 +446,7 @@ function createSingleRealizedSectionFull(ctx, cohort, title) { * Create realized section for CohortWithAdjusted (adjustedSopr but partial ratio) * @param {PartialContext} ctx * @param {CohortWithAdjusted} cohort - * @param {string} title + * @param {(metric: string) => string} title * @returns {PartialOptionsGroup} */ function createSingleRealizedSectionWithAdjusted(ctx, cohort, title) { @@ -511,7 +456,7 @@ function createSingleRealizedSectionWithAdjusted(ctx, cohort, title) { ...createSingleRealizedPriceChartsBasic(ctx, cohort, title), { name: "capitalization", - title: `Realized Cap ${title}`, + title: title("Realized Cap"), bottom: createSingleRealizedCapSeries(ctx, cohort), }, ...createSingleRealizedPnlSection(ctx, cohort, title), @@ -522,31 +467,34 @@ function createSingleRealizedSectionWithAdjusted(ctx, cohort, title) { /** * Create realized section with adjusted SOPR for grouped cohorts + * @template {readonly (CohortFull | CohortWithAdjusted)[]} T * @param {PartialContext} ctx - * @param {readonly (CohortFull | CohortWithAdjusted)[]} list - * @param {string} title + * @param {T} list + * @param {(metric: string) => string} title + * @param {Object} [options] + * @param {(cohort: T[number]) => AnyFetchedSeriesBlueprint[]} [options.ratioMetrics] - Generator for ratio metrics per cohort * @returns {PartialOptionsGroup} */ -function createGroupedRealizedSectionWithAdjusted(ctx, list, title) { +function createGroupedRealizedSectionWithAdjusted(ctx, list, title, { ratioMetrics } = {}) { return { name: "Realized", tree: [ { name: "Price", - title: `Realized Price ${title}`, + title: title("Realized Price"), top: createRealizedPriceSeries(list), }, { name: "Ratio", - title: `Realized Price Ratio ${title}`, + title: title("Realized Price Ratio"), bottom: createRealizedPriceRatioSeries(ctx, list), }, { name: "capitalization", - title: `Realized Cap ${title}`, + title: title("Realized Cap"), bottom: createGroupedRealizedCapSeries(list), }, - ...createGroupedRealizedPnlSections(ctx, list, title), + ...createGroupedRealizedPnlSections(ctx, list, title, { ratioMetrics }), createGroupedSoprSectionWithAdjusted(ctx, list, title), ], }; @@ -556,7 +504,7 @@ function createGroupedRealizedSectionWithAdjusted(ctx, list, title) { * Create realized section for CohortWithPercentiles (no adjustedSopr but full ratio) * @param {PartialContext} ctx * @param {CohortWithPercentiles} cohort - * @param {string} title + * @param {(metric: string) => string} title * @returns {PartialOptionsGroup} */ function createSingleRealizedSectionWithPercentiles(ctx, cohort, title) { @@ -566,10 +514,14 @@ function createSingleRealizedSectionWithPercentiles(ctx, cohort, title) { ...createSingleRealizedPriceChartsWithRatio(ctx, cohort, title), { name: "capitalization", - title: `Realized Cap ${title}`, - bottom: createSingleRealizedCapSeries(ctx, cohort), + title: title("Realized Cap"), + bottom: createSingleRealizedCapSeries(ctx, cohort, { + extra: createRealizedCapRatioSeries(ctx, cohort.tree), + }), }, - ...createSingleRealizedPnlSection(ctx, cohort, title), + ...createSingleRealizedPnlSection(ctx, cohort, title, { + extra: createRealizedPnlRatioSeries(ctx.colors, cohort.tree), + }), createSingleSoprSectionBasic(ctx, cohort, title), ], }; @@ -579,7 +531,7 @@ function createSingleRealizedSectionWithPercentiles(ctx, cohort, title) { * Create realized section for CohortBasic (no adjustedSopr, partial ratio) * @param {PartialContext} ctx * @param {CohortBasic | CohortAddress} cohort - * @param {string} title + * @param {(metric: string) => string} title * @returns {PartialOptionsGroup} */ function createSingleRealizedSectionBasic(ctx, cohort, title) { @@ -589,7 +541,7 @@ function createSingleRealizedSectionBasic(ctx, cohort, title) { ...createSingleRealizedPriceChartsBasic(ctx, cohort, title), { name: "capitalization", - title: `Realized Cap ${title}`, + title: title("Realized Cap"), bottom: createSingleRealizedCapSeries(ctx, cohort), }, ...createSingleRealizedPnlSection(ctx, cohort, title), @@ -600,31 +552,34 @@ function createSingleRealizedSectionBasic(ctx, cohort, title) { /** * Create realized section without adjusted SOPR for grouped cohorts + * @template {readonly (CohortWithPercentiles | CohortBasic | CohortAddress)[]} T * @param {PartialContext} ctx - * @param {readonly (CohortWithPercentiles | CohortBasic | CohortAddress)[]} list - * @param {string} title + * @param {T} list + * @param {(metric: string) => string} title + * @param {Object} [options] + * @param {(cohort: T[number]) => AnyFetchedSeriesBlueprint[]} [options.ratioMetrics] - Generator for ratio metrics per cohort * @returns {PartialOptionsGroup} */ -function createGroupedRealizedSectionBasic(ctx, list, title) { +function createGroupedRealizedSectionBasic(ctx, list, title, { ratioMetrics } = {}) { return { name: "Realized", tree: [ { name: "Price", - title: `Realized Price ${title}`, + title: title("Realized Price"), top: createRealizedPriceSeries(list), }, { name: "Ratio", - title: `Realized Price Ratio ${title}`, + title: title("Realized Price Ratio"), bottom: createRealizedPriceRatioSeries(ctx, list), }, { name: "capitalization", - title: `Realized Cap ${title}`, + title: title("Realized Cap"), bottom: createGroupedRealizedCapSeries(list), }, - ...createGroupedRealizedPnlSections(ctx, list, title), + ...createGroupedRealizedPnlSections(ctx, list, title, { ratioMetrics }), createGroupedSoprSectionBasic(ctx, list, title), ], }; @@ -633,14 +588,14 @@ function createGroupedRealizedSectionBasic(ctx, list, title) { /** * Create realized price chart for single cohort * @param {UtxoCohortObject} cohort - * @param {string} title + * @param {(metric: string) => string} title * @returns {PartialChartOption} */ function createSingleRealizedPriceChart(cohort, title) { const { tree, color } = cohort; return { name: "price", - title: `Realized Price ${title}`, + title: title("Realized Price"), top: [ line({ metric: tree.realized.realizedPrice, @@ -657,7 +612,7 @@ function createSingleRealizedPriceChart(cohort, title) { * (CohortAll, CohortFull, CohortWithPercentiles have RealizedPattern2/3 which has ActivePriceRatioPattern) * @param {PartialContext} ctx * @param {CohortAll | CohortFull | CohortWithPercentiles} cohort - * @param {string} title + * @param {(metric: string) => string} title * @returns {PartialOptionsTree} */ function createSingleRealizedPriceChartsWithRatio(ctx, cohort, title) { @@ -675,7 +630,7 @@ function createSingleRealizedPriceChartsWithRatio(ctx, cohort, title) { name: "MVRV", }), createZScoresFolder(ctx, { - title: `Realized Price ${title}`, + title: title("Realized Price"), legend: "price", price: tree.realized.realizedPrice, ratio, @@ -689,7 +644,7 @@ function createSingleRealizedPriceChartsWithRatio(ctx, cohort, title) { * (CohortWithAdjusted, CohortBasic have RealizedPattern/4 which has RealizedPriceExtraPattern) * @param {PartialContext} ctx * @param {CohortWithAdjusted | CohortBasic | CohortAddress} cohort - * @param {string} title + * @param {(metric: string) => string} title * @returns {PartialChartOption[]} */ function createSingleRealizedPriceChartsBasic(ctx, cohort, title) { @@ -698,7 +653,7 @@ function createSingleRealizedPriceChartsBasic(ctx, cohort, title) { createSingleRealizedPriceChart(cohort, title), { name: "ratio", - title: `Realized Price Ratio ${title}`, + title: title("Realized Price Ratio"), bottom: [ baseline({ metric: tree.realized.realizedPriceExtra.ratio, @@ -716,9 +671,11 @@ function createSingleRealizedPriceChartsBasic(ctx, cohort, title) { * Create realized cap series for single cohort * @param {PartialContext} ctx * @param {UtxoCohortObject} cohort + * @param {Object} [options] + * @param {AnyFetchedSeriesBlueprint[]} [options.extra] - Additional series (e.g., ratio for cohorts with RealizedWithCapRatio) * @returns {AnyFetchedSeriesBlueprint[]} */ -function createSingleRealizedCapSeries(ctx, cohort) { +function createSingleRealizedCapSeries(ctx, cohort, { extra = [] } = {}) { const { color, tree } = cohort; return [ @@ -742,22 +699,30 @@ function createSingleRealizedCapSeries(ctx, cohort) { defaultActive: false, }), priceLine({ ctx, unit: Unit.usd, defaultActive: false }), - ...("realizedCapRelToOwnMarketCap" in tree.realized - ? [ - baseline({ - metric: tree.realized.realizedCapRelToOwnMarketCap, - name: "ratio", - unit: Unit.pctOwnMcap, - options: { baseValue: { price: 100 } }, - }), - priceLine({ - ctx, - unit: Unit.pctOwnMcap, - defaultActive: true, - number: 100, - }), - ] - : []), + ...extra, + ]; +} + +/** + * Create realized cap ratio series (for cohorts with RealizedPattern2 or RealizedPattern3) + * @param {PartialContext} ctx + * @param {{ realized: RealizedWithExtras }} tree + * @returns {AnyFetchedSeriesBlueprint[]} + */ +function createRealizedCapRatioSeries(ctx, tree) { + return [ + baseline({ + metric: tree.realized.realizedCapRelToOwnMarketCap, + name: "ratio", + unit: Unit.pctOwnMcap, + options: { baseValue: { price: 100 } }, + }), + priceLine({ + ctx, + unit: Unit.pctOwnMcap, + defaultActive: true, + number: 100, + }), ]; } @@ -777,21 +742,56 @@ function createGroupedRealizedCapSeries(list) { ); } +/** + * Create realized PnL ratio series (for cohorts with RealizedPattern2 or RealizedPattern3) + * @param {Colors} colors + * @param {{ realized: RealizedWithExtras }} tree + * @returns {AnyFetchedSeriesBlueprint[]} + */ +function createRealizedPnlRatioSeries(colors, tree) { + return [ + line({ + metric: tree.realized.realizedProfitToLossRatio, + name: "Profit / Loss", + color: colors.yellow, + unit: Unit.ratio, + }), + ]; +} + +/** + * Create realized PnL ratio metrics generator for grouped cohorts with RealizedWithExtras + * @param {CohortWithRealizedExtras} cohort + * @returns {AnyFetchedSeriesBlueprint[]} + */ +function createGroupedRealizedPnlRatioMetrics(cohort) { + return [ + line({ + metric: cohort.tree.realized.realizedProfitToLossRatio, + name: cohort.name, + color: cohort.color, + unit: Unit.ratio, + }), + ]; +} + /** * Create realized PnL section for single cohort * @param {PartialContext} ctx * @param {UtxoCohortObject} cohort - * @param {string} title + * @param {(metric: string) => string} title + * @param {Object} [options] + * @param {AnyFetchedSeriesBlueprint[]} [options.extra] - Extra series (e.g., pnl ratio for cohorts with RealizedWithPnlRatio) * @returns {PartialOptionsTree} */ -function createSingleRealizedPnlSection(ctx, cohort, title) { +function createSingleRealizedPnlSection(ctx, cohort, title, { extra = [] } = {}) { const { colors, fromBlockCountWithUnit, fromBitcoinPatternWithUnit } = ctx; const { tree } = cohort; return [ { name: "pnl", - title: `Realized P&L ${title}`, + title: title("Realized P&L"), bottom: [ ...fromBlockCountWithUnit( tree.realized.realizedProfit, @@ -811,16 +811,7 @@ function createSingleRealizedPnlSection(ctx, cohort, title) { "Negative Loss", colors.red, ), - ...("realizedProfitToLossRatio" in tree.realized - ? [ - line({ - metric: tree.realized.realizedProfitToLossRatio, - name: "Profit / Loss", - color: colors.yellow, - unit: Unit.ratio, - }), - ] - : []), + ...extra, line({ metric: tree.realized.totalRealizedPnl, name: "Total", @@ -860,7 +851,7 @@ function createSingleRealizedPnlSection(ctx, cohort, title) { }, { name: "Net pnl", - title: `Net Realized P&L ${title}`, + title: title("Net Realized P&L"), bottom: [ ...fromBlockCountWithUnit( tree.realized.netRealizedPnl, @@ -906,16 +897,19 @@ function createSingleRealizedPnlSection(ctx, cohort, title) { /** * Create realized PnL sections for grouped cohorts + * @template {readonly UtxoCohortObject[]} T * @param {PartialContext} ctx - * @param {readonly UtxoCohortObject[]} list - * @param {string} title + * @param {T} list + * @param {(metric: string) => string} title + * @param {Object} [options] + * @param {(cohort: T[number]) => AnyFetchedSeriesBlueprint[]} [options.ratioMetrics] - Generator for ratio metrics per cohort * @returns {PartialOptionsTree} */ -function createGroupedRealizedPnlSections(ctx, list, title) { +function createGroupedRealizedPnlSections(ctx, list, title, { ratioMetrics } = {}) { return [ { name: "profit", - title: `Realized Profit ${title}`, + title: title("Realized Profit"), bottom: [ ...list.flatMap(({ color, name, tree }) => [ line({ @@ -936,7 +930,7 @@ function createGroupedRealizedPnlSections(ctx, list, title) { }, { name: "loss", - title: `Realized Loss ${title}`, + title: title("Realized Loss"), bottom: [ ...list.flatMap(({ color, name, tree }) => [ line({ @@ -957,31 +951,22 @@ function createGroupedRealizedPnlSections(ctx, list, title) { }, { name: "Total pnl", - title: `Total Realized P&L ${title}`, + title: title("Total Realized P&L"), bottom: [ - ...list.flatMap(({ color, name, tree }) => [ + ...list.flatMap((cohort) => [ line({ - metric: tree.realized.totalRealizedPnl, - name, - color, + metric: cohort.tree.realized.totalRealizedPnl, + name: cohort.name, + color: cohort.color, unit: Unit.usd, }), - ...("realizedProfitToLossRatio" in tree.realized - ? [ - line({ - metric: tree.realized.realizedProfitToLossRatio, - name, - color, - unit: Unit.ratio, - }), - ] - : []), + ...(ratioMetrics ? ratioMetrics(cohort) : []), ]), ], }, { name: "Net pnl", - title: `Net Realized P&L ${title}`, + title: title("Net Realized P&L"), bottom: [ ...list.flatMap(({ color, name, tree }) => [ baseline({ @@ -1006,7 +991,7 @@ function createGroupedRealizedPnlSections(ctx, list, title) { tree: [ { name: "profit", - title: `Cumulative Realized Profit ${title}`, + title: title("Cumulative Realized Profit"), bottom: list.flatMap(({ color, name, tree }) => [ line({ metric: tree.realized.realizedProfit.cumulative, @@ -1018,7 +1003,7 @@ function createGroupedRealizedPnlSections(ctx, list, title) { }, { name: "loss", - title: `Cumulative Realized Loss ${title}`, + title: title("Cumulative Realized Loss"), bottom: list.flatMap(({ color, name, tree }) => [ line({ metric: tree.realized.realizedLoss.cumulative, @@ -1030,7 +1015,7 @@ function createGroupedRealizedPnlSections(ctx, list, title) { }, { name: "Net pnl", - title: `Cumulative Net Realized P&L ${title}`, + title: title("Cumulative Net Realized P&L"), bottom: [ ...list.flatMap(({ color, name, tree }) => [ baseline({ @@ -1045,7 +1030,7 @@ function createGroupedRealizedPnlSections(ctx, list, title) { }, { name: "Net pnl 30d change", - title: `Net Realized P&L 30d Change ${title}`, + title: title("Net Realized P&L 30d Change"), bottom: [ ...list.flatMap(({ color, name, tree }) => [ baseline({ @@ -1085,24 +1070,10 @@ function createGroupedRealizedPnlSections(ctx, list, title) { // ============================================================================ /** - * @typedef {Object} CohortWithBaseSopr - * @property {string} name - * @property {Color} color - * @property {{ realized: { sopr: any, sopr7dEma: any, sopr30dEma: any } }} tree - */ - -/** - * @typedef {Object} CohortWithAdjustedSopr - * @property {string} name - * @property {Color} color - * @property {{ realized: { adjustedSopr: any, adjustedSopr7dEma: any, adjustedSopr30dEma: any } }} tree - */ - -/** - * Create single base SOPR chart + * Create single base SOPR chart (all UTXO cohorts have base SOPR) * @param {PartialContext} ctx - * @param {CohortWithBaseSopr} cohort - * @param {string} title + * @param {CohortAll | CohortFull | CohortWithAdjusted | CohortLongTerm | CohortAgeRange | CohortBasicWithMarketCap | CohortBasicWithoutMarketCap | CohortAddress} cohort + * @param {(metric: string) => string} title * @returns {PartialChartOption} */ function createSingleBaseSoprChart(ctx, cohort, title) { @@ -1111,7 +1082,7 @@ function createSingleBaseSoprChart(ctx, cohort, title) { return { name: "Normal", - title: `SOPR ${title}`, + title: title("SOPR"), bottom: [ baseline({ metric: tree.realized.sopr, @@ -1141,10 +1112,10 @@ function createSingleBaseSoprChart(ctx, cohort, title) { } /** - * Create single adjusted SOPR chart (only for age cohorts) + * Create single adjusted SOPR chart (cohorts with RealizedPattern3/4) * @param {PartialContext} ctx - * @param {CohortWithAdjustedSopr} cohort - * @param {string} title + * @param {CohortAll | CohortFull | CohortWithAdjusted} cohort + * @param {(metric: string) => string} title * @returns {PartialChartOption} */ function createSingleAdjustedSoprChart(ctx, cohort, title) { @@ -1153,7 +1124,7 @@ function createSingleAdjustedSoprChart(ctx, cohort, title) { return { name: "Adjusted", - title: `aSOPR ${title}`, + title: title("aSOPR"), bottom: [ baseline({ metric: tree.realized.adjustedSopr, @@ -1184,16 +1155,16 @@ function createSingleAdjustedSoprChart(ctx, cohort, title) { } /** - * Create grouped base SOPR chart + * Create grouped base SOPR chart (all UTXO cohorts have base SOPR) * @param {PartialContext} ctx - * @param {readonly CohortWithBaseSopr[]} list - * @param {string} title + * @param {readonly (CohortAll | CohortFull | CohortWithAdjusted | CohortLongTerm | CohortAgeRange | CohortBasicWithMarketCap | CohortBasicWithoutMarketCap | CohortAddress)[]} list + * @param {(metric: string) => string} title * @returns {PartialChartOption} */ function createGroupedBaseSoprChart(ctx, list, title) { return { name: "Normal", - title: `SOPR ${title}`, + title: title("SOPR"), bottom: [ ...list.flatMap(({ color, name, tree }) => [ baseline({ @@ -1226,16 +1197,16 @@ function createGroupedBaseSoprChart(ctx, list, title) { } /** - * Create grouped adjusted SOPR chart (only for age cohorts) + * Create grouped adjusted SOPR chart (cohorts with RealizedPattern3/4) * @param {PartialContext} ctx - * @param {readonly CohortWithAdjustedSopr[]} list - * @param {string} title + * @param {readonly (CohortFull | CohortWithAdjusted)[]} list + * @param {(metric: string) => string} title * @returns {PartialChartOption} */ function createGroupedAdjustedSoprChart(ctx, list, title) { return { name: "Adjusted", - title: `aSOPR ${title}`, + title: title("aSOPR"), bottom: [ ...list.flatMap(({ color, name, tree }) => [ baseline({ @@ -1275,7 +1246,7 @@ function createGroupedAdjustedSoprChart(ctx, list, title) { * Create SOPR section with adjusted SOPR (for cohorts with RealizedPattern3/4) * @param {PartialContext} ctx * @param {CohortAll | CohortFull | CohortWithAdjusted} cohort - * @param {string} title + * @param {(metric: string) => string} title * @returns {PartialOptionsGroup} */ function createSingleSoprSectionWithAdjusted(ctx, cohort, title) { @@ -1292,7 +1263,7 @@ function createSingleSoprSectionWithAdjusted(ctx, cohort, title) { * Create grouped SOPR section with adjusted SOPR * @param {PartialContext} ctx * @param {readonly (CohortFull | CohortWithAdjusted)[]} list - * @param {string} title + * @param {(metric: string) => string} title * @returns {PartialOptionsGroup} */ function createGroupedSoprSectionWithAdjusted(ctx, list, title) { @@ -1309,7 +1280,7 @@ function createGroupedSoprSectionWithAdjusted(ctx, list, title) { * Create SOPR section without adjusted SOPR (for cohorts with RealizedPattern/2) * @param {PartialContext} ctx * @param {CohortWithPercentiles | CohortBasic | CohortAddress} cohort - * @param {string} title + * @param {(metric: string) => string} title * @returns {PartialOptionsGroup} */ function createSingleSoprSectionBasic(ctx, cohort, title) { @@ -1323,7 +1294,7 @@ function createSingleSoprSectionBasic(ctx, cohort, title) { * Create grouped SOPR section without adjusted SOPR * @param {PartialContext} ctx * @param {readonly (CohortWithPercentiles | CohortBasic | CohortAddress)[]} list - * @param {string} title + * @param {(metric: string) => string} title * @returns {PartialOptionsGroup} */ function createGroupedSoprSectionBasic(ctx, list, title) { @@ -1527,13 +1498,13 @@ function createNetUnrealizedPnlBaseMetric(tree) { * Create NUPL chart for single cohort * @param {PartialContext} ctx * @param {RelativeWithNupl} rel - * @param {string} title + * @param {(metric: string) => string} title * @returns {PartialChartOption} */ function createNuplChart(ctx, rel, title) { return { name: "nupl", - title: `NUPL ${title}`, + title: title("NUPL"), bottom: [ baseline({ metric: rel.nupl, @@ -1549,13 +1520,13 @@ function createNuplChart(ctx, rel, title) { * Create NUPL chart for grouped cohorts * @param {PartialContext} ctx * @param {readonly { name: string, color: Color, tree: { relative: RelativeWithNupl } }[]} list - * @param {string} title + * @param {(metric: string) => string} title * @returns {PartialChartOption} */ function createGroupedNuplChart(ctx, list, title) { return { name: "nupl", - title: `NUPL ${title}`, + title: title("NUPL"), bottom: [ ...list.map(({ color, name, tree }) => baseline({ @@ -1570,6 +1541,87 @@ function createGroupedNuplChart(ctx, list, title) { }; } +// ============================================================================ +// Unrealized Section Builder (generic, type-safe composition) +// ============================================================================ + +/** + * Generic single unrealized section builder - callers pass typed metrics + * @param {Object} args + * @param {PartialContext} args.ctx + * @param {{ unrealized: UnrealizedPattern }} args.tree + * @param {(metric: string) => string} args.title + * @param {AnyFetchedSeriesBlueprint[]} [args.pnl] - Extra pnl metrics + * @param {AnyFetchedSeriesBlueprint[]} [args.netPnl] - Extra net pnl metrics + * @param {PartialChartOption[]} [args.charts] - Extra charts (e.g., nupl) + * @returns {PartialOptionsGroup} + */ +function createUnrealizedSection({ ctx, tree, title, pnl = [], netPnl = [], charts = [] }) { + return { + name: "Unrealized", + tree: [ + { + name: "pnl", + title: title("Unrealized P&L"), + bottom: [ + ...createUnrealizedPnlBaseMetrics(ctx, tree), + ...pnl, + priceLine({ ctx, unit: Unit.usd, defaultActive: false }), + ], + }, + { + name: "Net pnl", + title: title("Net Unrealized P&L"), + bottom: [ + createNetUnrealizedPnlBaseMetric(tree), + ...netPnl, + priceLine({ ctx, unit: Unit.usd }), + ], + }, + ...charts, + ], + }; +} + +/** + * Generic grouped unrealized section builder - callers pass typed metric generators + * @template {readonly { color: Color, name: string, tree: { unrealized: UnrealizedPattern } }[]} T + * @param {Object} args + * @param {PartialContext} args.ctx + * @param {T} args.list + * @param {(metric: string) => string} args.title + * @param {(cohort: T[number]) => AnyFetchedSeriesBlueprint[]} [args.netPnlMetrics] - Generator for extra net pnl metrics per cohort + * @param {AnyFetchedSeriesBlueprint[]} [args.priceLines] - Extra price lines for net pnl chart + * @param {PartialChartOption[]} [args.charts] - Extra charts + * @returns {PartialOptionsGroup} + */ +function createGroupedUnrealizedSection({ ctx, list, title, netPnlMetrics, priceLines = [], charts = [] }) { + return { + name: "Unrealized", + tree: [ + ...createGroupedUnrealizedBaseCharts(list, title), + { + name: "Net pnl", + title: title("Net Unrealized P&L"), + bottom: [ + ...list.flatMap((cohort) => [ + baseline({ + metric: cohort.tree.unrealized.netUnrealizedPnl, + name: cohort.name, + color: cohort.color, + unit: Unit.usd, + }), + ...(netPnlMetrics ? netPnlMetrics(cohort) : []), + ]), + priceLine({ ctx, unit: Unit.usd }), + ...priceLines, + ], + }, + ...charts, + ], + }; +} + // ============================================================================ // Unrealized Section Variants (by cohort capability) // ============================================================================ @@ -1578,241 +1630,116 @@ function createGroupedNuplChart(ctx, list, title) { * Unrealized section for All cohort (only RelToOwnPnl) * @param {PartialContext} ctx * @param {CohortAll} cohort - * @param {string} title - * @returns {PartialOptionsGroup} + * @param {(metric: string) => string} title */ function createSingleUnrealizedSectionAll(ctx, cohort, title) { const { tree } = cohort; - return { - name: "Unrealized", - tree: [ - { - name: "pnl", - title: `Unrealized P&L ${title}`, - bottom: [ - ...createUnrealizedPnlBaseMetrics(ctx, tree), - ...createUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative), - priceLine({ ctx, unit: Unit.usd, defaultActive: false }), - ], - }, - { - name: "Net pnl", - title: `Net Unrealized P&L ${title}`, - bottom: [ - createNetUnrealizedPnlBaseMetric(tree), - ...createNetUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative), - priceLine({ ctx, unit: Unit.usd }), - ], - }, - ], - }; + return createUnrealizedSection({ + ctx, + tree, + title, + pnl: createUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative), + netPnl: createNetUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative), + }); } /** * Unrealized section for Full cohort (all capabilities: MarketCap + OwnMarketCap + OwnPnl) * @param {PartialContext} ctx * @param {CohortFull} cohort - * @param {string} title - * @returns {PartialOptionsGroup} + * @param {(metric: string) => string} title */ function createSingleUnrealizedSectionFull(ctx, cohort, title) { const { tree } = cohort; - return { - name: "Unrealized", - tree: [ - { - name: "pnl", - title: `Unrealized P&L ${title}`, - bottom: [ - ...createUnrealizedPnlBaseMetrics(ctx, tree), - ...createUnrealizedPnlRelToMarketCapMetrics(ctx, tree.relative), - ...createUnrealizedPnlRelToOwnMarketCapMetrics(ctx, tree.relative), - ...createUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative), - priceLine({ ctx, unit: Unit.usd, defaultActive: false }), - priceLine({ ctx, unit: Unit.pctMcap, defaultActive: false }), - ], - }, - { - name: "Net pnl", - title: `Net Unrealized P&L ${title}`, - bottom: [ - createNetUnrealizedPnlBaseMetric(tree), - ...createNetUnrealizedPnlRelToMarketCapMetrics(tree.relative), - ...createNetUnrealizedPnlRelToOwnMarketCapMetrics(ctx, tree.relative), - ...createNetUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative), - priceLine({ ctx, unit: Unit.usd }), - priceLine({ ctx, unit: Unit.pctMcap }), - ], - }, - createNuplChart(ctx, tree.relative, title), + return createUnrealizedSection({ + ctx, + tree, + title, + pnl: [ + ...createUnrealizedPnlRelToMarketCapMetrics(ctx, tree.relative), + ...createUnrealizedPnlRelToOwnMarketCapMetrics(ctx, tree.relative), + ...createUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative), + priceLine({ ctx, unit: Unit.pctMcap, defaultActive: false }), ], - }; + netPnl: [ + ...createNetUnrealizedPnlRelToMarketCapMetrics(tree.relative), + ...createNetUnrealizedPnlRelToOwnMarketCapMetrics(ctx, tree.relative), + ...createNetUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative), + priceLine({ ctx, unit: Unit.pctMcap }), + ], + charts: [createNuplChart(ctx, tree.relative, title)], + }); } /** - * Unrealized section for WithAdjusted cohort (only MarketCap) + * Unrealized section for WithAdjusted cohort (MarketCap + nupl) * @param {PartialContext} ctx * @param {CohortWithAdjusted} cohort - * @param {string} title - * @returns {PartialOptionsGroup} + * @param {(metric: string) => string} title */ function createSingleUnrealizedSectionWithMarketCap(ctx, cohort, title) { const { tree } = cohort; - return { - name: "Unrealized", - tree: [ - { - name: "pnl", - title: `Unrealized P&L ${title}`, - bottom: [ - ...createUnrealizedPnlBaseMetrics(ctx, tree), - ...createUnrealizedPnlRelToMarketCapMetrics(ctx, tree.relative), - priceLine({ ctx, unit: Unit.usd, defaultActive: false }), - priceLine({ ctx, unit: Unit.pctMcap, defaultActive: false }), - ], - }, - { - name: "Net pnl", - title: `Net Unrealized P&L ${title}`, - bottom: [ - createNetUnrealizedPnlBaseMetric(tree), - ...createNetUnrealizedPnlRelToMarketCapMetrics(tree.relative), - priceLine({ ctx, unit: Unit.usd }), - priceLine({ ctx, unit: Unit.pctMcap }), - ], - }, - ...("nupl" in tree.relative - ? [createNuplChart(ctx, tree.relative, title)] - : []), + return createUnrealizedSection({ + ctx, + tree, + title, + pnl: [ + ...createUnrealizedPnlRelToMarketCapMetrics(ctx, tree.relative), + priceLine({ ctx, unit: Unit.pctMcap, defaultActive: false }), ], - }; + netPnl: [ + ...createNetUnrealizedPnlRelToMarketCapMetrics(tree.relative), + priceLine({ ctx, unit: Unit.pctMcap }), + ], + charts: [createNuplChart(ctx, tree.relative, title)], + }); } -/** - * Unrealized section for patterns with OwnMarketCap + OwnPnl (RelativePattern2, RelativePattern5) - * Used by: LongTerm, AgeRange - * @param {PartialContext} ctx - * @param {{ tree: { unrealized: PatternAll["unrealized"], relative: RelativeWithOwnMarketCap } }} cohort - * @param {string} title - * @returns {PartialOptionsGroup} - */ -function createSingleUnrealizedSectionWithOwnCaps(ctx, cohort, title) { - const { tree } = cohort; - return { - name: "Unrealized", - tree: [ - { - name: "pnl", - title: `Unrealized P&L ${title}`, - bottom: [ - ...createUnrealizedPnlBaseMetrics(ctx, tree), - ...createUnrealizedPnlRelToOwnMarketCapMetrics(ctx, tree.relative), - ...createUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative), - priceLine({ ctx, unit: Unit.usd, defaultActive: false }), - ], - }, - { - name: "Net pnl", - title: `Net Unrealized P&L ${title}`, - bottom: [ - createNetUnrealizedPnlBaseMetric(tree), - ...createNetUnrealizedPnlRelToOwnMarketCapMetrics(ctx, tree.relative), - ...createNetUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative), - priceLine({ ctx, unit: Unit.usd }), - ], - }, - ...("nupl" in tree.relative - ? [createNuplChart(ctx, tree.relative, title)] - : []), - ], - }; -} /** * Unrealized section WITH RelToMarketCap metrics (for CohortBasicWithMarketCap) - * Used by: minAge.*, geAmount.*, ltAmount.* * @param {PartialContext} ctx * @param {CohortBasicWithMarketCap} cohort - * @param {string} title - * @returns {PartialOptionsGroup} + * @param {(metric: string) => string} title */ function createSingleUnrealizedSectionWithMarketCapOnly(ctx, cohort, title) { const { tree } = cohort; - - return { - name: "Unrealized", - tree: [ - { - name: "pnl", - title: `Unrealized P&L ${title}`, - bottom: [ - ...createUnrealizedPnlBaseMetrics(ctx, tree), - ...createUnrealizedPnlRelToMarketCapMetrics(ctx, tree.relative), - priceLine({ ctx, unit: Unit.usd, defaultActive: false }), - priceLine({ ctx, unit: Unit.pctMcap, defaultActive: false }), - ], - }, - { - name: "Net pnl", - title: `Net Unrealized P&L ${title}`, - bottom: [ - createNetUnrealizedPnlBaseMetric(tree), - ...createNetUnrealizedPnlRelToMarketCapMetrics(tree.relative), - priceLine({ ctx, unit: Unit.usd }), - priceLine({ ctx, unit: Unit.pctMcap }), - ], - }, - ...("nupl" in tree.relative - ? [createNuplChart(ctx, tree.relative, title)] - : []), + return createUnrealizedSection({ + ctx, + tree, + title, + pnl: [ + ...createUnrealizedPnlRelToMarketCapMetrics(ctx, tree.relative), + priceLine({ ctx, unit: Unit.pctMcap, defaultActive: false }), ], - }; + netPnl: [ + ...createNetUnrealizedPnlRelToMarketCapMetrics(tree.relative), + priceLine({ ctx, unit: Unit.pctMcap }), + ], + charts: [createNuplChart(ctx, tree.relative, title)], + }); } /** * Unrealized section with only base metrics (no RelToMarketCap) - * Used by: epoch.*, amountRange.*, year.* * @param {PartialContext} ctx * @param {CohortBasicWithoutMarketCap} cohort - * @param {string} title - * @returns {PartialOptionsGroup} + * @param {(metric: string) => string} title */ function createSingleUnrealizedSectionBase(ctx, cohort, title) { - const { tree } = cohort; - - return { - name: "Unrealized", - tree: [ - { - name: "pnl", - title: `Unrealized P&L ${title}`, - bottom: [ - ...createUnrealizedPnlBaseMetrics(ctx, tree), - priceLine({ ctx, unit: Unit.usd, defaultActive: false }), - ], - }, - { - name: "Net pnl", - title: `Net Unrealized P&L ${title}`, - bottom: [ - createNetUnrealizedPnlBaseMetric(tree), - priceLine({ ctx, unit: Unit.usd }), - ], - }, - ], - }; + return createUnrealizedSection({ ctx, tree: cohort.tree, title }); } /** * Grouped unrealized base charts (profit, loss, total pnl) * @param {readonly { color: Color, name: string, tree: { unrealized: PatternAll["unrealized"] } }[]} list - * @param {string} title + * @param {(metric: string) => string} title */ function createGroupedUnrealizedBaseCharts(list, title) { return [ { name: "profit", - title: `Unrealized Profit ${title}`, + title: title("Unrealized Profit"), bottom: list.flatMap(({ color, name, tree }) => [ line({ metric: tree.unrealized.unrealizedProfit, @@ -1824,7 +1751,7 @@ function createGroupedUnrealizedBaseCharts(list, title) { }, { name: "loss", - title: `Unrealized Loss ${title}`, + title: title("Unrealized Loss"), bottom: list.flatMap(({ color, name, tree }) => [ line({ metric: tree.unrealized.unrealizedLoss, @@ -1836,7 +1763,7 @@ function createGroupedUnrealizedBaseCharts(list, title) { }, { name: "total pnl", - title: `Unrealized Total P&L ${title}`, + title: title("Unrealized Total P&L"), bottom: list.flatMap(({ color, name, tree }) => [ line({ metric: tree.unrealized.totalUnrealizedPnl, @@ -1853,398 +1780,184 @@ function createGroupedUnrealizedBaseCharts(list, title) { * Grouped unrealized section for Full cohorts (all relative capabilities) * @param {PartialContext} ctx * @param {readonly CohortFull[]} list - * @param {string} title - * @returns {PartialOptionsGroup} + * @param {(metric: string) => string} title */ function createGroupedUnrealizedSectionFull(ctx, list, title) { - return { - name: "Unrealized", - tree: [ - ...createGroupedUnrealizedBaseCharts(list, title), - { - name: "Net pnl", - title: `Net Unrealized P&L ${title}`, - bottom: [ - ...list.flatMap(({ color, name, tree }) => [ - baseline({ - metric: tree.unrealized.netUnrealizedPnl, - name, - color, - unit: Unit.usd, - }), - baseline({ - metric: tree.relative.netUnrealizedPnlRelToMarketCap, - name, - color, - unit: Unit.pctMcap, - }), - baseline({ - metric: tree.relative.netUnrealizedPnlRelToOwnMarketCap, - name, - color, - unit: Unit.pctOwnMcap, - }), - baseline({ - metric: tree.relative.netUnrealizedPnlRelToOwnTotalUnrealizedPnl, - name, - color, - unit: Unit.pctOwnPnl, - }), - ]), - priceLine({ ctx, unit: Unit.usd }), - priceLine({ ctx, unit: Unit.pctMcap }), - priceLine({ ctx, unit: Unit.pctOwnMcap }), - priceLine({ ctx, unit: Unit.pctOwnPnl }), - ], - }, - createGroupedNuplChart(ctx, list, title), + return createGroupedUnrealizedSection({ + ctx, + list, + title, + netPnlMetrics: ({ color, name, tree }) => [ + baseline({ metric: tree.relative.netUnrealizedPnlRelToMarketCap, name, color, unit: Unit.pctMcap }), + baseline({ metric: tree.relative.netUnrealizedPnlRelToOwnMarketCap, name, color, unit: Unit.pctOwnMcap }), + baseline({ metric: tree.relative.netUnrealizedPnlRelToOwnTotalUnrealizedPnl, name, color, unit: Unit.pctOwnPnl }), ], - }; + priceLines: [ + priceLine({ ctx, unit: Unit.pctMcap }), + priceLine({ ctx, unit: Unit.pctOwnMcap }), + priceLine({ ctx, unit: Unit.pctOwnPnl }), + ], + charts: [createGroupedNuplChart(ctx, list, title)], + }); } /** - * Grouped unrealized section for WithAdjusted cohorts (only MarketCap) + * Grouped unrealized section for WithAdjusted cohorts (MarketCap + nupl) * @param {PartialContext} ctx * @param {readonly CohortWithAdjusted[]} list - * @param {string} title - * @returns {PartialOptionsGroup} + * @param {(metric: string) => string} title */ function createGroupedUnrealizedSectionWithMarketCap(ctx, list, title) { - return { - name: "Unrealized", - tree: [ - ...createGroupedUnrealizedBaseCharts(list, title), - { - name: "Net pnl", - title: `Net Unrealized P&L ${title}`, - bottom: [ - ...list.flatMap(({ color, name, tree }) => [ - baseline({ - metric: tree.unrealized.netUnrealizedPnl, - name, - color, - unit: Unit.usd, - }), - baseline({ - metric: tree.relative.netUnrealizedPnlRelToMarketCap, - name, - color, - unit: Unit.pctMcap, - }), - ]), - priceLine({ ctx, unit: Unit.usd }), - priceLine({ ctx, unit: Unit.pctMcap }), - ], - }, - createGroupedNuplChart(ctx, list, title), + return createGroupedUnrealizedSection({ + ctx, + list, + title, + netPnlMetrics: ({ color, name, tree }) => [ + baseline({ metric: tree.relative.netUnrealizedPnlRelToMarketCap, name, color, unit: Unit.pctMcap }), ], - }; + priceLines: [priceLine({ ctx, unit: Unit.pctMcap })], + charts: [createGroupedNuplChart(ctx, list, title)], + }); } -/** - * Grouped unrealized section for WithPercentiles cohorts (OwnMarketCap + OwnPnl) - * @param {PartialContext} ctx - * @param {readonly CohortWithPercentiles[]} list - * @param {string} title - * @returns {PartialOptionsGroup} - */ -function createGroupedUnrealizedSectionWithOwnCaps(ctx, list, title) { - const cohortsWithNupl = - /** @type {CohortLongTerm[]} */ ( - list.filter(({ tree }) => "nupl" in tree.relative) - ); - return { - name: "Unrealized", - tree: [ - ...createGroupedUnrealizedBaseCharts(list, title), - { - name: "Net pnl", - title: `Net Unrealized P&L ${title}`, - bottom: [ - ...list.flatMap(({ color, name, tree }) => [ - baseline({ - metric: tree.unrealized.netUnrealizedPnl, - name, - color, - unit: Unit.usd, - }), - baseline({ - metric: tree.relative.netUnrealizedPnlRelToOwnMarketCap, - name, - color, - unit: Unit.pctOwnMcap, - }), - baseline({ - metric: tree.relative.netUnrealizedPnlRelToOwnTotalUnrealizedPnl, - name, - color, - unit: Unit.pctOwnPnl, - }), - ]), - priceLine({ ctx, unit: Unit.usd }), - priceLine({ ctx, unit: Unit.pctOwnMcap }), - priceLine({ ctx, unit: Unit.pctOwnPnl }), - ], - }, - ...(cohortsWithNupl.length > 0 - ? [createGroupedNuplChart(ctx, cohortsWithNupl, title)] - : []), - ], - }; -} /** * Grouped unrealized section WITH RelToMarketCap (for CohortBasicWithMarketCap) - * Used by: minAge.*, geAmount.*, ltAmount.* * @param {PartialContext} ctx * @param {readonly CohortBasicWithMarketCap[]} list - * @param {string} title - * @returns {PartialOptionsGroup} + * @param {(metric: string) => string} title */ function createGroupedUnrealizedSectionWithMarketCapOnly(ctx, list, title) { - return { - name: "Unrealized", - tree: [ - ...createGroupedUnrealizedBaseCharts(list, title), - { - name: "Net pnl", - title: `Net Unrealized P&L ${title}`, - bottom: [ - ...list.flatMap(({ color, name, tree }) => [ - baseline({ - metric: tree.unrealized.netUnrealizedPnl, - name, - color, - unit: Unit.usd, - }), - baseline({ - metric: tree.relative.netUnrealizedPnlRelToMarketCap, - name, - color, - unit: Unit.pctMcap, - }), - ]), - priceLine({ ctx, unit: Unit.usd }), - priceLine({ ctx, unit: Unit.pctMcap }), - ], - }, - createGroupedNuplChart(ctx, list, title), + return createGroupedUnrealizedSection({ + ctx, + list, + title, + netPnlMetrics: ({ color, name, tree }) => [ + baseline({ metric: tree.relative.netUnrealizedPnlRelToMarketCap, name, color, unit: Unit.pctMcap }), ], - }; + priceLines: [priceLine({ ctx, unit: Unit.pctMcap })], + charts: [createGroupedNuplChart(ctx, list, title)], + }); } /** * Grouped unrealized section without RelToMarketCap (for CohortBasicWithoutMarketCap) * @param {PartialContext} ctx * @param {readonly CohortBasicWithoutMarketCap[]} list - * @param {string} title - * @returns {PartialOptionsGroup} + * @param {(metric: string) => string} title */ function createGroupedUnrealizedSectionBase(ctx, list, title) { - return { - name: "Unrealized", - tree: [ - ...createGroupedUnrealizedBaseCharts(list, title), - { - name: "Net pnl", - title: `Net Unrealized P&L ${title}`, - bottom: [ - ...list.flatMap(({ color, name, tree }) => [ - baseline({ - metric: tree.unrealized.netUnrealizedPnl, - name, - color, - unit: Unit.usd, - }), - ]), - priceLine({ ctx, unit: Unit.usd }), - ], - }, - ], - }; + return createGroupedUnrealizedSection({ ctx, list, title }); } /** - * Unrealized section for LongTerm cohort (has nupl via RelativePattern5) - * @param {PartialContext} ctx - * @param {CohortLongTerm} cohort - * @param {string} title - * @returns {PartialOptionsGroup} + * Unrealized section for cohorts with nupl (OwnMarketCap + OwnPnl + nupl) + * @param {Object} args + * @param {PartialContext} args.ctx + * @param {CohortWithNuplPercentiles} args.cohort + * @param {(metric: string) => string} args.title */ -function createSingleUnrealizedSectionLongTerm(ctx, cohort, title) { +function createSingleUnrealizedSectionWithNupl({ ctx, cohort, title }) { const { tree } = cohort; - return { - name: "Unrealized", - tree: [ - { - name: "pnl", - title: `Unrealized P&L ${title}`, - bottom: [ - ...createUnrealizedPnlBaseMetrics(ctx, tree), - ...createUnrealizedPnlRelToOwnMarketCapMetrics(ctx, tree.relative), - ...createUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative), - priceLine({ ctx, unit: Unit.usd, defaultActive: false }), - ], - }, - { - name: "Net pnl", - title: `Net Unrealized P&L ${title}`, - bottom: [ - createNetUnrealizedPnlBaseMetric(tree), - ...createNetUnrealizedPnlRelToOwnMarketCapMetrics(ctx, tree.relative), - ...createNetUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative), - priceLine({ ctx, unit: Unit.usd }), - ], - }, - createNuplChart(ctx, tree.relative, title), + return createUnrealizedSection({ + ctx, + tree, + title, + pnl: [ + ...createUnrealizedPnlRelToOwnMarketCapMetrics(ctx, tree.relative), + ...createUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative), ], - }; + netPnl: [ + ...createNetUnrealizedPnlRelToOwnMarketCapMetrics(ctx, tree.relative), + ...createNetUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative), + ], + charts: [createNuplChart(ctx, tree.relative, title)], + }); } /** - * Grouped unrealized section for LongTerm cohorts (has nupl via RelativePattern5) - * @param {PartialContext} ctx - * @param {readonly CohortLongTerm[]} list - * @param {string} title - * @returns {PartialOptionsGroup} + * Grouped unrealized section for cohorts with nupl (OwnMarketCap + OwnPnl + nupl) + * @param {Object} args + * @param {PartialContext} args.ctx + * @param {readonly CohortWithNuplPercentiles[]} args.list + * @param {(metric: string) => string} args.title */ -function createGroupedUnrealizedSectionLongTerm(ctx, list, title) { - return { - name: "Unrealized", - tree: [ - ...createGroupedUnrealizedBaseCharts(list, title), - { - name: "Net pnl", - title: `Net Unrealized P&L ${title}`, - bottom: [ - ...list.flatMap(({ color, name, tree }) => [ - baseline({ - metric: tree.unrealized.netUnrealizedPnl, - name, - color, - unit: Unit.usd, - }), - baseline({ - metric: tree.relative.netUnrealizedPnlRelToOwnMarketCap, - name, - color, - unit: Unit.pctOwnMcap, - }), - baseline({ - metric: tree.relative.netUnrealizedPnlRelToOwnTotalUnrealizedPnl, - name, - color, - unit: Unit.pctOwnPnl, - }), - ]), - priceLine({ ctx, unit: Unit.usd }), - priceLine({ ctx, unit: Unit.pctOwnMcap }), - priceLine({ ctx, unit: Unit.pctOwnPnl }), - ], - }, - createGroupedNuplChart(ctx, list, title), +function createGroupedUnrealizedSectionWithNupl({ ctx, list, title }) { + return createGroupedUnrealizedSection({ + ctx, + list, + title, + netPnlMetrics: ({ color, name, tree }) => [ + baseline({ metric: tree.relative.netUnrealizedPnlRelToOwnMarketCap, name, color, unit: Unit.pctOwnMcap }), + baseline({ metric: tree.relative.netUnrealizedPnlRelToOwnTotalUnrealizedPnl, name, color, unit: Unit.pctOwnPnl }), ], - }; + priceLines: [priceLine({ ctx, unit: Unit.pctOwnMcap }), priceLine({ ctx, unit: Unit.pctOwnPnl })], + charts: [createGroupedNuplChart(ctx, list, title)], + }); } /** * Unrealized section for AgeRange cohort (no nupl via RelativePattern2) * @param {PartialContext} ctx * @param {CohortAgeRange} cohort - * @param {string} title - * @returns {PartialOptionsGroup} + * @param {(metric: string) => string} title */ function createSingleUnrealizedSectionAgeRange(ctx, cohort, title) { const { tree } = cohort; - return { - name: "Unrealized", - tree: [ - { - name: "pnl", - title: `Unrealized P&L ${title}`, - bottom: [ - ...createUnrealizedPnlBaseMetrics(ctx, tree), - ...createUnrealizedPnlRelToOwnMarketCapMetrics(ctx, tree.relative), - ...createUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative), - priceLine({ ctx, unit: Unit.usd, defaultActive: false }), - ], - }, - { - name: "Net pnl", - title: `Net Unrealized P&L ${title}`, - bottom: [ - createNetUnrealizedPnlBaseMetric(tree), - ...createNetUnrealizedPnlRelToOwnMarketCapMetrics(ctx, tree.relative), - ...createNetUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative), - priceLine({ ctx, unit: Unit.usd }), - ], - }, + return createUnrealizedSection({ + ctx, + tree, + title, + pnl: [ + ...createUnrealizedPnlRelToOwnMarketCapMetrics(ctx, tree.relative), + ...createUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative), ], - }; + netPnl: [ + ...createNetUnrealizedPnlRelToOwnMarketCapMetrics(ctx, tree.relative), + ...createNetUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative), + ], + }); } /** * Grouped unrealized section for AgeRange cohorts (no nupl via RelativePattern2) * @param {PartialContext} ctx * @param {readonly CohortAgeRange[]} list - * @param {string} title - * @returns {PartialOptionsGroup} + * @param {(metric: string) => string} title */ function createGroupedUnrealizedSectionAgeRange(ctx, list, title) { - return { - name: "Unrealized", - tree: [ - ...createGroupedUnrealizedBaseCharts(list, title), - { - name: "Net pnl", - title: `Net Unrealized P&L ${title}`, - bottom: [ - ...list.flatMap(({ color, name, tree }) => [ - baseline({ - metric: tree.unrealized.netUnrealizedPnl, - name, - color, - unit: Unit.usd, - }), - baseline({ - metric: tree.relative.netUnrealizedPnlRelToOwnMarketCap, - name, - color, - unit: Unit.pctOwnMcap, - }), - baseline({ - metric: tree.relative.netUnrealizedPnlRelToOwnTotalUnrealizedPnl, - name, - color, - unit: Unit.pctOwnPnl, - }), - ]), - priceLine({ ctx, unit: Unit.usd }), - priceLine({ ctx, unit: Unit.pctOwnMcap }), - priceLine({ ctx, unit: Unit.pctOwnPnl }), - ], - }, + return createGroupedUnrealizedSection({ + ctx, + list, + title, + netPnlMetrics: ({ color, name, tree }) => [ + baseline({ metric: tree.relative.netUnrealizedPnlRelToOwnMarketCap, name, color, unit: Unit.pctOwnMcap }), + baseline({ metric: tree.relative.netUnrealizedPnlRelToOwnTotalUnrealizedPnl, name, color, unit: Unit.pctOwnPnl }), ], - }; + priceLines: [priceLine({ ctx, unit: Unit.pctOwnMcap }), priceLine({ ctx, unit: Unit.pctOwnPnl })], + }); } +// ============================================================================ +// Cost Basis Section Builders (generic, type-safe composition) +// ============================================================================ + /** - * Create cost basis section for single cohort WITH percentiles - * @param {PartialContext} ctx - * @param {CohortAll | CohortFull | CohortWithPercentiles} cohort - * @param {string} title + * Generic single cost basis section builder - callers pass optional percentiles + * @param {Object} args + * @param {UtxoCohortObject} args.cohort + * @param {(metric: string) => string} args.title + * @param {PartialChartOption[]} [args.charts] - Extra charts (e.g., percentiles) * @returns {PartialOptionsGroup} */ -function createSingleCostBasisSectionWithPercentiles(ctx, cohort, title) { - const { colors } = ctx; +function createCostBasisSection({ cohort, title, charts = [] }) { const { color, tree } = cohort; - return { name: "Cost Basis", tree: [ { - name: "Average", - title: `Cost Basis ${title}`, + name: charts.length > 0 ? "Average" : "cost basis", + title: title("Cost Basis"), top: [ line({ metric: tree.realized.realizedPrice, @@ -2267,97 +1980,161 @@ function createSingleCostBasisSectionWithPercentiles(ctx, cohort, title) { }), ], }, + ...charts, + ], + }; +} + +/** + * Generic grouped cost basis section builder - callers pass optional percentiles + * @template {readonly UtxoCohortObject[]} T + * @param {Object} args + * @param {T} args.list + * @param {(metric: string) => string} args.title + * @param {PartialChartOption[]} [args.charts] - Extra charts (e.g., percentiles) + * @returns {PartialOptionsGroup} + */ +function createGroupedCostBasisSection({ list, title, charts = [] }) { + return { + name: "Cost Basis", + tree: [ + { + name: "Average", + title: title("Average Cost Basis"), + top: list.map(({ color, name, tree }) => + line({ + metric: tree.realized.realizedPrice, + name, + color, + unit: Unit.usd, + }), + ), + }, + { + name: "Min", + title: title("Min Cost Basis"), + top: list.map(({ color, name, tree }) => + line({ metric: tree.costBasis.min, name, color, unit: Unit.usd }), + ), + }, + { + name: "Max", + title: title("Max Cost Basis"), + top: list.map(({ color, name, tree }) => + line({ metric: tree.costBasis.max, name, color, unit: Unit.usd }), + ), + }, + ...charts, + ], + }; +} + +// ============================================================================ +// Cost Basis Section Variants (by cohort capability) +// ============================================================================ + +/** + * Create cost basis section for single cohort WITH percentiles + * @param {PartialContext} ctx + * @param {CohortAll | CohortFull | CohortWithPercentiles} cohort + * @param {(metric: string) => string} title + * @returns {PartialOptionsGroup} + */ +function createSingleCostBasisSectionWithPercentiles(ctx, cohort, title) { + const { colors } = ctx; + return createCostBasisSection({ + cohort, + title, + charts: [ { name: "percentiles", - title: `Cost Basis Percentiles ${title}`, + title: title("Cost Basis Percentiles"), top: createCostBasisPercentilesSeries(colors, [cohort], false), }, ], - }; + }); } /** * Create cost basis section for grouped cohorts WITH percentiles * @param {PartialContext} ctx * @param {readonly (CohortFull | CohortWithPercentiles)[]} list - * @param {string} title + * @param {(metric: string) => string} title * @returns {PartialOptionsGroup} */ function createGroupedCostBasisSectionWithPercentiles(ctx, list, title) { const { colors } = ctx; - return { - name: "Cost Basis", - tree: [ - { - name: "Average", - title: `Average Cost Basis ${title}`, - top: list.map(({ color, name, tree }) => - line({ - metric: tree.realized.realizedPrice, - name, - color, - unit: Unit.usd, - }), - ), - }, - { - name: "Min", - title: `Min Cost Basis ${title}`, - top: list.map(({ color, name, tree }) => - line({ metric: tree.costBasis.min, name, color, unit: Unit.usd }), - ), - }, - { - name: "Max", - title: `Max Cost Basis ${title}`, - top: list.map(({ color, name, tree }) => - line({ metric: tree.costBasis.max, name, color, unit: Unit.usd }), - ), - }, + return createGroupedCostBasisSection({ + list, + title, + charts: [ { name: "percentiles", - title: `Cost Basis Percentiles ${title}`, + title: title("Cost Basis Percentiles"), top: createCostBasisPercentilesSeries(colors, list, true), }, ], - }; + }); } + +// ============================================================================ +// Activity Section Builders (generic, type-safe composition) +// ============================================================================ + /** - * Create cost basis section for single cohort (no percentiles) - * @param {CohortWithAdjusted | CohortBasic | CohortAddress} cohort - * @param {string} title + * Generic single activity section builder - callers pass optional extra value metrics + * @param {Object} args + * @param {PartialContext} args.ctx + * @param {UtxoCohortObject} args.cohort + * @param {(metric: string) => string} args.title + * @param {AnyFetchedSeriesBlueprint[]} [args.valueMetrics] - Extra value metrics (e.g., adjusted) * @returns {PartialOptionsGroup} */ -function createSingleCostBasisSection(cohort, title) { - const { color, tree } = cohort; +function createActivitySection({ ctx, cohort, title, valueMetrics = [] }) { + const { colors, fromBlockCountWithUnit, fromBitcoinPatternWithUnit } = ctx; + const { tree, color } = cohort; return { - name: "Cost Basis", + name: "Activity", tree: [ { - name: "cost basis", - title: `Cost Basis ${title}`, - top: [ - line({ - metric: tree.realized.realizedPrice, - name: "Average", - color, - unit: Unit.usd, - }), - line({ - metric: tree.costBasis.min, - name: "Min", - color, - unit: Unit.usd, - defaultActive: false, - }), - line({ - metric: tree.costBasis.max, - name: "Max", - color, - unit: Unit.usd, - }), + name: "Sent", + title: title("Sent"), + bottom: [ + ...fromBlockCountWithUnit(tree.activity.sent.sats, Unit.sats, undefined, color), + ...fromBitcoinPatternWithUnit(tree.activity.sent.bitcoin, Unit.btc, undefined, color), + ...fromBlockCountWithUnit(tree.activity.sent.dollars, Unit.usd, undefined, color), + ], + }, + { + name: "Sell Side Risk", + title: title("Sell Side Risk Ratio"), + bottom: [ + line({ metric: tree.realized.sellSideRiskRatio, name: "Raw", color: colors.orange, unit: Unit.ratio }), + line({ metric: tree.realized.sellSideRiskRatio7dEma, name: "7d EMA", color: colors.red, unit: Unit.ratio, defaultActive: false }), + line({ metric: tree.realized.sellSideRiskRatio30dEma, name: "30d EMA", color: colors.rose, unit: Unit.ratio, defaultActive: false }), + ], + }, + { + name: "value", + title: title("Value Created & Destroyed"), + bottom: [ + line({ metric: tree.realized.valueCreated, name: "Created", color: colors.emerald, unit: Unit.usd }), + line({ metric: tree.realized.valueDestroyed, name: "Destroyed", color: colors.red, unit: Unit.usd }), + ...valueMetrics, + ], + }, + { + name: "Coins Destroyed", + title: title("Coins Destroyed"), + bottom: [ + line({ metric: tree.activity.coinblocksDestroyed.sum, name: "Coinblocks", color, unit: Unit.coinblocks }), + line({ metric: tree.activity.coinblocksDestroyed.cumulative, name: "Cumulative", color, unit: Unit.coinblocks, defaultActive: false }), + line({ metric: tree.activity.coindaysDestroyed.sum, name: "Coindays", color, unit: Unit.coindays }), + line({ metric: tree.activity.coindaysDestroyed.cumulative, name: "Cumulative", color, unit: Unit.coindays, defaultActive: false }), + line({ metric: tree.activity.satblocksDestroyed, name: "Satblocks", color, unit: Unit.satblocks }), + line({ metric: tree.activity.satdaysDestroyed, name: "Satdays", color, unit: Unit.satdays }), ], }, ], @@ -2365,539 +2142,144 @@ function createSingleCostBasisSection(cohort, title) { } /** - * Create cost basis section for grouped cohorts (no percentiles) - * @param {readonly (CohortWithAdjusted | CohortBasic | CohortAddress)[]} list - * @param {string} title + * Generic grouped activity section builder - callers pass optional value tree + * @template {readonly UtxoCohortObject[]} T + * @param {Object} args + * @param {T} args.list + * @param {(metric: string) => string} args.title + * @param {PartialOptionsTree} [args.valueTree] - Optional value tree (defaults to basic created/destroyed) * @returns {PartialOptionsGroup} */ -function createGroupedCostBasisSection(list, title) { +function createGroupedActivitySection({ list, title, valueTree }) { return { - name: "Cost Basis", + name: "Activity", tree: [ { - name: "Average", - title: `Average Cost Basis ${title}`, - top: list.map(({ color, name, tree }) => - line({ - metric: tree.realized.realizedPrice, - name, - color, - unit: Unit.usd, - }), - ), + name: "Sell Side Risk", + title: title("Sell Side Risk Ratio"), + bottom: list.flatMap(({ color, name, tree }) => [ + line({ metric: tree.realized.sellSideRiskRatio, name, color, unit: Unit.ratio }), + ]), }, { - name: "Min", - title: `Min Cost Basis ${title}`, - top: list.map(({ color, name, tree }) => - line({ metric: tree.costBasis.min, name, color, unit: Unit.usd }), - ), + name: "value", + tree: valueTree ?? [ + { + name: "created", + title: title("Value Created"), + bottom: list.flatMap(({ color, name, tree }) => [ + line({ metric: tree.realized.valueCreated, name, color, unit: Unit.usd }), + ]), + }, + { + name: "destroyed", + title: title("Value Destroyed"), + bottom: list.flatMap(({ color, name, tree }) => [ + line({ metric: tree.realized.valueDestroyed, name, color, unit: Unit.usd }), + ]), + }, + ], }, { - name: "Max", - title: `Max Cost Basis ${title}`, - top: list.map(({ color, name, tree }) => - line({ metric: tree.costBasis.max, name, color, unit: Unit.usd }), - ), + name: "Coins Destroyed", + tree: [ + { + name: "Sum", + title: title("Coins Destroyed"), + bottom: list.flatMap(({ color, name, tree }) => [ + line({ metric: tree.activity.coinblocksDestroyed.sum, name, color, unit: Unit.coinblocks }), + line({ metric: tree.activity.coindaysDestroyed.sum, name, color, unit: Unit.coindays }), + ]), + }, + { + name: "Cumulative", + title: title("Cumulative Coins Destroyed"), + bottom: list.flatMap(({ color, name, tree }) => [ + line({ metric: tree.activity.coinblocksDestroyed.cumulative, name, color, unit: Unit.coinblocks }), + line({ metric: tree.activity.coindaysDestroyed.cumulative, name, color, unit: Unit.coindays }), + ]), + }, + ], }, ], }; } +// ============================================================================ +// Activity Section Variants (by cohort capability) +// ============================================================================ + /** * Create activity section with adjusted values (for cohorts with RealizedPattern3/4) * @param {PartialContext} ctx * @param {CohortAll | CohortFull | CohortWithAdjusted} cohort - * @param {string} title + * @param {(metric: string) => string} title * @returns {PartialOptionsGroup} */ function createSingleActivitySectionWithAdjusted(ctx, cohort, title) { - const { colors, fromBlockCountWithUnit, fromBitcoinPatternWithUnit } = ctx; - const { tree, color } = cohort; - - return { - name: "Activity", - tree: [ - { - name: "Sent", - title: `Sent ${title}`, - bottom: [ - ...fromBlockCountWithUnit( - tree.activity.sent.sats, - Unit.sats, - undefined, - color, - ), - ...fromBitcoinPatternWithUnit( - tree.activity.sent.bitcoin, - Unit.btc, - undefined, - color, - ), - ...fromBlockCountWithUnit( - tree.activity.sent.dollars, - Unit.usd, - undefined, - color, - ), - ], - }, - { - name: "Sell Side Risk", - title: `Sell Side Risk Ratio ${title}`, - bottom: [ - line({ - metric: tree.realized.sellSideRiskRatio, - name: "Raw", - color: colors.orange, - unit: Unit.ratio, - }), - line({ - metric: tree.realized.sellSideRiskRatio7dEma, - name: "7d EMA", - color: colors.red, - unit: Unit.ratio, - defaultActive: false, - }), - line({ - metric: tree.realized.sellSideRiskRatio30dEma, - name: "30d EMA", - color: colors.rose, - unit: Unit.ratio, - defaultActive: false, - }), - ], - }, - { - name: "value", - title: `Value Created & Destroyed ${title}`, - bottom: [ - line({ - metric: tree.realized.valueCreated, - name: "Created", - color: colors.emerald, - unit: Unit.usd, - }), - line({ - metric: tree.realized.adjustedValueCreated, - name: "Adjusted Created", - color: colors.lime, - unit: Unit.usd, - }), - line({ - metric: tree.realized.valueDestroyed, - name: "Destroyed", - color: colors.red, - unit: Unit.usd, - }), - line({ - metric: tree.realized.adjustedValueDestroyed, - name: "Adjusted Destroyed", - color: colors.pink, - unit: Unit.usd, - }), - ], - }, - { - name: "Coins Destroyed", - title: `Coins Destroyed ${title}`, - bottom: [ - line({ - metric: tree.activity.coinblocksDestroyed.sum, - name: "Coinblocks", - color, - unit: Unit.coinblocks, - }), - line({ - metric: tree.activity.coinblocksDestroyed.cumulative, - name: "Cumulative", - color, - unit: Unit.coinblocks, - defaultActive: false, - }), - line({ - metric: tree.activity.coindaysDestroyed.sum, - name: "Coindays", - color, - unit: Unit.coindays, - }), - line({ - metric: tree.activity.coindaysDestroyed.cumulative, - name: "Cumulative", - color, - unit: Unit.coindays, - defaultActive: false, - }), - line({ - metric: tree.activity.satblocksDestroyed, - name: "Satblocks", - color, - unit: Unit.satblocks, - }), - line({ - metric: tree.activity.satdaysDestroyed, - name: "Satdays", - color, - unit: Unit.satdays, - }), - ], - }, + const { colors } = ctx; + const { tree } = cohort; + return createActivitySection({ + ctx, + cohort, + title, + valueMetrics: [ + line({ metric: tree.realized.adjustedValueCreated, name: "Adjusted Created", color: colors.lime, unit: Unit.usd }), + line({ metric: tree.realized.adjustedValueDestroyed, name: "Adjusted Destroyed", color: colors.pink, unit: Unit.usd }), ], - }; -} - -/** - * Create activity section without adjusted values (for cohorts with RealizedPattern/2) - * @param {PartialContext} ctx - * @param {CohortWithPercentiles | CohortBasic | CohortAddress} cohort - * @param {string} title - * @returns {PartialOptionsGroup} - */ -function createSingleActivitySectionBasic(ctx, cohort, title) { - const { colors, fromBlockCountWithUnit, fromBitcoinPatternWithUnit } = ctx; - const { tree, color } = cohort; - - return { - name: "Activity", - tree: [ - { - name: "Sent", - title: `Sent ${title}`, - bottom: [ - ...fromBlockCountWithUnit( - tree.activity.sent.sats, - Unit.sats, - undefined, - color, - ), - ...fromBitcoinPatternWithUnit( - tree.activity.sent.bitcoin, - Unit.btc, - undefined, - color, - ), - ...fromBlockCountWithUnit( - tree.activity.sent.dollars, - Unit.usd, - undefined, - color, - ), - ], - }, - { - name: "Sell Side Risk", - title: `Sell Side Risk Ratio ${title}`, - bottom: [ - line({ - metric: tree.realized.sellSideRiskRatio, - name: "Raw", - color: colors.orange, - unit: Unit.ratio, - }), - line({ - metric: tree.realized.sellSideRiskRatio7dEma, - name: "7d EMA", - color: colors.red, - unit: Unit.ratio, - defaultActive: false, - }), - line({ - metric: tree.realized.sellSideRiskRatio30dEma, - name: "30d EMA", - color: colors.rose, - unit: Unit.ratio, - defaultActive: false, - }), - ], - }, - { - name: "value", - title: `Value Created & Destroyed ${title}`, - bottom: [ - line({ - metric: tree.realized.valueCreated, - name: "Created", - color: colors.emerald, - unit: Unit.usd, - }), - line({ - metric: tree.realized.valueDestroyed, - name: "Destroyed", - color: colors.red, - unit: Unit.usd, - }), - ], - }, - { - name: "Coins Destroyed", - title: `Coins Destroyed ${title}`, - bottom: [ - line({ - metric: tree.activity.coinblocksDestroyed.sum, - name: "Coinblocks", - color, - unit: Unit.coinblocks, - }), - line({ - metric: tree.activity.coinblocksDestroyed.cumulative, - name: "Cumulative", - color, - unit: Unit.coinblocks, - defaultActive: false, - }), - line({ - metric: tree.activity.coindaysDestroyed.sum, - name: "Coindays", - color, - unit: Unit.coindays, - }), - line({ - metric: tree.activity.coindaysDestroyed.cumulative, - name: "Cumulative", - color, - unit: Unit.coindays, - defaultActive: false, - }), - line({ - metric: tree.activity.satblocksDestroyed, - name: "Satblocks", - color, - unit: Unit.satblocks, - }), - line({ - metric: tree.activity.satdaysDestroyed, - name: "Satdays", - color, - unit: Unit.satdays, - }), - ], - }, - ], - }; + }); } /** * Create activity section for grouped cohorts with adjusted values (for cohorts with RealizedPattern3/4) * @param {readonly (CohortFull | CohortWithAdjusted)[]} list - * @param {string} title + * @param {(metric: string) => string} title * @returns {PartialOptionsGroup} */ function createGroupedActivitySectionWithAdjusted(list, title) { - return { - name: "Activity", - tree: [ + return createGroupedActivitySection({ + list, + title, + valueTree: [ { - name: "Sell Side Risk", - title: `Sell Side Risk Ratio ${title}`, - bottom: list.flatMap(({ color, name, tree }) => [ - line({ - metric: tree.realized.sellSideRiskRatio, - name, - color, - unit: Unit.ratio, - }), - ]), - }, - { - name: "value", + name: "created", tree: [ { - name: "created", - tree: [ - { - name: "Normal", - title: `Value Created ${title}`, - bottom: list.flatMap(({ color, name, tree }) => [ - line({ - metric: tree.realized.valueCreated, - name, - color, - unit: Unit.usd, - }), - ]), - }, - { - name: "Adjusted", - title: `Adjusted Value Created ${title}`, - bottom: list.flatMap(({ color, name, tree }) => [ - line({ - metric: tree.realized.adjustedValueCreated, - name, - color, - unit: Unit.usd, - }), - ]), - }, - ], + name: "Normal", + title: title("Value Created"), + bottom: list.flatMap(({ color, name, tree }) => [ + line({ metric: tree.realized.valueCreated, name, color, unit: Unit.usd }), + ]), }, { - name: "destroyed", - tree: [ - { - name: "Normal", - title: `Value Destroyed ${title}`, - bottom: list.flatMap(({ color, name, tree }) => [ - line({ - metric: tree.realized.valueDestroyed, - name, - color, - unit: Unit.usd, - }), - ]), - }, - { - name: "Adjusted", - title: `Adjusted Value Destroyed ${title}`, - bottom: list.flatMap(({ color, name, tree }) => [ - line({ - metric: tree.realized.adjustedValueDestroyed, - name, - color, - unit: Unit.usd, - }), - ]), - }, - ], + name: "Adjusted", + title: title("Adjusted Value Created"), + bottom: list.flatMap(({ color, name, tree }) => [ + line({ metric: tree.realized.adjustedValueCreated, name, color, unit: Unit.usd }), + ]), }, ], }, { - name: "Coins Destroyed", + name: "destroyed", tree: [ { - name: "Sum", - title: `Coins Destroyed ${title}`, + name: "Normal", + title: title("Value Destroyed"), bottom: list.flatMap(({ color, name, tree }) => [ - line({ - metric: tree.activity.coinblocksDestroyed.sum, - name, - color, - unit: Unit.coinblocks, - }), - line({ - metric: tree.activity.coindaysDestroyed.sum, - name, - color, - unit: Unit.coindays, - }), + line({ metric: tree.realized.valueDestroyed, name, color, unit: Unit.usd }), ]), }, { - name: "Cumulative", - title: `Cumulative Coins Destroyed ${title}`, + name: "Adjusted", + title: title("Adjusted Value Destroyed"), bottom: list.flatMap(({ color, name, tree }) => [ - line({ - metric: tree.activity.coinblocksDestroyed.cumulative, - name, - color, - unit: Unit.coinblocks, - }), - line({ - metric: tree.activity.coindaysDestroyed.cumulative, - name, - color, - unit: Unit.coindays, - }), + line({ metric: tree.realized.adjustedValueDestroyed, name, color, unit: Unit.usd }), ]), }, ], }, ], - }; + }); } -/** - * Create activity section for grouped cohorts without adjusted values (for cohorts with RealizedPattern/2) - * @param {readonly (CohortWithPercentiles | CohortBasic | CohortAddress)[]} list - * @param {string} title - * @returns {PartialOptionsGroup} - */ -function createGroupedActivitySectionBasic(list, title) { - return { - name: "Activity", - tree: [ - { - name: "Sell Side Risk", - title: `Sell Side Risk Ratio ${title}`, - bottom: list.flatMap(({ color, name, tree }) => [ - line({ - metric: tree.realized.sellSideRiskRatio, - name, - color, - unit: Unit.ratio, - }), - ]), - }, - { - name: "value", - tree: [ - { - name: "created", - title: `Value Created ${title}`, - bottom: list.flatMap(({ color, name, tree }) => [ - line({ - metric: tree.realized.valueCreated, - name, - color, - unit: Unit.usd, - }), - ]), - }, - { - name: "destroyed", - title: `Value Destroyed ${title}`, - bottom: list.flatMap(({ color, name, tree }) => [ - line({ - metric: tree.realized.valueDestroyed, - name, - color, - unit: Unit.usd, - }), - ]), - }, - ], - }, - { - name: "Coins Destroyed", - tree: [ - { - name: "Sum", - title: `Coins Destroyed ${title}`, - bottom: list.flatMap(({ color, name, tree }) => [ - line({ - metric: tree.activity.coinblocksDestroyed.sum, - name, - color, - unit: Unit.coinblocks, - }), - line({ - metric: tree.activity.coindaysDestroyed.sum, - name, - color, - unit: Unit.coindays, - }), - ]), - }, - { - name: "Cumulative", - title: `Cumulative Coins Destroyed ${title}`, - bottom: list.flatMap(({ color, name, tree }) => [ - line({ - metric: tree.activity.coinblocksDestroyed.cumulative, - name, - color, - unit: Unit.coinblocks, - }), - line({ - metric: tree.activity.coindaysDestroyed.cumulative, - name, - color, - unit: Unit.coindays, - }), - ]), - }, - ], - }, - ], - }; -} diff --git a/website/scripts/options/market/averages.js b/website/scripts/options/market/averages.js index 8ed834551..12cc73a13 100644 --- a/website/scripts/options/market/averages.js +++ b/website/scripts/options/market/averages.js @@ -2,7 +2,7 @@ import { Unit } from "../../utils/units.js"; import { line } from "../series.js"; -import { createRatioChart, createZScoresFolder } from "../shared.js"; +import { createRatioChart, createZScoresFolder, formatCohortTitle } from "../shared.js"; import { periodIdToName } from "./utils.js"; /** @@ -89,7 +89,7 @@ export function createPriceWithRatioOptions( title, top: [line({ metric: priceMetric, name: legend, color, unit: Unit.usd })], }, - createRatioChart(ctx, { title, price: priceMetric, ratio, color }), + createRatioChart(ctx, { title: formatCohortTitle(title), price: priceMetric, ratio, color }), createZScoresFolder(ctx, { title, legend, diff --git a/website/scripts/options/market/investing.js b/website/scripts/options/market/investing.js index ac19144fa..f44df92e6 100644 --- a/website/scripts/options/market/investing.js +++ b/website/scripts/options/market/investing.js @@ -47,83 +47,104 @@ export function createInvestingSection(ctx, { dca, lookback, returns }) { const { colors } = ctx; const dcaClasses = buildDcaClasses(colors, dca); + /** + * @param {string} id + * @param {ShortPeriodKey} key + */ + const createPeriodTree = (id, key) => { + const name = periodIdToName(id, true); + return { + name, + tree: [ + { + name: "Cost basis", + title: `${name} Cost Basis`, + top: [ + line({ metric: dca.periodAveragePrice[key], name: "DCA", color: colors.green, unit: Unit.usd }), + line({ metric: lookback[key], name: "Lump sum", color: colors.orange, unit: Unit.usd }), + ], + }, + { + name: "Returns", + title: `${name} Returns`, + bottom: [ + baseline({ metric: dca.periodReturns[key], name: "DCA", unit: Unit.percentage }), + baseline({ metric: returns.priceReturns[key], name: "Lump sum", color: [colors.cyan, colors.orange], unit: Unit.percentage }), + priceLine({ ctx, unit: Unit.percentage }), + ], + }, + { + name: "Stack", + title: `${name} Stack`, + bottom: [ + ...satsBtcUsd(dca.periodStack[key], "DCA", colors.green), + ...satsBtcUsd(dca.periodLumpSumStack[key], "Lump sum", colors.orange), + ], + }, + ], + }; + }; + + /** + * @param {string} id + * @param {LongPeriodKey} key + */ + const createPeriodTreeWithCagr = (id, key) => { + const name = periodIdToName(id, true); + return { + name, + tree: [ + { + name: "Cost basis", + title: `${name} Cost Basis`, + top: [ + line({ metric: dca.periodAveragePrice[key], name: "DCA", color: colors.green, unit: Unit.usd }), + line({ metric: lookback[key], name: "Lump sum", color: colors.orange, unit: Unit.usd }), + ], + }, + { + name: "Returns", + title: `${name} Returns`, + bottom: [ + baseline({ metric: dca.periodReturns[key], name: "DCA", unit: Unit.percentage }), + baseline({ metric: returns.priceReturns[key], name: "Lump sum", color: [colors.cyan, colors.orange], unit: Unit.percentage }), + line({ metric: dca.periodCagr[key], name: "DCA CAGR", color: colors.purple, unit: Unit.percentage, defaultActive: false }), + line({ metric: returns.cagr[key], name: "Lump sum CAGR", color: colors.indigo, unit: Unit.percentage, defaultActive: false }), + priceLine({ ctx, unit: Unit.percentage }), + ], + }, + { + name: "Stack", + title: `${name} Stack`, + bottom: [ + ...satsBtcUsd(dca.periodStack[key], "DCA", colors.green), + ...satsBtcUsd(dca.periodLumpSumStack[key], "Lump sum", colors.orange), + ], + }, + ], + }; + }; + return { name: "Investing", tree: [ // DCA vs Lump sum { name: "DCA vs Lump sum", - tree: /** @type {const} */ ([ - ["1w", "_1w"], - ["1m", "_1m"], - ["3m", "_3m"], - ["6m", "_6m"], - ["1y", "_1y"], - ["2y", "_2y"], - ["3y", "_3y"], - ["4y", "_4y"], - ["5y", "_5y"], - ["6y", "_6y"], - ["8y", "_8y"], - ["10y", "_10y"], - ]).map(([id, key]) => { - const name = periodIdToName(id, true); - const priceAgo = lookback[key]; - const priceReturns = returns.priceReturns[key]; - const dcaCostBasis = dca.periodAveragePrice[key]; - const dcaReturns = dca.periodReturns[key]; - const dcaStack = dca.periodStack[key]; - const lumpSumStack = dca.periodLumpSumStack[key]; - return { - name, - tree: [ - { - name: "Cost basis", - title: `${name} Cost Basis`, - top: [ - line({ - metric: dcaCostBasis, - name: "DCA", - color: colors.green, - unit: Unit.usd, - }), - line({ - metric: priceAgo, - name: "Lump sum", - color: colors.orange, - unit: Unit.usd, - }), - ], - }, - { - name: "Returns", - title: `${name} Returns`, - bottom: [ - baseline({ - metric: dcaReturns, - name: "DCA", - unit: Unit.percentage, - }), - baseline({ - metric: priceReturns, - name: "Lump sum", - color: [colors.cyan, colors.orange], - unit: Unit.percentage, - }), - priceLine({ ctx, unit: Unit.percentage }), - ], - }, - { - name: "Stack", - title: `${name} Stack`, - bottom: [ - ...satsBtcUsd(dcaStack, "DCA", colors.green), - ...satsBtcUsd(lumpSumStack, "Lump sum", colors.orange), - ], - }, - ], - }; - }), + tree: [ + createPeriodTree("1w", "_1w"), + createPeriodTree("1m", "_1m"), + createPeriodTree("3m", "_3m"), + createPeriodTree("6m", "_6m"), + createPeriodTree("1y", "_1y"), + createPeriodTreeWithCagr("2y", "_2y"), + createPeriodTreeWithCagr("3y", "_3y"), + createPeriodTreeWithCagr("4y", "_4y"), + createPeriodTreeWithCagr("5y", "_5y"), + createPeriodTreeWithCagr("6y", "_6y"), + createPeriodTreeWithCagr("8y", "_8y"), + createPeriodTreeWithCagr("10y", "_10y"), + ], }, // DCA classes diff --git a/website/scripts/options/partial.js b/website/scripts/options/partial.js index 8c6e66273..d5ab329b1 100644 --- a/website/scripts/options/partial.js +++ b/website/scripts/options/partial.js @@ -6,8 +6,7 @@ import { createCohortFolderAll, createCohortFolderFull, createCohortFolderWithAdjusted, - createCohortFolderWithPercentiles, - createCohortFolderLongTerm, + createCohortFolderWithNupl, createCohortFolderAgeRange, createCohortFolderBasicWithMarketCap, createCohortFolderBasicWithoutMarketCap, @@ -99,19 +98,19 @@ export function createPartialOptions({ brk }) { // All UTXOs - CohortAll (adjustedSopr + percentiles but no RelToMarketCap) createCohortFolderAll(ctx, cohortAll), - // Terms (STH/LTH) - Short is Full, Long is LongTerm + // Terms (STH/LTH) - Short is Full, Long has nupl { name: "Terms", tree: [ - // Compare folder uses WithPercentiles (common capabilities) - createCohortFolderWithPercentiles(ctx, { + // Compare folder - both have nupl + percentiles + createCohortFolderWithNupl(ctx, { name: "Compare", title: "Term", list: [termShort, termLong], }), // Individual cohorts with their specific capabilities createCohortFolderFull(ctx, termShort), - createCohortFolderLongTerm(ctx, termLong), + createCohortFolderWithNupl(ctx, termLong), ], }, diff --git a/website/scripts/options/series.js b/website/scripts/options/series.js index e9b031fb4..b12b95979 100644 --- a/website/scripts/options/series.js +++ b/website/scripts/options/series.js @@ -2,6 +2,34 @@ import { Unit } from "../utils/units.js"; +// ============================================================================ +// Shared percentile helper +// ============================================================================ + +/** + * Create percentile series (max/min/median/pct75/pct25/pct90/pct10) from any stats pattern + * Works with FullnessPattern, FeeRatePattern, AnyStatsPattern, DollarsPattern, etc. + * @param {Colors} colors + * @param {FullnessPattern | FeeRatePattern | AnyStatsPattern | DollarsPattern} pattern + * @param {Unit} unit + * @param {string} title + * @param {{ type?: "Dots" }} [options] + * @returns {AnyFetchedSeriesBlueprint[]} + */ +function percentileSeries(colors, pattern, unit, title, { type } = {}) { + const { stat } = colors; + const base = { unit, defaultActive: false }; + return [ + { type, metric: pattern.max, title: `${title} max`.trim(), color: stat.max, ...base }, + { type, metric: pattern.min, title: `${title} min`.trim(), color: stat.min, ...base }, + { type, metric: pattern.median, title: `${title} median`.trim(), color: stat.median, ...base }, + { type, metric: pattern.pct75, title: `${title} pct75`.trim(), color: stat.pct75, ...base }, + { type, metric: pattern.pct25, title: `${title} pct25`.trim(), color: stat.pct25, ...base }, + { type, metric: pattern.pct90, title: `${title} pct90`.trim(), color: stat.pct90, ...base }, + { type, metric: pattern.pct10, title: `${title} pct10`.trim(), color: stat.pct10, ...base }, + ]; +} + /** * Create a Line series * @param {Object} args @@ -180,158 +208,6 @@ export function histogram({ }; } -/** - * Create series from a BlockCountPattern ({ base, sum, cumulative }) - * @param {Colors} colors - * @param {BlockCountPattern} pattern - * @param {string} title - * @param {Color} [color] - * @returns {AnyFetchedSeriesBlueprint[]} - */ -export function fromBlockCount(colors, pattern, title, color) { - return [ - { metric: pattern.sum, title, color: color ?? colors.default }, - { - metric: pattern.cumulative, - title: `${title} cumulative`, - color: colors.stat.cumulative, - defaultActive: false, - }, - ]; -} - -/** - * Create series from a FullnessPattern ({ base, sum, cumulative, average, min, max, percentiles }) - * @param {Colors} colors - * @param {FullnessPattern} pattern - * @param {string} title - * @param {Color} [color] - * @returns {AnyFetchedSeriesBlueprint[]} - */ -export function fromBitcoin(colors, pattern, title, color) { - const { stat } = colors; - return [ - { metric: pattern.base, title, color: color ?? colors.default }, - { - metric: pattern.average, - title: `${title} avg`, - color: stat.avg, - defaultActive: false, - }, - { - metric: pattern.max, - title: `${title} max`, - color: stat.max, - defaultActive: false, - }, - { - metric: pattern.min, - title: `${title} min`, - color: stat.min, - defaultActive: false, - }, - { - metric: pattern.median, - title: `${title} median`, - color: stat.median, - defaultActive: false, - }, - { - metric: pattern.pct75, - title: `${title} pct75`, - color: stat.pct75, - defaultActive: false, - }, - { - metric: pattern.pct25, - title: `${title} pct25`, - color: stat.pct25, - defaultActive: false, - }, - { - metric: pattern.pct90, - title: `${title} pct90`, - color: stat.pct90, - defaultActive: false, - }, - { - metric: pattern.pct10, - title: `${title} pct10`, - color: stat.pct10, - defaultActive: false, - }, - ]; -} - -/** - * Create series from a SizePattern ({ sum, cumulative, average, min, max, percentiles }) - * @param {Colors} colors - * @param {AnyStatsPattern} pattern - * @param {string} title - * @param {Color} [color] - * @returns {AnyFetchedSeriesBlueprint[]} - */ -export function fromBlockSize(colors, pattern, title, color) { - const { stat } = colors; - return [ - { metric: pattern.sum, title, color: color ?? colors.default }, - { - metric: pattern.average, - title: `${title} avg`, - color: stat.avg, - defaultActive: false, - }, - { - metric: pattern.cumulative, - title: `${title} cumulative`, - color: stat.cumulative, - defaultActive: false, - }, - { - metric: pattern.max, - title: `${title} max`, - color: stat.max, - defaultActive: false, - }, - { - metric: pattern.min, - title: `${title} min`, - color: stat.min, - defaultActive: false, - }, - { - metric: pattern.median, - title: `${title} median`, - color: stat.median, - defaultActive: false, - }, - { - metric: pattern.pct75, - title: `${title} pct75`, - color: stat.pct75, - defaultActive: false, - }, - { - metric: pattern.pct25, - title: `${title} pct25`, - color: stat.pct25, - defaultActive: false, - }, - { - metric: pattern.pct90, - title: `${title} pct90`, - color: stat.pct90, - defaultActive: false, - }, - { - metric: pattern.pct10, - title: `${title} pct10`, - color: stat.pct10, - defaultActive: false, - }, - ]; -} - /** * Create series from a SizePattern ({ average, sum, cumulative, min, max, percentiles }) * @param {Colors} colors @@ -344,69 +220,9 @@ export function fromSizePattern(colors, pattern, unit, title = "") { const { stat } = colors; return [ { metric: pattern.average, title: `${title} avg`.trim(), unit }, - { - metric: pattern.sum, - title: `${title} sum`.trim(), - color: stat.sum, - unit, - defaultActive: false, - }, - { - metric: pattern.cumulative, - title: `${title} cumulative`.trim(), - color: stat.cumulative, - unit, - defaultActive: false, - }, - { - metric: pattern.max, - title: `${title} max`.trim(), - color: stat.max, - unit, - defaultActive: false, - }, - { - metric: pattern.min, - title: `${title} min`.trim(), - color: stat.min, - unit, - defaultActive: false, - }, - { - metric: pattern.median, - title: `${title} median`.trim(), - color: stat.median, - unit, - defaultActive: false, - }, - { - metric: pattern.pct75, - title: `${title} pct75`.trim(), - color: stat.pct75, - unit, - defaultActive: false, - }, - { - metric: pattern.pct25, - title: `${title} pct25`.trim(), - color: stat.pct25, - unit, - defaultActive: false, - }, - { - metric: pattern.pct90, - title: `${title} pct90`.trim(), - color: stat.pct90, - unit, - defaultActive: false, - }, - { - metric: pattern.pct10, - title: `${title} pct10`.trim(), - color: stat.pct10, - unit, - defaultActive: false, - }, + { metric: pattern.sum, title: `${title} sum`.trim(), color: stat.sum, unit, defaultActive: false }, + { metric: pattern.cumulative, title: `${title} cumulative`.trim(), color: stat.cumulative, unit, defaultActive: false }, + ...percentileSeries(colors, pattern, unit, title), ]; } @@ -422,61 +238,8 @@ export function fromFullnessPattern(colors, pattern, unit, title = "") { const { stat } = colors; return [ { metric: pattern.base, title: title || "base", unit }, - { - metric: pattern.average, - title: `${title} avg`.trim(), - color: stat.avg, - unit, - }, - { - metric: pattern.max, - title: `${title} max`.trim(), - color: stat.max, - unit, - defaultActive: false, - }, - { - metric: pattern.min, - title: `${title} min`.trim(), - color: stat.min, - unit, - defaultActive: false, - }, - { - metric: pattern.median, - title: `${title} median`.trim(), - color: stat.median, - unit, - defaultActive: false, - }, - { - metric: pattern.pct75, - title: `${title} pct75`.trim(), - color: stat.pct75, - unit, - defaultActive: false, - }, - { - metric: pattern.pct25, - title: `${title} pct25`.trim(), - color: stat.pct25, - unit, - defaultActive: false, - }, - { - metric: pattern.pct90, - title: `${title} pct90`.trim(), - color: stat.pct90, - unit, - defaultActive: false, - }, - { - metric: pattern.pct10, - title: `${title} pct10`.trim(), - color: stat.pct10, - unit, - defaultActive: false, - }, + { metric: pattern.average, title: `${title} avg`.trim(), color: stat.avg, unit }, + ...percentileSeries(colors, pattern, unit, title), ]; } @@ -492,75 +255,10 @@ export function fromDollarsPattern(colors, pattern, unit, title = "") { const { stat } = colors; return [ { metric: pattern.base, title: title || "base", unit }, - { - metric: pattern.sum, - title: `${title} sum`.trim(), - color: stat.sum, - unit, - }, - { - metric: pattern.cumulative, - title: `${title} cumulative`.trim(), - color: stat.cumulative, - unit, - defaultActive: false, - }, - { - metric: pattern.average, - title: `${title} avg`.trim(), - color: stat.avg, - unit, - defaultActive: false, - }, - { - metric: pattern.max, - title: `${title} max`.trim(), - color: stat.max, - unit, - defaultActive: false, - }, - { - metric: pattern.min, - title: `${title} min`.trim(), - color: stat.min, - unit, - defaultActive: false, - }, - { - metric: pattern.median, - title: `${title} median`.trim(), - color: stat.median, - unit, - defaultActive: false, - }, - { - metric: pattern.pct75, - title: `${title} pct75`.trim(), - color: stat.pct75, - unit, - defaultActive: false, - }, - { - metric: pattern.pct25, - title: `${title} pct25`.trim(), - color: stat.pct25, - unit, - defaultActive: false, - }, - { - metric: pattern.pct90, - title: `${title} pct90`.trim(), - color: stat.pct90, - unit, - defaultActive: false, - }, - { - metric: pattern.pct10, - title: `${title} pct10`.trim(), - color: stat.pct10, - unit, - defaultActive: false, - }, + { metric: pattern.sum, title: `${title} sum`.trim(), color: stat.sum, unit }, + { metric: pattern.cumulative, title: `${title} cumulative`.trim(), color: stat.cumulative, unit, defaultActive: false }, + { metric: pattern.average, title: `${title} avg`.trim(), color: stat.avg, unit, defaultActive: false }, + ...percentileSeries(colors, pattern, unit, title), ]; } @@ -573,85 +271,41 @@ export function fromDollarsPattern(colors, pattern, unit, title = "") { * @returns {AnyFetchedSeriesBlueprint[]} */ export function fromFeeRatePattern(colors, pattern, unit, title = "") { - const { stat } = colors; return [ - { - type: "Dots", - metric: pattern.average, - title: `${title} avg`.trim(), - unit, - }, - { - type: "Dots", - metric: pattern.max, - title: `${title} max`.trim(), - color: stat.max, - unit, - defaultActive: false, - }, - { - type: "Dots", - metric: pattern.min, - title: `${title} min`.trim(), - color: stat.min, - unit, - defaultActive: false, - }, - { - type: "Dots", - metric: pattern.median, - title: `${title} median`.trim(), - color: stat.median, - unit, - defaultActive: false, - }, - { - type: "Dots", - metric: pattern.pct75, - title: `${title} pct75`.trim(), - color: stat.pct75, - unit, - defaultActive: false, - }, - { - type: "Dots", - metric: pattern.pct25, - title: `${title} pct25`.trim(), - color: stat.pct25, - unit, - defaultActive: false, - }, - { - type: "Dots", - metric: pattern.pct90, - title: `${title} pct90`.trim(), - color: stat.pct90, - unit, - defaultActive: false, - }, - { - type: "Dots", - metric: pattern.pct10, - title: `${title} pct10`.trim(), - color: stat.pct10, - unit, - defaultActive: false, - }, + { type: "Dots", metric: pattern.average, title: `${title} avg`.trim(), unit }, + ...percentileSeries(colors, pattern, unit, title, { type: "Dots" }), ]; } /** - * Create series from a CoinbasePattern ({ sats, bitcoin, dollars } each as FullnessPattern) + * Create series from a pattern with sum and cumulative (fullness stats + sum + cumulative) + * @param {Colors} colors + * @param {FullnessPatternWithSumCumulative} pattern + * @param {Unit} unit + * @param {string} [title] + * @returns {AnyFetchedSeriesBlueprint[]} + */ +export function fromFullnessPatternWithSumCumulative(colors, pattern, unit, title = "") { + const { stat } = colors; + return [ + ...fromFullnessPattern(colors, pattern, unit, title), + { metric: pattern.sum, title: `${title} sum`.trim(), color: stat.sum, unit }, + { metric: pattern.cumulative, title: `${title} cumulative`.trim(), color: stat.cumulative, unit, defaultActive: false }, + ]; +} + +/** + * Create series from a CoinbasePattern ({ sats, bitcoin, dollars } each with stats + sum + cumulative) * @param {Colors} colors * @param {CoinbasePattern} pattern * @param {string} [title] * @returns {AnyFetchedSeriesBlueprint[]} */ -export function fromCoinbasePattern(colors, pattern, title) { +export function fromCoinbasePattern(colors, pattern, title = "") { return [ - ...fromFullnessPattern(colors, pattern.bitcoin, Unit.btc, title), - ...fromFullnessPattern(colors, pattern.sats, Unit.sats, title), - ...fromFullnessPattern(colors, pattern.dollars, Unit.usd, title), + ...fromFullnessPatternWithSumCumulative(colors, pattern.bitcoin, Unit.btc, title), + ...fromFullnessPatternWithSumCumulative(colors, pattern.sats, Unit.sats, title), + ...fromFullnessPatternWithSumCumulative(colors, pattern.dollars, Unit.usd, title), ]; } @@ -797,62 +451,8 @@ export function fromIntervalPattern(colors, pattern, unit, title = "", color) { const { stat } = colors; return [ { metric: pattern.base, title: title ?? "base", color, unit }, - { - metric: pattern.average, - title: `${title} avg`.trim(), - color: stat.avg, - unit, - defaultActive: false, - }, - { - metric: pattern.max, - title: `${title} max`.trim(), - color: stat.max, - unit, - defaultActive: false, - }, - { - metric: pattern.min, - title: `${title} min`.trim(), - color: stat.min, - unit, - defaultActive: false, - }, - { - metric: pattern.median, - title: `${title} median`.trim(), - color: stat.median, - unit, - defaultActive: false, - }, - { - metric: pattern.pct75, - title: `${title} pct75`.trim(), - color: stat.pct75, - unit, - defaultActive: false, - }, - { - metric: pattern.pct25, - title: `${title} pct25`.trim(), - color: stat.pct25, - unit, - defaultActive: false, - }, - { - metric: pattern.pct90, - title: `${title} pct90`.trim(), - color: stat.pct90, - unit, - defaultActive: false, - }, - { - metric: pattern.pct10, - title: `${title} pct10`.trim(), - color: stat.pct10, - unit, - defaultActive: false, - }, + { metric: pattern.average, title: `${title} avg`.trim(), color: stat.avg, unit, defaultActive: false }, + ...percentileSeries(colors, pattern, unit, title), ]; } diff --git a/website/scripts/options/shared.js b/website/scripts/options/shared.js index 04fea79ee..2981ac81d 100644 --- a/website/scripts/options/shared.js +++ b/website/scripts/options/shared.js @@ -4,6 +4,14 @@ import { Unit } from "../utils/units.js"; import { line, baseline } from "./series.js"; import { priceLine, priceLines } from "./constants.js"; +/** + * Create a title formatter for chart titles + * @param {string} [cohortTitle] + * @returns {(metric: string) => string} + */ +export const formatCohortTitle = (cohortTitle) => + (metric) => cohortTitle ? `${metric}: ${cohortTitle}` : metric; + /** * Create sats/btc/usd line series from a pattern with .sats/.bitcoin/.dollars * @param {{ sats: AnyMetricPattern, bitcoin: AnyMetricPattern, dollars: AnyMetricPattern }} pattern @@ -144,7 +152,7 @@ export function ratioSmas(colors, ratio) { * Create ratio chart from ActivePriceRatioPattern * @param {PartialContext} ctx * @param {Object} args - * @param {string} args.title + * @param {(metric: string) => string} args.title * @param {AnyMetricPattern} args.price - The price metric to show in top pane * @param {ActivePriceRatioPattern} args.ratio - The ratio pattern * @param {Color} args.color @@ -156,7 +164,7 @@ export function createRatioChart(ctx, { title, price, ratio, color, name }) { return { name: name ?? "ratio", - title: name ? (title ? `${name} - ${title}` : name) : `${title} Ratio`, + title: title(name ?? "Ratio"), top: [ line({ metric: price, name: "price", color, unit: Unit.usd }), ...percentileUsdMap(colors, ratio).map(({ name, prop, color }) => diff --git a/website/scripts/options/types.js b/website/scripts/options/types.js index 926d9aaef..d4730a822 100644 --- a/website/scripts/options/types.js +++ b/website/scripts/options/types.js @@ -289,6 +289,11 @@ * @property {readonly AddressCohortObject[]} list * * @typedef {UtxoCohortGroupObject | AddressCohortGroupObject} CohortGroupObject + * + * @typedef {Object} CohortGroupAddress + * @property {string} name + * @property {string} title + * @property {readonly CohortAddress[]} list */ // Re-export for type consumers diff --git a/website/scripts/options/unused.js b/website/scripts/options/unused.js index 138ec8279..a0d581ee4 100644 --- a/website/scripts/options/unused.js +++ b/website/scripts/options/unused.js @@ -1,8 +1,19 @@ import { localhost } from "../utils/env.js"; +import { serdeChartableIndex } from "../utils/serde.js"; /** @type {Map | null} */ export const unused = localhost ? new Map() : null; +/** + * Check if a metric pattern has at least one chartable index + * @param {AnyMetricPattern} node + * @returns {boolean} + */ +function hasChartableIndex(node) { + const indexes = node.indexes(); + return indexes.some((idx) => serdeChartableIndex.serialize(idx) !== null); +} + /** * @param {TreeNode | null | undefined} node * @param {Map} map @@ -10,7 +21,9 @@ export const unused = localhost ? new Map() : null; */ function walk(node, map, path) { if (node && "by" in node) { - map.set(/** @type {AnyMetricPattern} */ (node), path); + const metricNode = /** @type {AnyMetricPattern} */ (node); + if (!hasChartableIndex(metricNode)) return; + map.set(metricNode, path); } else if (node && typeof node === "object") { for (const [key, value] of Object.entries(node)) { const kn = key.toLowerCase(); @@ -19,12 +32,16 @@ function walk(node, map, path) { kn === "time" || kn === "height" || kn === "constants" || + kn === "blockhash" || kn === "oracle" || kn === "split" || + kn === "ohlc" || kn === "outpoint" || kn === "positions" || kn === "outputtype" || kn === "heighttopool" || + kn === "txid" || + kn.endsWith("state") || kn.endsWith("index") || kn.endsWith("indexes") || kn.endsWith("bytes") || diff --git a/website/scripts/types.js b/website/scripts/types.js index 69f6d2876..62c367e75 100644 --- a/website/scripts/types.js +++ b/website/scripts/types.js @@ -14,7 +14,7 @@ * * @import { WebSockets } from "./utils/ws.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, PartialContext, PatternAll, PatternFull, PatternWithAdjusted, PatternWithPercentiles, PatternBasic, PatternBasicWithMarketCap, PatternBasicWithoutMarketCap, CohortAll, CohortFull, CohortWithAdjusted, CohortWithPercentiles, CohortBasic, CohortBasicWithMarketCap, CohortBasicWithoutMarketCap, CohortAddress, CohortLongTerm, CohortAgeRange, CohortGroupFull, CohortGroupWithAdjusted, CohortGroupWithPercentiles, CohortGroupLongTerm, CohortGroupAgeRange, CohortGroupBasic, CohortGroupBasicWithMarketCap, CohortGroupBasicWithoutMarketCap, UtxoCohortGroupObject, AddressCohortGroupObject, FetchedDotsSeriesBlueprint, FetchedCandlestickSeriesBlueprint } 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, PartialContext, PatternAll, PatternFull, PatternWithAdjusted, PatternWithPercentiles, PatternBasic, PatternBasicWithMarketCap, PatternBasicWithoutMarketCap, CohortAll, CohortFull, CohortWithAdjusted, CohortWithPercentiles, CohortBasic, CohortBasicWithMarketCap, CohortBasicWithoutMarketCap, CohortAddress, CohortLongTerm, CohortAgeRange, CohortGroupFull, CohortGroupWithAdjusted, CohortGroupWithPercentiles, CohortGroupLongTerm, CohortGroupAgeRange, CohortGroupBasic, CohortGroupBasicWithMarketCap, CohortGroupBasicWithoutMarketCap, CohortGroupAddress, UtxoCohortGroupObject, AddressCohortGroupObject, FetchedDotsSeriesBlueprint, FetchedCandlestickSeriesBlueprint } from "./options/partial.js" * * * @import { UnitObject as Unit } from "./utils/units.js" @@ -111,6 +111,12 @@ * @typedef {Brk.MetricsTree_Market} Market * @typedef {Brk.MetricsTree_Market_MovingAverage} MarketMovingAverage * @typedef {Brk.MetricsTree_Market_Dca} MarketDca + * @typedef {Brk.PeriodCagrPattern} PeriodCagrPattern + * @typedef {Brk.BitcoinPattern | Brk.DollarsPattern} FullnessPatternWithSumCumulative + * + * DCA period keys + * @typedef {"_1w" | "_1m" | "_3m" | "_6m" | "_1y"} ShortPeriodKey + * @typedef {keyof PeriodCagrPattern} LongPeriodKey * * Pattern unions by cohort type * @typedef {AllUtxoPattern | AgeRangePattern | UtxoAmountPattern} UtxoCohortPattern @@ -123,6 +129,9 @@ * @typedef {OwnRelativePattern | FullRelativePattern | AllRelativePattern} RelativeWithOwnPnl * @typedef {GlobalRelativePattern | FullRelativePattern} RelativeWithNupl * + * Realized pattern capability types (RealizedPattern2 and RealizedPattern3 have extra metrics) + * @typedef {Brk.RealizedPattern2 | Brk.RealizedPattern3} RealizedWithExtras + * * Capability-based pattern groupings (patterns that have specific properties) * @typedef {AllUtxoPattern | AgeRangePattern | UtxoAmountPattern} PatternWithRealizedPrice * @typedef {AllUtxoPattern} PatternWithFullRealized @@ -139,6 +148,23 @@ * @typedef {{ name: string, title: string, color: Color, tree: PatternWithActivity }} CohortWithActivity * @typedef {{ name: string, title: string, color: Color, tree: PatternWithCostBasisPercentiles }} CohortWithCostBasisPercentiles * + * Cohorts with nupl + percentiles (CohortFull and CohortLongTerm both have nupl and percentiles) + * @typedef {CohortFull | CohortLongTerm} CohortWithNuplPercentiles + * @typedef {{ name: string, title: string, list: readonly CohortWithNuplPercentiles[] }} CohortGroupWithNuplPercentiles + * + * Cohorts with RealizedWithExtras (realizedCapRelToOwnMarketCap + realizedProfitToLossRatio) + * @typedef {CohortAll | CohortFull | CohortWithPercentiles} CohortWithRealizedExtras + * + * Cohorts with circulating supply relative metrics (supplyRelToCirculatingSupply etc.) + * These have GlobalRelativePattern or FullRelativePattern (same as RelativeWithMarketCap/RelativeWithNupl) + * @typedef {CohortFull | CohortLongTerm | CohortWithAdjusted | CohortBasicWithMarketCap} UtxoCohortWithCirculatingSupplyRelative + * + * Address cohorts with circulating supply relative metrics (all address amount cohorts have these) + * @typedef {AddressCohortObject} AddressCohortWithCirculatingSupplyRelative + * + * All cohorts with circulating supply relative metrics + * @typedef {UtxoCohortWithCirculatingSupplyRelative | AddressCohortWithCirculatingSupplyRelative} CohortWithCirculatingSupplyRelative + * * Generic tree node type for walking * @typedef {AnyMetricPattern | Record} TreeNode * diff --git a/website/styles/fonts.css b/website/styles/fonts.css index e7ba3d445..96e6f49b2 100644 --- a/website/styles/fonts.css +++ b/website/styles/fonts.css @@ -1,29 +1,14 @@ -@font-face { - font-family: "Geist Mono"; - src: url("/assets/fonts/GeistMono[wght]-v1_5.woff2") format("woff2"); - font-style: normal; - font-weight: 100 900; - font-display: block; -} -@font-face { - font-family: "Geist Mono"; - src: url("/assets/fonts/GeistMono-Italic[wght]-v1_5.woff2") format("woff2"); - font-style: italic; - font-weight: 100 900; - font-display: block; -} - @font-face { font-family: Lilex; - src: url("/assets/fonts/Lilex[wght].woff2") format("woff2"); + src: url("/assets/fonts/Lilex[wght]-v2_620.woff2") format("woff2"); font-style: normal; - font-weight: 100 900; + font-weight: 100 700; font-display: block; } @font-face { font-family: Lilex; - src: url("/assets/fonts/Lilex-Italic[wght].woff2") format("woff2"); + src: url("/assets/fonts/Lilex-Italic[wght]-v2_620.woff2") format("woff2"); font-style: italic; - font-weight: 100 900; + font-weight: 100 700; font-display: block; } diff --git a/website/styles/reset.css b/website/styles/reset.css index 79ac1b5b8..dca213135 100644 --- a/website/styles/reset.css +++ b/website/styles/reset.css @@ -15,9 +15,8 @@ html, -webkit-text-size-adjust: 100%; tab-size: 4; font-family: - "Geist Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, + "Lilex", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; - font-feature-settings: "ss03"; -webkit-tap-highlight-color: transparent; }