From 2df9ee4a1da80839032e93e8e3f55c866bda54a1 Mon Sep 17 00:00:00 2001 From: nym21 Date: Thu, 19 Mar 2026 14:13:37 +0100 Subject: [PATCH] global: snapshot --- crates/brk_client/src/lib.rs | 86 +- .../src/distribution/metrics/realized/core.rs | 29 +- .../src/distribution/metrics/realized/full.rs | 12 +- modules/brk-client/index.js | 95 +- packages/brk_client/brk_client/__init__.py | 41 +- .../options/distribution/profitability.js | 1173 ++++++----------- website/scripts/options/series.js | 9 + 7 files changed, 569 insertions(+), 876 deletions(-) diff --git a/crates/brk_client/src/lib.rs b/crates/brk_client/src/lib.rs index 51eccdf33..d9fbfc8e2 100644 --- a/crates/brk_client/src/lib.rs +++ b/crates/brk_client/src/lib.rs @@ -1077,7 +1077,7 @@ pub struct CapGrossInvestorLossMvrvNetPeakPriceProfitSellSoprPattern { pub loss: BaseCapitulationCumulativeNegativeSumToValuePattern, pub mvrv: SeriesPattern1, pub net_pnl: BaseChangeCumulativeDeltaSumToPattern, - pub peak_regret: BaseCumulativeToPattern, + pub peak_regret: BaseCumulativeSumToPattern, pub price: BpsCentsPercentilesRatioSatsSmaStdUsdPattern, pub profit: BaseCumulativeDistributionSumToValuePattern, pub profit_to_loss_ratio: _1m1w1y24hPattern, @@ -1259,7 +1259,7 @@ pub struct BaseCapitulationCumulativeNegativeSumToValuePattern { pub base: CentsUsdPattern2, pub capitulation_flow: SeriesPattern1, pub cumulative: CentsUsdPattern2, - pub negative: SeriesPattern1, + pub negative: BaseSumPattern, pub sum: _1m1w1y24hPattern4, pub to_rcap: BpsPercentRatioPattern4, pub value_created: BaseCumulativeSumPattern, @@ -2042,7 +2042,7 @@ impl BaseCumulativeDeltaSumPattern { pub struct BaseCumulativeNegativeSumPattern { pub base: CentsUsdPattern2, pub cumulative: CentsUsdPattern2, - pub negative: SeriesPattern1, + pub negative: BaseSumPattern, pub sum: _1m1w1y24hPattern4, } @@ -2052,12 +2052,32 @@ impl BaseCumulativeNegativeSumPattern { Self { base: CentsUsdPattern2::new(client.clone(), _m(&acc, "realized_loss")), cumulative: CentsUsdPattern2::new(client.clone(), _m(&acc, "realized_loss_cumulative")), - negative: SeriesPattern1::new(client.clone(), _m(&acc, "neg_realized_loss")), + negative: BaseSumPattern::new(client.clone(), _m(&acc, "neg_realized_loss")), sum: _1m1w1y24hPattern4::new(client.clone(), _m(&acc, "realized_loss_sum")), } } } +/// Pattern struct for repeated tree structure. +pub struct BaseCumulativeSumToPattern { + pub base: CentsUsdPattern2, + pub cumulative: CentsUsdPattern2, + pub sum: _1m1w1y24hPattern4, + pub to_rcap: BpsPercentRatioPattern4, +} + +impl BaseCumulativeSumToPattern { + /// Create a new pattern node with accumulated series name. + pub fn new(client: Arc, acc: String) -> Self { + Self { + base: CentsUsdPattern2::new(client.clone(), acc.clone()), + cumulative: CentsUsdPattern2::new(client.clone(), _m(&acc, "cumulative")), + sum: _1m1w1y24hPattern4::new(client.clone(), _m(&acc, "sum")), + to_rcap: BpsPercentRatioPattern4::new(client.clone(), _m(&acc, "to_rcap")), + } + } +} + /// Pattern struct for repeated tree structure. pub struct BothReactivatedReceivingSendingPattern { pub both: _1m1w1y24hBasePattern, @@ -2244,24 +2264,6 @@ impl BaseCumulativeSumPattern4 { } } -/// Pattern struct for repeated tree structure. -pub struct BaseCumulativeToPattern { - pub base: SeriesPattern1, - pub cumulative: SeriesPattern1, - pub to_rcap: BpsPercentRatioPattern4, -} - -impl BaseCumulativeToPattern { - /// Create a new pattern node with accumulated series name. - pub fn new(client: Arc, acc: String) -> Self { - Self { - base: SeriesPattern1::new(client.clone(), acc.clone()), - cumulative: SeriesPattern1::new(client.clone(), _m(&acc, "cumulative")), - to_rcap: BpsPercentRatioPattern4::new(client.clone(), _m(&acc, "to_rcap")), - } - } -} - /// Pattern struct for repeated tree structure. pub struct BaseCumulativeSumPattern3 { pub base: CentsUsdPattern2, @@ -2682,6 +2684,22 @@ impl AllSthPattern { } } +/// Pattern struct for repeated tree structure. +pub struct BaseSumPattern { + pub base: SeriesPattern1, + pub sum: _1m1w1y24hPattern, +} + +impl BaseSumPattern { + /// Create a new pattern node with accumulated series name. + pub fn new(client: Arc, acc: String) -> Self { + Self { + base: SeriesPattern1::new(client.clone(), acc.clone()), + sum: _1m1w1y24hPattern::new(client.clone(), _m(&acc, "sum")), + } + } +} + /// Pattern struct for repeated tree structure. pub struct BaseDeltaPattern { pub base: SeriesPattern1, @@ -6322,7 +6340,7 @@ pub struct SeriesTree_Cohorts_Utxo_All_Realized { pub net_pnl: BaseChangeCumulativeDeltaSumToPattern, pub gross_pnl: BaseCumulativeSumPattern3, pub sell_side_risk_ratio: _1m1w1y24hPattern6, - pub peak_regret: BaseCumulativeToPattern, + pub peak_regret: BaseCumulativeSumToPattern, pub investor: PricePattern, pub profit_to_loss_ratio: _1m1w1y24hPattern, } @@ -6339,7 +6357,7 @@ impl SeriesTree_Cohorts_Utxo_All_Realized { net_pnl: BaseChangeCumulativeDeltaSumToPattern::new(client.clone(), "net".to_string()), gross_pnl: BaseCumulativeSumPattern3::new(client.clone(), "realized_gross_pnl".to_string()), sell_side_risk_ratio: _1m1w1y24hPattern6::new(client.clone(), "sell_side_risk_ratio".to_string()), - peak_regret: BaseCumulativeToPattern::new(client.clone(), "realized_peak_regret".to_string()), + peak_regret: BaseCumulativeSumToPattern::new(client.clone(), "realized_peak_regret".to_string()), investor: PricePattern::new(client.clone(), "investor_price".to_string()), profit_to_loss_ratio: _1m1w1y24hPattern::new(client.clone(), "realized_profit_to_loss_ratio".to_string()), } @@ -6376,7 +6394,7 @@ pub struct SeriesTree_Cohorts_Utxo_All_Realized_Loss { pub base: CentsUsdPattern2, pub cumulative: CentsUsdPattern2, pub sum: _1m1w1y24hPattern4, - pub negative: SeriesPattern1, + pub negative: BaseSumPattern, pub to_rcap: BpsPercentRatioPattern4, pub value_created: BaseCumulativeSumPattern, pub value_destroyed: BaseCumulativeSumPattern, @@ -6389,7 +6407,7 @@ impl SeriesTree_Cohorts_Utxo_All_Realized_Loss { base: CentsUsdPattern2::new(client.clone(), "realized_loss".to_string()), cumulative: CentsUsdPattern2::new(client.clone(), "realized_loss_cumulative".to_string()), sum: _1m1w1y24hPattern4::new(client.clone(), "realized_loss_sum".to_string()), - negative: SeriesPattern1::new(client.clone(), "neg_realized_loss".to_string()), + negative: BaseSumPattern::new(client.clone(), "neg_realized_loss".to_string()), to_rcap: BpsPercentRatioPattern4::new(client.clone(), "realized_loss_to_rcap".to_string()), value_created: BaseCumulativeSumPattern::new(client.clone(), "loss_value_created".to_string()), value_destroyed: BaseCumulativeSumPattern::new(client.clone(), "loss_value_destroyed".to_string()), @@ -6817,7 +6835,7 @@ pub struct SeriesTree_Cohorts_Utxo_Sth_Realized { pub net_pnl: BaseChangeCumulativeDeltaSumToPattern, pub gross_pnl: BaseCumulativeSumPattern3, pub sell_side_risk_ratio: _1m1w1y24hPattern6, - pub peak_regret: BaseCumulativeToPattern, + pub peak_regret: BaseCumulativeSumToPattern, pub investor: PricePattern, pub profit_to_loss_ratio: _1m1w1y24hPattern, } @@ -6834,7 +6852,7 @@ impl SeriesTree_Cohorts_Utxo_Sth_Realized { net_pnl: BaseChangeCumulativeDeltaSumToPattern::new(client.clone(), "sth_net".to_string()), gross_pnl: BaseCumulativeSumPattern3::new(client.clone(), "sth_realized_gross_pnl".to_string()), sell_side_risk_ratio: _1m1w1y24hPattern6::new(client.clone(), "sth_sell_side_risk_ratio".to_string()), - peak_regret: BaseCumulativeToPattern::new(client.clone(), "sth_realized_peak_regret".to_string()), + peak_regret: BaseCumulativeSumToPattern::new(client.clone(), "sth_realized_peak_regret".to_string()), investor: PricePattern::new(client.clone(), "sth_investor_price".to_string()), profit_to_loss_ratio: _1m1w1y24hPattern::new(client.clone(), "sth_realized_profit_to_loss_ratio".to_string()), } @@ -6871,7 +6889,7 @@ pub struct SeriesTree_Cohorts_Utxo_Sth_Realized_Loss { pub base: CentsUsdPattern2, pub cumulative: CentsUsdPattern2, pub sum: _1m1w1y24hPattern4, - pub negative: SeriesPattern1, + pub negative: BaseSumPattern, pub to_rcap: BpsPercentRatioPattern4, pub value_created: BaseCumulativeSumPattern, pub value_destroyed: BaseCumulativeSumPattern, @@ -6884,7 +6902,7 @@ impl SeriesTree_Cohorts_Utxo_Sth_Realized_Loss { base: CentsUsdPattern2::new(client.clone(), "sth_realized_loss".to_string()), cumulative: CentsUsdPattern2::new(client.clone(), "sth_realized_loss_cumulative".to_string()), sum: _1m1w1y24hPattern4::new(client.clone(), "sth_realized_loss_sum".to_string()), - negative: SeriesPattern1::new(client.clone(), "sth_neg_realized_loss".to_string()), + negative: BaseSumPattern::new(client.clone(), "sth_neg_realized_loss".to_string()), to_rcap: BpsPercentRatioPattern4::new(client.clone(), "sth_realized_loss_to_rcap".to_string()), value_created: BaseCumulativeSumPattern::new(client.clone(), "sth_loss_value_created".to_string()), value_destroyed: BaseCumulativeSumPattern::new(client.clone(), "sth_loss_value_destroyed".to_string()), @@ -7255,7 +7273,7 @@ pub struct SeriesTree_Cohorts_Utxo_Lth_Realized { pub net_pnl: BaseChangeCumulativeDeltaSumToPattern, pub gross_pnl: BaseCumulativeSumPattern3, pub sell_side_risk_ratio: _1m1w1y24hPattern6, - pub peak_regret: BaseCumulativeToPattern, + pub peak_regret: BaseCumulativeSumToPattern, pub investor: PricePattern, pub profit_to_loss_ratio: _1m1w1y24hPattern, } @@ -7272,7 +7290,7 @@ impl SeriesTree_Cohorts_Utxo_Lth_Realized { net_pnl: BaseChangeCumulativeDeltaSumToPattern::new(client.clone(), "lth_net".to_string()), gross_pnl: BaseCumulativeSumPattern3::new(client.clone(), "lth_realized_gross_pnl".to_string()), sell_side_risk_ratio: _1m1w1y24hPattern6::new(client.clone(), "lth_sell_side_risk_ratio".to_string()), - peak_regret: BaseCumulativeToPattern::new(client.clone(), "lth_realized_peak_regret".to_string()), + peak_regret: BaseCumulativeSumToPattern::new(client.clone(), "lth_realized_peak_regret".to_string()), investor: PricePattern::new(client.clone(), "lth_investor_price".to_string()), profit_to_loss_ratio: _1m1w1y24hPattern::new(client.clone(), "lth_realized_profit_to_loss_ratio".to_string()), } @@ -7309,7 +7327,7 @@ pub struct SeriesTree_Cohorts_Utxo_Lth_Realized_Loss { pub base: CentsUsdPattern2, pub cumulative: CentsUsdPattern2, pub sum: _1m1w1y24hPattern4, - pub negative: SeriesPattern1, + pub negative: BaseSumPattern, pub to_rcap: BpsPercentRatioPattern4, pub value_created: BaseCumulativeSumPattern, pub value_destroyed: BaseCumulativeSumPattern, @@ -7322,7 +7340,7 @@ impl SeriesTree_Cohorts_Utxo_Lth_Realized_Loss { base: CentsUsdPattern2::new(client.clone(), "lth_realized_loss".to_string()), cumulative: CentsUsdPattern2::new(client.clone(), "lth_realized_loss_cumulative".to_string()), sum: _1m1w1y24hPattern4::new(client.clone(), "lth_realized_loss_sum".to_string()), - negative: SeriesPattern1::new(client.clone(), "lth_neg_realized_loss".to_string()), + negative: BaseSumPattern::new(client.clone(), "lth_neg_realized_loss".to_string()), to_rcap: BpsPercentRatioPattern4::new(client.clone(), "lth_realized_loss_to_rcap".to_string()), value_created: BaseCumulativeSumPattern::new(client.clone(), "lth_loss_value_created".to_string()), value_destroyed: BaseCumulativeSumPattern::new(client.clone(), "lth_loss_value_destroyed".to_string()), diff --git a/crates/brk_computer/src/distribution/metrics/realized/core.rs b/crates/brk_computer/src/distribution/metrics/realized/core.rs index b8c25ff7d..5077a0b7a 100644 --- a/crates/brk_computer/src/distribution/metrics/realized/core.rs +++ b/crates/brk_computer/src/distribution/metrics/realized/core.rs @@ -10,7 +10,7 @@ use crate::{ distribution::state::{CohortState, CostBasisOps, RealizedOps}, internal::{ FiatPerBlockCumulativeWithSumsAndDeltas, LazyPerBlock, NegCentsUnsignedToDollars, - RatioCents64, RollingWindow24hPerBlock, + RatioCents64, RollingWindow24hPerBlock, Windows, }, prices, }; @@ -19,6 +19,13 @@ use crate::distribution::metrics::ImportConfig; use super::RealizedMinimal; +#[derive(Clone, Traversable)] +pub struct NegRealizedLoss { + #[traversable(flatten)] + pub base: LazyPerBlock, + pub sum: Windows>, +} + #[derive(Traversable)] pub struct RealizedSoprCore { pub ratio: RollingWindow24hPerBlock, @@ -32,7 +39,7 @@ pub struct RealizedCore { pub minimal: RealizedMinimal, #[traversable(wrap = "loss", rename = "negative")] - pub neg_loss: LazyPerBlock, + pub neg_loss: NegRealizedLoss, pub net_pnl: FiatPerBlockCumulativeWithSumsAndDeltas, pub sopr: RealizedSoprCore, } @@ -43,13 +50,27 @@ impl RealizedCore { let minimal = RealizedMinimal::forced_import(cfg)?; - let neg_realized_loss = LazyPerBlock::from_height_source::( + let neg_loss_base = LazyPerBlock::from_height_source::( &cfg.name("neg_realized_loss"), cfg.version + Version::ONE, minimal.loss.base.cents.height.read_only_boxed_clone(), cfg.indexes, ); + let neg_loss_sum = minimal.loss.sum.0.map_with_suffix(|suffix, slot| { + LazyPerBlock::from_height_source::( + &cfg.name(&format!("neg_realized_loss_sum_{suffix}")), + cfg.version + Version::ONE, + slot.cents.height.read_only_boxed_clone(), + cfg.indexes, + ) + }); + + let neg_loss = NegRealizedLoss { + base: neg_loss_base, + sum: neg_loss_sum, + }; + let net_pnl = FiatPerBlockCumulativeWithSumsAndDeltas::forced_import( cfg.db, &cfg.name("net_realized_pnl"), @@ -61,7 +82,7 @@ impl RealizedCore { Ok(Self { minimal, - neg_loss: neg_realized_loss, + neg_loss, net_pnl, sopr: RealizedSoprCore { ratio: cfg.import("sopr", v1)?, diff --git a/crates/brk_computer/src/distribution/metrics/realized/full.rs b/crates/brk_computer/src/distribution/metrics/realized/full.rs index 9c08f77bb..b897b5e03 100644 --- a/crates/brk_computer/src/distribution/metrics/realized/full.rs +++ b/crates/brk_computer/src/distribution/metrics/realized/full.rs @@ -14,7 +14,7 @@ use crate::{ blocks, distribution::state::{WithCapital, CohortState, CostBasisData, RealizedState}, internal::{ - CentsUnsignedToDollars, PerBlockCumulative, + CentsUnsignedToDollars, PerBlockCumulativeWithSums, FiatPerBlockCumulativeWithSums, LazyPerBlock, PercentPerBlock, PercentRollingWindows, PriceWithRatioExtendedPerBlock, RatioCents64, RatioCentsBp32, @@ -63,7 +63,7 @@ pub struct RealizedSopr { #[derive(Traversable)] pub struct RealizedPeakRegret { #[traversable(flatten)] - pub value: PerBlockCumulative, + pub value: FiatPerBlockCumulativeWithSums, pub to_rcap: PercentPerBlock, } @@ -227,7 +227,7 @@ impl RealizedFull { .min(self.investor.price.cents.height.len()) .min(self.cap_raw.len()) .min(self.investor.cap_raw.len()) - .min(self.peak_regret.value.base.height.len()) + .min(self.peak_regret.value.base.cents.height.len()) } #[inline(always)] @@ -269,6 +269,7 @@ impl RealizedFull { self.peak_regret .value .base + .cents .height .push(state.realized.peak_regret()); } @@ -282,7 +283,7 @@ impl RealizedFull { vecs.push(&mut self.investor.price.cents.height); vecs.push(&mut self.cap_raw as &mut dyn AnyStoredVec); vecs.push(&mut self.investor.cap_raw as &mut dyn AnyStoredVec); - vecs.push(&mut self.peak_regret.value.base.height); + vecs.push(&mut self.peak_regret.value.base.cents.height); vecs } @@ -346,6 +347,7 @@ impl RealizedFull { self.peak_regret .value .base + .cents .height .push(accum.peak_regret()); } @@ -470,7 +472,7 @@ impl RealizedFull { .to_rcap .compute_binary::( starting_indexes.height, - &self.peak_regret.value.base.height, + &self.peak_regret.value.base.cents.height, &self.core.minimal.cap.cents.height, exit, )?; diff --git a/modules/brk-client/index.js b/modules/brk-client/index.js index 5923a43c6..71a1a5fed 100644 --- a/modules/brk-client/index.js +++ b/modules/brk-client/index.js @@ -1789,7 +1789,7 @@ function create_10y1m1w1y2y3m3y4y5y6m6y8yPattern3(client, acc) { * @property {BaseCapitulationCumulativeNegativeSumToValuePattern} loss * @property {SeriesPattern1} mvrv * @property {BaseChangeCumulativeDeltaSumToPattern} netPnl - * @property {BaseCumulativeToPattern} peakRegret + * @property {BaseCumulativeSumToPattern} peakRegret * @property {BpsCentsPercentilesRatioSatsSmaStdUsdPattern} price * @property {BaseCumulativeDistributionSumToValuePattern} profit * @property {_1m1w1y24hPattern} profitToLossRatio @@ -1988,7 +1988,7 @@ function createAverageMaxMedianMinPct10Pct25Pct75Pct90Pattern2(client, acc) { * @property {CentsUsdPattern2} base * @property {SeriesPattern1} capitulationFlow * @property {CentsUsdPattern2} cumulative - * @property {SeriesPattern1} negative + * @property {BaseSumPattern} negative * @property {_1m1w1y24hPattern4} sum * @property {BpsPercentRatioPattern4} toRcap * @property {BaseCumulativeSumPattern} valueCreated @@ -2868,7 +2868,7 @@ function createBaseCumulativeDeltaSumPattern(client, acc) { * @typedef {Object} BaseCumulativeNegativeSumPattern * @property {CentsUsdPattern2} base * @property {CentsUsdPattern2} cumulative - * @property {SeriesPattern1} negative + * @property {BaseSumPattern} negative * @property {_1m1w1y24hPattern4} sum */ @@ -2882,11 +2882,34 @@ function createBaseCumulativeNegativeSumPattern(client, acc) { return { base: createCentsUsdPattern2(client, _m(acc, 'realized_loss')), cumulative: createCentsUsdPattern2(client, _m(acc, 'realized_loss_cumulative')), - negative: createSeriesPattern1(client, _m(acc, 'neg_realized_loss')), + negative: createBaseSumPattern(client, _m(acc, 'neg_realized_loss')), sum: create_1m1w1y24hPattern4(client, _m(acc, 'realized_loss_sum')), }; } +/** + * @typedef {Object} BaseCumulativeSumToPattern + * @property {CentsUsdPattern2} base + * @property {CentsUsdPattern2} cumulative + * @property {_1m1w1y24hPattern4} sum + * @property {BpsPercentRatioPattern4} toRcap + */ + +/** + * Create a BaseCumulativeSumToPattern pattern node + * @param {BrkClientBase} client + * @param {string} acc - Accumulated series name + * @returns {BaseCumulativeSumToPattern} + */ +function createBaseCumulativeSumToPattern(client, acc) { + return { + base: createCentsUsdPattern2(client, acc), + cumulative: createCentsUsdPattern2(client, _m(acc, 'cumulative')), + sum: create_1m1w1y24hPattern4(client, _m(acc, 'sum')), + toRcap: createBpsPercentRatioPattern4(client, _m(acc, 'to_rcap')), + }; +} + /** * @typedef {Object} BothReactivatedReceivingSendingPattern * @property {_1m1w1y24hBasePattern} both @@ -3102,27 +3125,6 @@ function createBaseCumulativeSumPattern4(client, acc) { }; } -/** - * @typedef {Object} BaseCumulativeToPattern - * @property {SeriesPattern1} base - * @property {SeriesPattern1} cumulative - * @property {BpsPercentRatioPattern4} toRcap - */ - -/** - * Create a BaseCumulativeToPattern pattern node - * @param {BrkClientBase} client - * @param {string} acc - Accumulated series name - * @returns {BaseCumulativeToPattern} - */ -function createBaseCumulativeToPattern(client, acc) { - return { - base: createSeriesPattern1(client, acc), - cumulative: createSeriesPattern1(client, _m(acc, 'cumulative')), - toRcap: createBpsPercentRatioPattern4(client, _m(acc, 'to_rcap')), - }; -} - /** * @typedef {Object} BaseCumulativeSumPattern3 * @property {CentsUsdPattern2} base @@ -3617,6 +3619,25 @@ function createAllSthPattern(client, acc) { }; } +/** + * @typedef {Object} BaseSumPattern + * @property {SeriesPattern1} base + * @property {_1m1w1y24hPattern} sum + */ + +/** + * Create a BaseSumPattern pattern node + * @param {BrkClientBase} client + * @param {string} acc - Accumulated series name + * @returns {BaseSumPattern} + */ +function createBaseSumPattern(client, acc) { + return { + base: createSeriesPattern1(client, acc), + sum: create_1m1w1y24hPattern(client, _m(acc, 'sum')), + }; +} + /** * @typedef {Object} BaseDeltaPattern * @property {SeriesPattern1} base @@ -5391,7 +5412,7 @@ function createUnspentPattern(client, acc) { * @property {BaseChangeCumulativeDeltaSumToPattern} netPnl * @property {BaseCumulativeSumPattern3} grossPnl * @property {_1m1w1y24hPattern6} sellSideRiskRatio - * @property {BaseCumulativeToPattern} peakRegret + * @property {BaseCumulativeSumToPattern} peakRegret * @property {PricePattern} investor * @property {_1m1w1y24hPattern} profitToLossRatio */ @@ -5412,7 +5433,7 @@ function createUnspentPattern(client, acc) { * @property {CentsUsdPattern2} base * @property {CentsUsdPattern2} cumulative * @property {_1m1w1y24hPattern4} sum - * @property {SeriesPattern1} negative + * @property {BaseSumPattern} negative * @property {BpsPercentRatioPattern4} toRcap * @property {BaseCumulativeSumPattern} valueCreated * @property {BaseCumulativeSumPattern} valueDestroyed @@ -5610,7 +5631,7 @@ function createUnspentPattern(client, acc) { * @property {BaseChangeCumulativeDeltaSumToPattern} netPnl * @property {BaseCumulativeSumPattern3} grossPnl * @property {_1m1w1y24hPattern6} sellSideRiskRatio - * @property {BaseCumulativeToPattern} peakRegret + * @property {BaseCumulativeSumToPattern} peakRegret * @property {PricePattern} investor * @property {_1m1w1y24hPattern} profitToLossRatio */ @@ -5631,7 +5652,7 @@ function createUnspentPattern(client, acc) { * @property {CentsUsdPattern2} base * @property {CentsUsdPattern2} cumulative * @property {_1m1w1y24hPattern4} sum - * @property {SeriesPattern1} negative + * @property {BaseSumPattern} negative * @property {BpsPercentRatioPattern4} toRcap * @property {BaseCumulativeSumPattern} valueCreated * @property {BaseCumulativeSumPattern} valueDestroyed @@ -5805,7 +5826,7 @@ function createUnspentPattern(client, acc) { * @property {BaseChangeCumulativeDeltaSumToPattern} netPnl * @property {BaseCumulativeSumPattern3} grossPnl * @property {_1m1w1y24hPattern6} sellSideRiskRatio - * @property {BaseCumulativeToPattern} peakRegret + * @property {BaseCumulativeSumToPattern} peakRegret * @property {PricePattern} investor * @property {_1m1w1y24hPattern} profitToLossRatio */ @@ -5826,7 +5847,7 @@ function createUnspentPattern(client, acc) { * @property {CentsUsdPattern2} base * @property {CentsUsdPattern2} cumulative * @property {_1m1w1y24hPattern4} sum - * @property {SeriesPattern1} negative + * @property {BaseSumPattern} negative * @property {BpsPercentRatioPattern4} toRcap * @property {BaseCumulativeSumPattern} valueCreated * @property {BaseCumulativeSumPattern} valueDestroyed @@ -8513,7 +8534,7 @@ class BrkClient extends BrkClientBase { base: createCentsUsdPattern2(this, 'realized_loss'), cumulative: createCentsUsdPattern2(this, 'realized_loss_cumulative'), sum: create_1m1w1y24hPattern4(this, 'realized_loss_sum'), - negative: createSeriesPattern1(this, 'neg_realized_loss'), + negative: createBaseSumPattern(this, 'neg_realized_loss'), toRcap: createBpsPercentRatioPattern4(this, 'realized_loss_to_rcap'), valueCreated: createBaseCumulativeSumPattern(this, 'loss_value_created'), valueDestroyed: createBaseCumulativeSumPattern(this, 'loss_value_destroyed'), @@ -8612,7 +8633,7 @@ class BrkClient extends BrkClientBase { netPnl: createBaseChangeCumulativeDeltaSumToPattern(this, 'net'), grossPnl: createBaseCumulativeSumPattern3(this, 'realized_gross_pnl'), sellSideRiskRatio: create_1m1w1y24hPattern6(this, 'sell_side_risk_ratio'), - peakRegret: createBaseCumulativeToPattern(this, 'realized_peak_regret'), + peakRegret: createBaseCumulativeSumToPattern(this, 'realized_peak_regret'), investor: createPricePattern(this, 'investor_price'), profitToLossRatio: create_1m1w1y24hPattern(this, 'realized_profit_to_loss_ratio'), }, @@ -8676,7 +8697,7 @@ class BrkClient extends BrkClientBase { base: createCentsUsdPattern2(this, 'sth_realized_loss'), cumulative: createCentsUsdPattern2(this, 'sth_realized_loss_cumulative'), sum: create_1m1w1y24hPattern4(this, 'sth_realized_loss_sum'), - negative: createSeriesPattern1(this, 'sth_neg_realized_loss'), + negative: createBaseSumPattern(this, 'sth_neg_realized_loss'), toRcap: createBpsPercentRatioPattern4(this, 'sth_realized_loss_to_rcap'), valueCreated: createBaseCumulativeSumPattern(this, 'sth_loss_value_created'), valueDestroyed: createBaseCumulativeSumPattern(this, 'sth_loss_value_destroyed'), @@ -8775,7 +8796,7 @@ class BrkClient extends BrkClientBase { netPnl: createBaseChangeCumulativeDeltaSumToPattern(this, 'sth_net'), grossPnl: createBaseCumulativeSumPattern3(this, 'sth_realized_gross_pnl'), sellSideRiskRatio: create_1m1w1y24hPattern6(this, 'sth_sell_side_risk_ratio'), - peakRegret: createBaseCumulativeToPattern(this, 'sth_realized_peak_regret'), + peakRegret: createBaseCumulativeSumToPattern(this, 'sth_realized_peak_regret'), investor: createPricePattern(this, 'sth_investor_price'), profitToLossRatio: create_1m1w1y24hPattern(this, 'sth_realized_profit_to_loss_ratio'), }, @@ -8824,7 +8845,7 @@ class BrkClient extends BrkClientBase { base: createCentsUsdPattern2(this, 'lth_realized_loss'), cumulative: createCentsUsdPattern2(this, 'lth_realized_loss_cumulative'), sum: create_1m1w1y24hPattern4(this, 'lth_realized_loss_sum'), - negative: createSeriesPattern1(this, 'lth_neg_realized_loss'), + negative: createBaseSumPattern(this, 'lth_neg_realized_loss'), toRcap: createBpsPercentRatioPattern4(this, 'lth_realized_loss_to_rcap'), valueCreated: createBaseCumulativeSumPattern(this, 'lth_loss_value_created'), valueDestroyed: createBaseCumulativeSumPattern(this, 'lth_loss_value_destroyed'), @@ -8918,7 +8939,7 @@ class BrkClient extends BrkClientBase { netPnl: createBaseChangeCumulativeDeltaSumToPattern(this, 'lth_net'), grossPnl: createBaseCumulativeSumPattern3(this, 'lth_realized_gross_pnl'), sellSideRiskRatio: create_1m1w1y24hPattern6(this, 'lth_sell_side_risk_ratio'), - peakRegret: createBaseCumulativeToPattern(this, 'lth_realized_peak_regret'), + peakRegret: createBaseCumulativeSumToPattern(this, 'lth_realized_peak_regret'), investor: createPricePattern(this, 'lth_investor_price'), profitToLossRatio: create_1m1w1y24hPattern(this, 'lth_realized_profit_to_loss_ratio'), }, diff --git a/packages/brk_client/brk_client/__init__.py b/packages/brk_client/brk_client/__init__.py index 96a305055..d69efdfc5 100644 --- a/packages/brk_client/brk_client/__init__.py +++ b/packages/brk_client/brk_client/__init__.py @@ -2695,9 +2695,19 @@ class BaseCumulativeNegativeSumPattern: """Create pattern node with accumulated series name.""" self.base: CentsUsdPattern2 = CentsUsdPattern2(client, _m(acc, 'realized_loss')) self.cumulative: CentsUsdPattern2 = CentsUsdPattern2(client, _m(acc, 'realized_loss_cumulative')) - self.negative: SeriesPattern1[Dollars] = SeriesPattern1(client, _m(acc, 'neg_realized_loss')) + self.negative: BaseSumPattern = BaseSumPattern(client, _m(acc, 'neg_realized_loss')) self.sum: _1m1w1y24hPattern4 = _1m1w1y24hPattern4(client, _m(acc, 'realized_loss_sum')) +class BaseCumulativeSumToPattern: + """Pattern struct for repeated tree structure.""" + + def __init__(self, client: BrkClientBase, acc: str): + """Create pattern node with accumulated series name.""" + self.base: CentsUsdPattern2 = CentsUsdPattern2(client, acc) + self.cumulative: CentsUsdPattern2 = CentsUsdPattern2(client, _m(acc, 'cumulative')) + self.sum: _1m1w1y24hPattern4 = _1m1w1y24hPattern4(client, _m(acc, 'sum')) + self.to_rcap: BpsPercentRatioPattern4 = BpsPercentRatioPattern4(client, _m(acc, 'to_rcap')) + class BothReactivatedReceivingSendingPattern: """Pattern struct for repeated tree structure.""" @@ -2791,15 +2801,6 @@ class BaseCumulativeSumPattern4: self.cumulative: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, _m(acc, 'cumulative')) self.sum: _1m1w1y24hPattern5 = _1m1w1y24hPattern5(client, _m(acc, 'sum')) -class BaseCumulativeToPattern: - """Pattern struct for repeated tree structure.""" - - def __init__(self, client: BrkClientBase, acc: str): - """Create pattern node with accumulated series name.""" - self.base: SeriesPattern1[Cents] = SeriesPattern1(client, acc) - self.cumulative: SeriesPattern1[Cents] = SeriesPattern1(client, _m(acc, 'cumulative')) - self.to_rcap: BpsPercentRatioPattern4 = BpsPercentRatioPattern4(client, _m(acc, 'to_rcap')) - class BaseCumulativeSumPattern3: """Pattern struct for repeated tree structure.""" @@ -3011,6 +3012,14 @@ class AllSthPattern: self.all: SeriesPattern1[Dollars] = SeriesPattern1(client, _m(acc, 'realized_cap')) self.sth: SeriesPattern1[Dollars] = SeriesPattern1(client, _m(acc, 'sth_realized_cap')) +class BaseSumPattern: + """Pattern struct for repeated tree structure.""" + + def __init__(self, client: BrkClientBase, acc: str): + """Create pattern node with accumulated series name.""" + self.base: SeriesPattern1[Dollars] = SeriesPattern1(client, acc) + self.sum: _1m1w1y24hPattern[Dollars] = _1m1w1y24hPattern(client, _m(acc, 'sum')) + class BaseDeltaPattern: """Pattern struct for repeated tree structure.""" @@ -4715,7 +4724,7 @@ class SeriesTree_Cohorts_Utxo_All_Realized_Loss: self.base: CentsUsdPattern2 = CentsUsdPattern2(client, 'realized_loss') self.cumulative: CentsUsdPattern2 = CentsUsdPattern2(client, 'realized_loss_cumulative') self.sum: _1m1w1y24hPattern4 = _1m1w1y24hPattern4(client, 'realized_loss_sum') - self.negative: SeriesPattern1[Dollars] = SeriesPattern1(client, 'neg_realized_loss') + self.negative: BaseSumPattern = BaseSumPattern(client, 'neg_realized_loss') self.to_rcap: BpsPercentRatioPattern4 = BpsPercentRatioPattern4(client, 'realized_loss_to_rcap') self.value_created: BaseCumulativeSumPattern[Cents] = BaseCumulativeSumPattern(client, 'loss_value_created') self.value_destroyed: BaseCumulativeSumPattern[Cents] = BaseCumulativeSumPattern(client, 'loss_value_destroyed') @@ -4853,7 +4862,7 @@ class SeriesTree_Cohorts_Utxo_All_Realized: self.net_pnl: BaseChangeCumulativeDeltaSumToPattern = BaseChangeCumulativeDeltaSumToPattern(client, 'net') self.gross_pnl: BaseCumulativeSumPattern3 = BaseCumulativeSumPattern3(client, 'realized_gross_pnl') self.sell_side_risk_ratio: _1m1w1y24hPattern6 = _1m1w1y24hPattern6(client, 'sell_side_risk_ratio') - self.peak_regret: BaseCumulativeToPattern = BaseCumulativeToPattern(client, 'realized_peak_regret') + self.peak_regret: BaseCumulativeSumToPattern = BaseCumulativeSumToPattern(client, 'realized_peak_regret') self.investor: PricePattern = PricePattern(client, 'investor_price') self.profit_to_loss_ratio: _1m1w1y24hPattern[StoredF64] = _1m1w1y24hPattern(client, 'realized_profit_to_loss_ratio') @@ -4953,7 +4962,7 @@ class SeriesTree_Cohorts_Utxo_Sth_Realized_Loss: self.base: CentsUsdPattern2 = CentsUsdPattern2(client, 'sth_realized_loss') self.cumulative: CentsUsdPattern2 = CentsUsdPattern2(client, 'sth_realized_loss_cumulative') self.sum: _1m1w1y24hPattern4 = _1m1w1y24hPattern4(client, 'sth_realized_loss_sum') - self.negative: SeriesPattern1[Dollars] = SeriesPattern1(client, 'sth_neg_realized_loss') + self.negative: BaseSumPattern = BaseSumPattern(client, 'sth_neg_realized_loss') self.to_rcap: BpsPercentRatioPattern4 = BpsPercentRatioPattern4(client, 'sth_realized_loss_to_rcap') self.value_created: BaseCumulativeSumPattern[Cents] = BaseCumulativeSumPattern(client, 'sth_loss_value_created') self.value_destroyed: BaseCumulativeSumPattern[Cents] = BaseCumulativeSumPattern(client, 'sth_loss_value_destroyed') @@ -5091,7 +5100,7 @@ class SeriesTree_Cohorts_Utxo_Sth_Realized: self.net_pnl: BaseChangeCumulativeDeltaSumToPattern = BaseChangeCumulativeDeltaSumToPattern(client, 'sth_net') self.gross_pnl: BaseCumulativeSumPattern3 = BaseCumulativeSumPattern3(client, 'sth_realized_gross_pnl') self.sell_side_risk_ratio: _1m1w1y24hPattern6 = _1m1w1y24hPattern6(client, 'sth_sell_side_risk_ratio') - self.peak_regret: BaseCumulativeToPattern = BaseCumulativeToPattern(client, 'sth_realized_peak_regret') + self.peak_regret: BaseCumulativeSumToPattern = BaseCumulativeSumToPattern(client, 'sth_realized_peak_regret') self.investor: PricePattern = PricePattern(client, 'sth_investor_price') self.profit_to_loss_ratio: _1m1w1y24hPattern[StoredF64] = _1m1w1y24hPattern(client, 'sth_realized_profit_to_loss_ratio') @@ -5164,7 +5173,7 @@ class SeriesTree_Cohorts_Utxo_Lth_Realized_Loss: self.base: CentsUsdPattern2 = CentsUsdPattern2(client, 'lth_realized_loss') self.cumulative: CentsUsdPattern2 = CentsUsdPattern2(client, 'lth_realized_loss_cumulative') self.sum: _1m1w1y24hPattern4 = _1m1w1y24hPattern4(client, 'lth_realized_loss_sum') - self.negative: SeriesPattern1[Dollars] = SeriesPattern1(client, 'lth_neg_realized_loss') + self.negative: BaseSumPattern = BaseSumPattern(client, 'lth_neg_realized_loss') self.to_rcap: BpsPercentRatioPattern4 = BpsPercentRatioPattern4(client, 'lth_realized_loss_to_rcap') self.value_created: BaseCumulativeSumPattern[Cents] = BaseCumulativeSumPattern(client, 'lth_loss_value_created') self.value_destroyed: BaseCumulativeSumPattern[Cents] = BaseCumulativeSumPattern(client, 'lth_loss_value_destroyed') @@ -5293,7 +5302,7 @@ class SeriesTree_Cohorts_Utxo_Lth_Realized: self.net_pnl: BaseChangeCumulativeDeltaSumToPattern = BaseChangeCumulativeDeltaSumToPattern(client, 'lth_net') self.gross_pnl: BaseCumulativeSumPattern3 = BaseCumulativeSumPattern3(client, 'lth_realized_gross_pnl') self.sell_side_risk_ratio: _1m1w1y24hPattern6 = _1m1w1y24hPattern6(client, 'lth_sell_side_risk_ratio') - self.peak_regret: BaseCumulativeToPattern = BaseCumulativeToPattern(client, 'lth_realized_peak_regret') + self.peak_regret: BaseCumulativeSumToPattern = BaseCumulativeSumToPattern(client, 'lth_realized_peak_regret') self.investor: PricePattern = PricePattern(client, 'lth_investor_price') self.profit_to_loss_ratio: _1m1w1y24hPattern[StoredF64] = _1m1w1y24hPattern(client, 'lth_realized_profit_to_loss_ratio') diff --git a/website/scripts/options/distribution/profitability.js b/website/scripts/options/distribution/profitability.js index 7f69194ec..dcef26229 100644 --- a/website/scripts/options/distribution/profitability.js +++ b/website/scripts/options/distribution/profitability.js @@ -11,7 +11,7 @@ */ import { Unit } from "../../utils/units.js"; -import { ROLLING_WINDOWS, line, baseline, dots, dotsBaseline, percentRatio, percentRatioBaseline } from "../series.js"; +import { ROLLING_WINDOWS, line, dotted, baseline, dots, dotsBaseline, percentRatio, percentRatioBaseline } from "../series.js"; import { colors } from "../../utils/colors.js"; import { priceLine } from "../constants.js"; import { @@ -24,31 +24,6 @@ import { // Core Series Builders // ============================================================================ -/** - * @typedef {Object} PnlSeriesConfig - * @property {AnySeriesPattern} profit - * @property {AnySeriesPattern} loss - * @property {AnySeriesPattern} negLoss - * @property {AnySeriesPattern} [gross] - */ - -/** - * @param {PnlSeriesConfig} m - * @param {Unit} unit - * @returns {AnyFetchedSeriesBlueprint[]} - */ -function pnlLines(m, unit) { - const series = [ - line({ series: m.profit, name: "Profit", color: colors.profit, unit }), - line({ series: m.loss, name: "Loss", color: colors.loss, unit }), - ]; - if (m.gross) { - series.push(line({ series: m.gross, name: "Total", color: colors.default, unit })); - } - series.push(line({ series: m.negLoss, name: "Loss (Inverted)", color: colors.loss, unit, defaultActive: false })); - return series; -} - /** * @param {AnySeriesPattern} s * @param {Unit} unit @@ -63,25 +38,34 @@ function netBaseline(s, unit) { // ============================================================================ /** - * @param {{ profit: { usd: AnySeriesPattern }, loss: { usd: AnySeriesPattern, negative: AnySeriesPattern }, grossPnl: { usd: AnySeriesPattern } }} u - * @returns {AnyFetchedSeriesBlueprint[]} + * Overview chart: net + profit + loss inverted (active), loss raw (hidden) + * @param {{ usd: AnySeriesPattern }} profit + * @param {{ usd: AnySeriesPattern, negative: AnySeriesPattern }} loss + * @param {AnySeriesPattern} netPnlUsd + * @param {(name: string) => string} title + * @returns {PartialChartOption} */ -function unrealizedUsdSeries(u) { - return [ - ...pnlLines( - { profit: u.profit.usd, loss: u.loss.usd, negLoss: u.loss.negative, gross: u.grossPnl.usd }, - Unit.usd, - ), - priceLine({ unit: Unit.usd, defaultActive: false }), - ]; +function unrealizedOverview(profit, loss, netPnlUsd, title) { + return { + name: "Overview", + title: title("Unrealized P&L"), + bottom: [ + baseline({ series: netPnlUsd, name: "Net P&L", unit: Unit.usd }), + dotted({ series: profit.usd, name: "Profit", color: colors.profit, unit: Unit.usd }), + dotted({ series: loss.negative, name: "Neg. Loss", color: colors.loss, unit: Unit.usd }), + dotted({ series: loss.usd, name: "Loss", color: colors.loss, unit: Unit.usd, defaultActive: false }), + priceLine({ unit: Unit.usd }), + ], + }; } /** + * Relative P&L chart: profit + loss as % of some denominator * @param {{ percent: AnySeriesPattern, ratio: AnySeriesPattern }} profit * @param {{ percent: AnySeriesPattern, ratio: AnySeriesPattern }} loss * @param {string} name * @param {(name: string) => string} title - * @returns {AnyPartialOption} + * @returns {PartialChartOption} */ function relPnlChart(profit, loss, name, title) { return { @@ -95,123 +79,118 @@ function relPnlChart(profit, loss, name, title) { } /** - * Unrealized P&L tree for All cohort + * Unrealized tree for All cohort * @param {AllRelativePattern} u * @param {(name: string) => string} title * @returns {PartialOptionsTree} */ -function unrealizedPnlTreeAll(u, title) { +function unrealizedTreeAll(u, title) { return [ - { name: "USD", title: title("Unrealized P&L"), bottom: unrealizedUsdSeries(u) }, - relPnlChart(u.profit.toMcap, u.loss.toMcap, "% of Mcap", title), - relPnlChart(u.profit.toOwnGrossPnl, u.loss.toOwnGrossPnl, "% of Own P&L", title), - ]; -} - -/** - * Unrealized P&L tree for Full cohorts (STH) - * @param {FullRelativePattern} u - * @param {(name: string) => string} title - * @returns {PartialOptionsTree} - */ -function unrealizedPnlTreeFull(u, title) { - return [ - { name: "USD", title: title("Unrealized P&L"), bottom: unrealizedUsdSeries(u) }, - relPnlChart(u.profit.toMcap, u.loss.toMcap, "% of Mcap", title), - relPnlChart(u.profit.toOwnMcap, u.loss.toOwnMcap, "% of Own Mcap", title), - relPnlChart(u.profit.toOwnGrossPnl, u.loss.toOwnGrossPnl, "% of Own P&L", title), - ]; -} - -/** - * Unrealized P&L tree for LTH (loss relToMcap only) - * @param {FullRelativePattern} u - * @param {(name: string) => string} title - * @returns {PartialOptionsTree} - */ -function unrealizedPnlTreeLongTerm(u, title) { - return [ - { name: "USD", title: title("Unrealized P&L"), bottom: unrealizedUsdSeries(u) }, + unrealizedOverview(u.profit, u.loss, u.netPnl.usd, title), + { name: "Net P&L", title: title("Net Unrealized P&L"), bottom: [netBaseline(u.netPnl.usd, Unit.usd)] }, + { name: "NUPL", title: title("NUPL"), bottom: nuplSeries(u.nupl) }, + { name: "Profit", title: title("Unrealized Profit"), bottom: [line({ series: u.profit.usd, name: "Profit", color: colors.profit, unit: Unit.usd })] }, + { name: "Loss", title: title("Unrealized Loss"), bottom: [line({ series: u.loss.usd, name: "Loss", color: colors.loss, unit: Unit.usd })] }, { - name: "% of Mcap", - title: title("Unrealized Loss (% of Mcap)"), - bottom: percentRatio({ pattern: u.loss.toMcap, name: "Loss", color: colors.loss }), - }, - relPnlChart(u.profit.toOwnMcap, u.loss.toOwnMcap, "% of Own Mcap", title), - relPnlChart(u.profit.toOwnGrossPnl, u.loss.toOwnGrossPnl, "% of Own P&L", title), - ]; -} - -/** - * Unrealized P&L tree for mid-tier cohorts (AgeRange/MaxAge) - * @param {BasicRelativePattern} u - * @param {(name: string) => string} title - * @returns {PartialOptionsTree} - */ -function unrealizedPnlTreeMid(u, title) { - return [ - { - name: "USD", - title: title("Unrealized P&L"), - bottom: [ - ...pnlLines( - { profit: u.profit.usd, loss: u.loss.usd, negLoss: u.loss.negative }, - Unit.usd, - ), - priceLine({ unit: Unit.usd, defaultActive: false }), + name: "Relative", + tree: [ + { + name: "Own P&L", + title: title("Unrealized P&L (% of Own P&L)"), + bottom: [ + ...percentRatioBaseline({ pattern: u.netPnl.toOwnGrossPnl, name: "Net" }), + ...percentRatio({ pattern: u.profit.toOwnGrossPnl, name: "Profit", color: colors.profit, defaultActive: false }), + ...percentRatio({ pattern: u.loss.toOwnGrossPnl, name: "Loss", color: colors.loss, defaultActive: false }), + ], + }, + relPnlChart(u.profit.toMcap, u.loss.toMcap, "Market Cap", title), ], }, ]; } -// ============================================================================ -// Net Unrealized P&L Builders -// ============================================================================ - -/** - * @param {AllRelativePattern} u - * @param {(name: string) => string} title - * @returns {PartialOptionsTree} - */ -function netUnrealizedTreeAll(u, title) { - return [ - { name: "USD", title: title("Net Unrealized P&L"), bottom: [netBaseline(u.netPnl.usd, Unit.usd)] }, - { - name: "% of Own P&L", - title: title("Net Unrealized P&L (% of Own P&L)"), - bottom: percentRatioBaseline({ pattern: u.netPnl.toOwnGrossPnl, name: "Net P&L" }), - }, - ]; -} - /** + * Unrealized tree for Full cohorts (STH) * @param {FullRelativePattern} u * @param {(name: string) => string} title * @returns {PartialOptionsTree} */ -function netUnrealizedTreeFull(u, title) { +function unrealizedTreeFull(u, title) { return [ - { name: "USD", title: title("Net Unrealized P&L"), bottom: [netBaseline(u.netPnl.usd, Unit.usd)] }, + unrealizedOverview(u.profit, u.loss, u.netPnl.usd, title), + { name: "Net P&L", title: title("Net Unrealized P&L"), bottom: [netBaseline(u.netPnl.usd, Unit.usd)] }, + { name: "NUPL", title: title("NUPL"), bottom: nuplSeries(u.nupl) }, + { name: "Profit", title: title("Unrealized Profit"), bottom: [line({ series: u.profit.usd, name: "Profit", color: colors.profit, unit: Unit.usd })] }, + { name: "Loss", title: title("Unrealized Loss"), bottom: [line({ series: u.loss.usd, name: "Loss", color: colors.loss, unit: Unit.usd })] }, { - name: "% of Own Mcap", - title: title("Net Unrealized P&L (% of Own Mcap)"), - bottom: percentRatioBaseline({ pattern: u.netPnl.toOwnMcap, name: "Net P&L" }), - }, - { - name: "% of Own P&L", - title: title("Net Unrealized P&L (% of Own P&L)"), - bottom: percentRatioBaseline({ pattern: u.netPnl.toOwnGrossPnl, name: "Net P&L" }), + name: "Relative", + tree: [ + { + name: "Own P&L", + title: title("Unrealized P&L (% of Own P&L)"), + bottom: [ + ...percentRatioBaseline({ pattern: u.netPnl.toOwnGrossPnl, name: "Net" }), + ...percentRatio({ pattern: u.profit.toOwnGrossPnl, name: "Profit", color: colors.profit, defaultActive: false }), + ...percentRatio({ pattern: u.loss.toOwnGrossPnl, name: "Loss", color: colors.loss, defaultActive: false }), + ], + }, + relPnlChart(u.profit.toMcap, u.loss.toMcap, "Market Cap", title), + relPnlChart(u.profit.toOwnMcap, u.loss.toOwnMcap, "Own Market Cap", title), + ], }, ]; } /** - * Net P&L for mid-tier cohorts - * @param {BasicRelativePattern} u - * @returns {AnyFetchedSeriesBlueprint[]} + * Unrealized tree for LTH (loss relToMcap only, has own mcap + own P&L) + * @param {FullRelativePattern} u + * @param {(name: string) => string} title + * @returns {PartialOptionsTree} */ -function netUnrealizedMid(u) { - return [netBaseline(u.netPnl.usd, Unit.usd)]; +function unrealizedTreeLongTerm(u, title) { + return [ + unrealizedOverview(u.profit, u.loss, u.netPnl.usd, title), + { name: "Net P&L", title: title("Net Unrealized P&L"), bottom: [netBaseline(u.netPnl.usd, Unit.usd)] }, + { name: "NUPL", title: title("NUPL"), bottom: nuplSeries(u.nupl) }, + { name: "Profit", title: title("Unrealized Profit"), bottom: [line({ series: u.profit.usd, name: "Profit", color: colors.profit, unit: Unit.usd })] }, + { name: "Loss", title: title("Unrealized Loss"), bottom: [line({ series: u.loss.usd, name: "Loss", color: colors.loss, unit: Unit.usd })] }, + { + name: "Relative", + tree: [ + { + name: "Own P&L", + title: title("Unrealized P&L (% of Own P&L)"), + bottom: [ + ...percentRatioBaseline({ pattern: u.netPnl.toOwnGrossPnl, name: "Net" }), + ...percentRatio({ pattern: u.profit.toOwnGrossPnl, name: "Profit", color: colors.profit, defaultActive: false }), + ...percentRatio({ pattern: u.loss.toOwnGrossPnl, name: "Loss", color: colors.loss, defaultActive: false }), + ], + }, + { + name: "Market Cap", + title: title("Unrealized Loss (% of Market Cap)"), + bottom: percentRatio({ pattern: u.loss.toMcap, name: "Loss", color: colors.loss }), + }, + relPnlChart(u.profit.toOwnMcap, u.loss.toOwnMcap, "Own Market Cap", title), + ], + }, + ]; +} + +/** + * Unrealized tree for mid-tier cohorts (AgeRange/MaxAge — profit/loss/net, no relative) + * @param {BasicRelativePattern} u + * @param {(name: string) => string} title + * @returns {PartialOptionsTree} + */ +function unrealizedTreeMid(u, title) { + return [ + unrealizedOverview(u.profit, u.loss, u.netPnl.usd, title), + { name: "Net P&L", title: title("Net Unrealized P&L"), bottom: [netBaseline(u.netPnl.usd, Unit.usd)] }, + { name: "NUPL", title: title("NUPL"), bottom: nuplSeries(u.nupl) }, + { name: "Profit", title: title("Unrealized Profit"), bottom: [line({ series: u.profit.usd, name: "Profit", color: colors.profit, unit: Unit.usd })] }, + { name: "Loss", title: title("Unrealized Loss"), bottom: [line({ series: u.loss.usd, name: "Loss", color: colors.loss, unit: Unit.usd })] }, + ]; } // ============================================================================ @@ -253,166 +232,67 @@ function nuplSeries(nupl) { } // ============================================================================ -// Realized P&L Builders — Full (All/STH/LTH) +// Realized P&L Helpers // ============================================================================ /** - * @param {RealizedPattern | LthRealizedPattern} r - * @param {(name: string) => string} title + * Flat metric folder: Compare + windows + Cumulative + Per Block + optional % of Realized Cap + * @param {Object} args + * @param {{ sum: Record, cumulative: { usd: AnySeriesPattern }, base: { usd: AnySeriesPattern } }} args.pattern + * @param {string} args.metricTitle + * @param {Color} args.color + * @param {(name: string) => string} args.title + * @param {{ percent: AnySeriesPattern, ratio: AnySeriesPattern }} [args.toRcap] * @returns {PartialOptionsTree} */ -function realizedPnlSumTreeFull(r, title) { +function realizedMetricFolder({ pattern, metricTitle, color, title, toRcap }) { return [ { - name: "USD", - title: title("Realized P&L"), - bottom: [ - dots({ series: r.profit.base.usd, name: "Profit", color: colors.profit, unit: Unit.usd }), - dots({ series: r.loss.negative, name: "Loss (Inverted)", color: colors.loss, unit: Unit.usd, defaultActive: false }), - dots({ series: r.loss.base.usd, name: "Loss", color: colors.loss, unit: Unit.usd, defaultActive: false }), - ], + name: "Compare", + title: title(`Realized ${metricTitle}`), + bottom: ROLLING_WINDOWS.map((w) => + line({ series: pattern.sum[w.key].usd, name: w.name, color: w.color, unit: Unit.usd }), + ), + }, + ...ROLLING_WINDOWS.map((w) => ({ + name: w.name, + title: title(`Realized ${metricTitle} (${w.title})`), + bottom: [line({ series: pattern.sum[w.key].usd, name: metricTitle, color, unit: Unit.usd })], + })), + { + name: "Cumulative", + title: title(`Realized ${metricTitle} (Total)`), + bottom: [line({ series: pattern.cumulative.usd, name: metricTitle, color, unit: Unit.usd })], }, { - name: "% of Realized Cap", - title: title("Realized P&L (% of Realized Cap)"), - bottom: [ - ...percentRatioBaseline({ pattern: r.profit.toRcap, name: "Profit", color: colors.profit }), - ...percentRatioBaseline({ pattern: r.loss.toRcap, name: "Loss", color: colors.loss }), - ], + name: "Per Block", + title: title(`Realized ${metricTitle} per Block`), + bottom: [dots({ series: pattern.base.usd, name: metricTitle, color, unit: Unit.usd })], }, + ...(toRcap ? [{ + name: "% of Realized Cap", + title: title(`Realized ${metricTitle} (% of Realized Cap)`), + bottom: percentRatioBaseline({ pattern: toRcap, name: metricTitle, color }), + }] : []), ]; } /** - * @param {RealizedPattern | LthRealizedPattern} r - * @param {(name: string) => string} title - * @returns {PartialOptionsTree} - */ -function realizedNetPnlSumTreeFull(r, title) { - return [ - { name: "USD", title: title("Net Realized P&L"), bottom: [dotsBaseline({ series: r.netPnl.base.usd, name: "Net", unit: Unit.usd })] }, - { - name: "% of Realized Cap", - title: title("Net Realized P&L (% of Realized Cap)"), - bottom: percentRatioBaseline({ pattern: r.netPnl.toRcap, name: "Net" }), - }, - ]; -} - -/** - * @param {RealizedPattern | LthRealizedPattern} r - * @param {(name: string) => string} title - * @returns {PartialOptionsTree} - */ -function realizedPnlCumulativeTreeFull(r, title) { - return [ - { - name: "USD", - title: title("Cumulative Realized P&L"), - bottom: [ - line({ series: r.profit.cumulative.usd, name: "Profit", color: colors.profit, unit: Unit.usd }), - line({ series: r.loss.cumulative.usd, name: "Loss", color: colors.loss, unit: Unit.usd }), - line({ series: r.loss.negative, name: "Loss (Inverted)", color: colors.loss, unit: Unit.usd, defaultActive: false }), - ], - }, - { - name: "% of Realized Cap", - title: title("Cumulative Realized P&L (% of Realized Cap)"), - bottom: [ - ...percentRatioBaseline({ pattern: r.profit.toRcap, name: "Profit", color: colors.profit }), - ...percentRatioBaseline({ pattern: r.loss.toRcap, name: "Loss", color: colors.loss }), - ], - }, - ]; -} - -/** - * Net realized P&L delta tree (absolute + rate across all rolling windows) - * @param {NetPnlFullPattern | NetPnlBasicPattern} netPnl - * @param {(name: string) => string} title + * Net P&L folder: Compare + windows + Cumulative + Per Block + optional % of Rcap + Change/ + * @param {Object} args + * @param {NetPnlFullPattern | NetPnlBasicPattern} args.netPnl + * @param {(name: string) => string} args.title + * @param {{ percent: AnySeriesPattern, ratio: AnySeriesPattern }} [args.toRcap] + * @param {PartialOptionsTree} [args.extraChange] - Additional change items (% of Mcap, % of Rcap) * @returns {PartialOptionsGroup} */ -function realizedNetPnlDeltaTree(netPnl, title) { +function realizedNetFolder({ netPnl, title, toRcap, extraChange = [] }) { return { - name: "Change", - tree: [ - { - name: "Absolute", - tree: [ - { - name: "Compare", - title: title("Net Realized P&L Change"), - bottom: ROLLING_WINDOWS.map((w) => - baseline({ series: netPnl.delta.absolute[w.key].usd, name: w.name, color: w.color, unit: Unit.usd }), - ), - }, - ...ROLLING_WINDOWS.map((w) => ({ - name: w.name, - title: title(`Net Realized P&L Change (${w.title})`), - bottom: [baseline({ series: netPnl.delta.absolute[w.key].usd, name: "Change", unit: Unit.usd })], - })), - ], - }, - { - name: "Rate", - tree: [ - { - name: "Compare", - title: title("Net Realized P&L Rate"), - bottom: ROLLING_WINDOWS.flatMap((w) => - percentRatio({ pattern: netPnl.delta.rate[w.key], name: w.name, color: w.color }), - ), - }, - ...ROLLING_WINDOWS.map((w) => ({ - name: w.name, - title: title(`Net Realized P&L Rate (${w.title})`), - bottom: percentRatioBaseline({ pattern: netPnl.delta.rate[w.key], name: "Rate" }), - })), - ], - }, - ], - }; -} - -/** - * Full realized delta tree (absolute + rate + rel to mcap/rcap) - * @param {RealizedPattern | LthRealizedPattern} r - * @param {(name: string) => string} title - * @returns {PartialOptionsGroup} - */ -function realizedNetPnlDeltaTreeFull(r, title) { - const base = realizedNetPnlDeltaTree(r.netPnl, title); - return { - ...base, - tree: [ - ...base.tree, - { - name: "% of Mcap", - title: title("Net Realized P&L Change (% of Mcap)"), - bottom: percentRatioBaseline({ pattern: r.netPnl.change1m.toMcap, name: "30d Change" }), - }, - { - name: "% of Realized Cap", - title: title("Net Realized P&L Change (% of Realized Cap)"), - bottom: percentRatioBaseline({ pattern: r.netPnl.change1m.toRcap, name: "30d Change" }), - }, - ], - }; -} - -/** - * Rolling net realized P&L tree (reusable by full and mid realized) - * @param {{ sum: { _24h: { usd: AnySeriesPattern }, _1w: { usd: AnySeriesPattern }, _1m: { usd: AnySeriesPattern }, _1y: { usd: AnySeriesPattern } } }} netPnl - * @param {(name: string) => string} title - * @returns {PartialOptionsGroup} - */ -function rollingNetRealizedTree(netPnl, title) { - return { - name: "Net", + name: "Net P&L", tree: [ { name: "Compare", - title: title("Rolling Net Realized P&L"), + title: title("Net Realized P&L"), bottom: ROLLING_WINDOWS.map((w) => baseline({ series: netPnl.sum[w.key].usd, name: w.name, color: w.color, unit: Unit.usd }), ), @@ -422,195 +302,100 @@ function rollingNetRealizedTree(netPnl, title) { title: title(`Net Realized P&L (${w.title})`), bottom: [baseline({ series: netPnl.sum[w.key].usd, name: "Net", unit: Unit.usd })], })), + { + name: "Cumulative", + title: title("Net Realized P&L (Total)"), + bottom: [baseline({ series: netPnl.cumulative.usd, name: "Net", unit: Unit.usd })], + }, + { + name: "Per Block", + title: title("Net Realized P&L per Block"), + bottom: [dotsBaseline({ series: netPnl.base.usd, name: "Net", unit: Unit.usd })], + }, + ...(toRcap ? [{ + name: "% of Realized Cap", + title: title("Net Realized P&L (% of Realized Cap)"), + bottom: percentRatioBaseline({ pattern: toRcap, name: "Net" }), + }] : []), + { + name: "Change", + tree: [ + { + name: "Absolute", + tree: [ + { + name: "Compare", + title: title("Net Realized P&L Change"), + bottom: ROLLING_WINDOWS.map((w) => + baseline({ series: netPnl.delta.absolute[w.key].usd, name: w.name, color: w.color, unit: Unit.usd }), + ), + }, + ...ROLLING_WINDOWS.map((w) => ({ + name: w.name, + title: title(`Net Realized P&L Change (${w.title})`), + bottom: [baseline({ series: netPnl.delta.absolute[w.key].usd, name: "Change", unit: Unit.usd })], + })), + ], + }, + { + name: "Rate", + tree: [ + { + name: "Compare", + title: title("Net Realized P&L Rate"), + bottom: ROLLING_WINDOWS.flatMap((w) => + percentRatio({ pattern: netPnl.delta.rate[w.key], name: w.name, color: w.color }), + ), + }, + ...ROLLING_WINDOWS.map((w) => ({ + name: w.name, + title: title(`Net Realized P&L Rate (${w.title})`), + bottom: percentRatioBaseline({ pattern: netPnl.delta.rate[w.key], name: "Rate" }), + })), + ], + }, + ...extraChange, + ], + }, ], }; } -/** - * Rolling realized with P/L and ratio (full realized only) - * @param {RealizedPattern | LthRealizedPattern} r - * @param {(name: string) => string} title - * @returns {PartialOptionsTree} - */ -function singleRollingRealizedTreeFull(r, title) { - return [ - { - name: "Profit", - tree: [ - { - name: "Compare", - title: title("Rolling Realized Profit"), - bottom: ROLLING_WINDOWS.map((w) => - line({ series: r.profit.sum[w.key].usd, name: w.name, color: w.color, unit: Unit.usd }), - ), - }, - ...ROLLING_WINDOWS.map((w) => ({ - name: w.name, - title: title(`Realized Profit (${w.title})`), - bottom: [line({ series: r.profit.sum[w.key].usd, name: "Profit", color: colors.profit, unit: Unit.usd })], - })), - ], - }, - { - name: "Loss", - tree: [ - { - name: "Compare", - title: title("Rolling Realized Loss"), - bottom: ROLLING_WINDOWS.map((w) => - line({ series: r.loss.sum[w.key].usd, name: w.name, color: w.color, unit: Unit.usd }), - ), - }, - ...ROLLING_WINDOWS.map((w) => ({ - name: w.name, - title: title(`Realized Loss (${w.title})`), - bottom: [line({ series: r.loss.sum[w.key].usd, name: "Loss", color: colors.loss, unit: Unit.usd })], - })), - ], - }, - rollingNetRealizedTree(r.netPnl, title), - { - name: "P/L Ratio", - tree: [ - { - name: "Compare", - title: title("Rolling Realized P/L Ratio"), - bottom: ROLLING_WINDOWS.map((w) => - baseline({ series: r.profitToLossRatio[w.key], name: w.name, color: w.color, unit: Unit.ratio, base: 1 }), - ), - }, - ...ROLLING_WINDOWS.map((w) => ({ - name: w.name, - title: title(`Realized P/L Ratio (${w.title})`), - bottom: [baseline({ series: r.profitToLossRatio[w.key], name: "P/L Ratio", unit: Unit.ratio, base: 1 })], - })), - ], - }, - ]; -} /** - * Rolling realized profit/loss sums (basic — no P/L ratio) - * @param {RealizedProfitLossPattern} profit - * @param {RealizedProfitLossPattern} loss - * @param {(name: string) => string} title - * @returns {PartialOptionsTree} + * Realized overview folder: one chart per window showing net + profit (dotted) + neg. loss (dotted) + loss (hidden) + gross (hidden) + * @param {Object} args + * @param {{ sum: Record }} args.profit + * @param {{ sum: Record, negative: { sum: Record } }} args.loss + * @param {{ sum: Record }} args.netPnl + * @param {{ sum: Record }} [args.grossPnl] + * @param {{ sum: Record }} [args.peakRegret] + * @param {(name: string) => string} args.title + * @returns {PartialOptionsGroup} */ -function singleRollingRealizedTreeBasic(profit, loss, title) { - return [ - { - name: "Profit", - tree: ROLLING_WINDOWS.map((w) => ({ - name: w.name, - title: title(`Realized Profit (${w.title})`), - bottom: [line({ series: profit.sum[w.key].usd, name: "Profit", color: colors.profit, unit: Unit.usd })], - })), - }, - { - name: "Loss", - tree: ROLLING_WINDOWS.map((w) => ({ - name: w.name, - title: title(`Realized Loss (${w.title})`), - bottom: [line({ series: loss.sum[w.key].usd, name: "Loss", color: colors.loss, unit: Unit.usd })], - })), - }, - ]; +function realizedOverviewFolder({ profit, loss, netPnl, grossPnl, peakRegret, title }) { + return { + name: "Overview", + tree: ROLLING_WINDOWS.map((w) => ({ + name: w.name, + title: title(`Realized P&L (${w.title})`), + bottom: [ + baseline({ series: netPnl.sum[w.key].usd, name: "Net P&L", unit: Unit.usd }), + dotted({ series: profit.sum[w.key].usd, name: "Profit", color: colors.profit, unit: Unit.usd }), + dotted({ series: loss.negative.sum[w.key], name: "Neg. Loss", color: colors.loss, unit: Unit.usd }), + dotted({ series: loss.sum[w.key].usd, name: "Loss", color: colors.loss, unit: Unit.usd, defaultActive: false }), + ...(grossPnl ? [dotted({ series: grossPnl.sum[w.key].usd, name: "Gross", color: colors.bitcoin, unit: Unit.usd, defaultActive: false })] : []), + ...(peakRegret ? [dotted({ series: peakRegret.sum[w.key].usd, name: "Peak Regret", color: colors.default, unit: Unit.usd, defaultActive: false })] : []), + priceLine({ unit: Unit.usd }), + ], + })), + }; } // ============================================================================ // Realized Subfolder Builders // ============================================================================ -/** - * Value Created/Destroyed tree for a single P&L side (profit or loss) - * @param {CountPattern} valueCreated - * @param {CountPattern} valueDestroyed - * @param {string} label - "Profit" or "Loss" - * @param {(name: string) => string} title - * @returns {PartialOptionsGroup} - */ -function realizedValueTree(valueCreated, valueDestroyed, label, title) { - return { - name: label, - tree: [ - { - name: "Rolling", - tree: [ - { - name: "Compare", - title: title(`${label} Value Created vs Destroyed`), - bottom: ROLLING_WINDOWS.flatMap((w) => [ - line({ series: valueCreated.sum[w.key], name: `Created (${w.name})`, color: w.color, unit: Unit.usd }), - line({ series: valueDestroyed.sum[w.key], name: `Destroyed (${w.name})`, color: w.color, unit: Unit.usd, style: 2 }), - ]), - }, - ...ROLLING_WINDOWS.map((w) => ({ - name: w.name, - title: title(`${label} Value (${w.title})`), - bottom: [ - line({ series: valueCreated.sum[w.key], name: "Created", color: colors.profit, unit: Unit.usd }), - line({ series: valueDestroyed.sum[w.key], name: "Destroyed", color: colors.loss, unit: Unit.usd }), - ], - })), - ], - }, - { - name: "Cumulative", - title: title(`Cumulative ${label} Value`), - bottom: [ - line({ series: valueCreated.cumulative, name: "Created", color: colors.profit, unit: Unit.usd }), - line({ series: valueDestroyed.cumulative, name: "Destroyed", color: colors.loss, unit: Unit.usd }), - ], - }, - ], - }; -} - -/** - * Investor price percentiles tree (pct1/2/5/95/98/99) - * @param {InvestorPercentilesPattern} percentiles - * @param {(name: string) => string} title - * @returns {PartialOptionsGroup} - */ -function investorPricePercentilesTree(percentiles, title) { - /** @type {readonly [InvestorPercentileEntry, string, Color][]} */ - const pcts = [ - [percentiles.pct99, "p99", colors.stat.max], - [percentiles.pct98, "p98", colors.stat.pct90], - [percentiles.pct95, "p95", colors.stat.pct75], - [percentiles.pct5, "p5", colors.stat.pct25], - [percentiles.pct2, "p2", colors.stat.pct10], - [percentiles.pct1, "p1", colors.stat.min], - ]; - - return { - name: "Percentiles", - tree: [ - { - name: "USD", - title: title("Investor Price Percentiles"), - bottom: pcts.map(([p, name, color]) => - line({ series: p.price.usd, name, color, unit: Unit.usd }), - ), - }, - { - name: "Sats", - title: title("Investor Price Percentiles (Sats)"), - bottom: pcts.map(([p, name, color]) => - line({ series: p.price.sats, name, color, unit: Unit.sats }), - ), - }, - { - name: "Ratio", - title: title("Investor Price Percentile Ratios"), - bottom: pcts.map(([p, name, color]) => - baseline({ series: p.ratio, name, color, unit: Unit.ratio }), - ), - }, - ], - }; -} - /** * Full realized subfolder (All/STH/LTH) * @param {RealizedPattern | LthRealizedPattern} r @@ -621,82 +406,73 @@ function realizedSubfolderFull(r, title) { return { name: "Realized", tree: [ - { name: "P&L", tree: realizedPnlSumTreeFull(r, title) }, - { name: "Net", tree: realizedNetPnlSumTreeFull(r, title) }, - realizedNetPnlDeltaTreeFull(r, title), - { - name: "Gross P&L", - tree: [ - { name: "Per Block", title: title("Gross Realized P&L"), bottom: [dots({ series: r.grossPnl.base.usd, name: "Gross P&L", color: colors.bitcoin, unit: Unit.usd })] }, + realizedOverviewFolder({ profit: r.profit, loss: r.loss, netPnl: r.netPnl, grossPnl: r.grossPnl, peakRegret: r.peakRegret, title }), + realizedNetFolder({ + netPnl: r.netPnl, + title, + toRcap: r.netPnl.toRcap, + extraChange: [ { - name: "Rolling", - tree: [ - { - name: "Compare", - title: title("Rolling Gross Realized P&L"), - bottom: ROLLING_WINDOWS.map((w) => - line({ series: r.grossPnl.sum[w.key].usd, name: w.name, color: w.color, unit: Unit.usd }), - ), - }, - ...ROLLING_WINDOWS.map((w) => ({ - name: w.name, - title: title(`Gross Realized P&L (${w.title})`), - bottom: [line({ series: r.grossPnl.sum[w.key].usd, name: "Gross P&L", color: colors.bitcoin, unit: Unit.usd })], - })), - ], + name: "% of Market Cap", + title: title("Net Realized P&L Change (% of Market Cap)"), + bottom: percentRatioBaseline({ pattern: r.netPnl.change1m.toMcap, name: "30d Change" }), + }, + { + name: "% of Realized Cap", + title: title("Net Realized P&L Change (% of Realized Cap)"), + bottom: percentRatioBaseline({ pattern: r.netPnl.change1m.toRcap, name: "30d Change" }), }, - { name: "Cumulative", title: title("Total Realized P&L"), bottom: [line({ series: r.grossPnl.cumulative.usd, name: "Total", unit: Unit.usd, color: colors.bitcoin })] }, ], - }, - { - name: "Value", - tree: [ - realizedValueTree(r.profit.valueCreated, r.profit.valueDestroyed, "Profit", title), - realizedValueTree(r.loss.valueCreated, r.loss.valueDestroyed, "Loss", title), - ], - }, + }), + { name: "Profit", tree: realizedMetricFolder({ pattern: r.profit, metricTitle: "Profit", color: colors.profit, title, toRcap: r.profit.toRcap }) }, + { name: "Loss", tree: realizedMetricFolder({ pattern: r.loss, metricTitle: "Loss", color: colors.loss, title, toRcap: r.loss.toRcap }) }, + { name: "Gross P&L", tree: realizedMetricFolder({ pattern: r.grossPnl, metricTitle: "Gross P&L", color: colors.bitcoin, title }) }, { name: "P/L Ratio", - title: title("Realized Profit/Loss Ratio"), - bottom: [baseline({ series: r.profitToLossRatio._1y, name: "P/L Ratio", unit: Unit.ratio, base: 1 })], + tree: [ + { + name: "Compare", + title: title("Realized P/L Ratio"), + bottom: ROLLING_WINDOWS.map((w) => + baseline({ series: r.profitToLossRatio[w.key], name: w.name, color: w.color, unit: Unit.ratio, base: 1 }), + ), + }, + ...ROLLING_WINDOWS.map((w) => ({ + name: w.name, + title: title(`Realized P/L Ratio (${w.title})`), + bottom: [baseline({ series: r.profitToLossRatio[w.key], name: "P/L Ratio", unit: Unit.ratio, base: 1 })], + })), + ], }, { name: "Peak Regret", - title: title("Realized Peak Regret"), - bottom: [line({ series: r.peakRegret.base, name: "Peak Regret", unit: Unit.usd })], - }, - { - name: "Investor Price", tree: [ - investorPricePercentilesTree(r.investor.price.percentiles, title), - ], - }, - { name: "Rolling", tree: singleRollingRealizedTreeFull(r, title) }, - { - name: "Cumulative", - tree: [ - { name: "P&L", tree: realizedPnlCumulativeTreeFull(r, title) }, { - name: "Net", - tree: [ - { name: "USD", title: title("Cumulative Net Realized P&L"), bottom: [baseline({ series: r.netPnl.cumulative.usd, name: "Net", unit: Unit.usd })] }, - { - name: "% of Realized Cap", - title: title("Cumulative Net P&L (% of Realized Cap)"), - bottom: percentRatioBaseline({ pattern: r.netPnl.toRcap, name: "Net" }), - }, - ], + name: "Compare", + title: title("Peak Regret"), + bottom: ROLLING_WINDOWS.map((w) => + line({ series: r.peakRegret.sum[w.key].usd, name: w.name, color: w.color, unit: Unit.usd }), + ), + }, + ...ROLLING_WINDOWS.map((w) => ({ + name: w.name, + title: title(`Peak Regret (${w.title})`), + bottom: [line({ series: r.peakRegret.sum[w.key].usd, name: "Peak Regret", unit: Unit.usd })], + })), + { + name: "Cumulative", + title: title("Peak Regret (Total)"), + bottom: [line({ series: r.peakRegret.cumulative.usd, name: "Peak Regret", unit: Unit.usd })], }, { - name: "Peak Regret", - tree: [ - { name: "USD", title: title("Cumulative Peak Regret"), bottom: [line({ series: r.peakRegret.cumulative, name: "Peak Regret", unit: Unit.usd })] }, - { - name: "% of Realized Cap", - title: title("Cumulative Peak Regret (% of Realized Cap)"), - bottom: percentRatioBaseline({ pattern: r.peakRegret.toRcap, name: "Peak Regret" }), - }, - ], + name: "Per Block", + title: title("Peak Regret per Block"), + bottom: [dots({ series: r.peakRegret.base.usd, name: "Peak Regret", unit: Unit.usd })], + }, + { + name: "% of Realized Cap", + title: title("Peak Regret (% of Realized Cap)"), + bottom: percentRatioBaseline({ pattern: r.peakRegret.toRcap, name: "Peak Regret" }), }, ], }, @@ -714,46 +490,10 @@ function realizedSubfolderMid(r, title) { return { name: "Realized", tree: [ - { - name: "P&L", - title: title("Realized P&L"), - bottom: [ - dots({ series: r.profit.base.usd, name: "Profit", color: colors.profit, unit: Unit.usd }), - dots({ series: r.loss.negative, name: "Loss (Inverted)", color: colors.loss, unit: Unit.usd, defaultActive: false }), - dots({ series: r.loss.base.usd, name: "Loss", color: colors.loss, unit: Unit.usd, defaultActive: false }), - ], - }, - { - name: "Net", - title: title("Net Realized P&L"), - bottom: [dotsBaseline({ series: r.netPnl.base.usd, name: "Net", unit: Unit.usd })], - }, - realizedNetPnlDeltaTree(r.netPnl, title), - { - name: "Rolling", - tree: [ - ...singleRollingRealizedTreeBasic(r.profit, r.loss, title), - rollingNetRealizedTree(r.netPnl, title), - ], - }, - { - name: "Cumulative", - tree: [ - { - name: "P&L", - title: title("Cumulative Realized P&L"), - bottom: [ - line({ series: r.profit.cumulative.usd, name: "Profit", color: colors.profit, unit: Unit.usd }), - line({ series: r.loss.cumulative.usd, name: "Loss", color: colors.loss, unit: Unit.usd }), - ], - }, - { - name: "Net", - title: title("Cumulative Net Realized P&L"), - bottom: [baseline({ series: r.netPnl.cumulative.usd, name: "Net", unit: Unit.usd })], - }, - ], - }, + realizedOverviewFolder({ profit: r.profit, loss: r.loss, netPnl: r.netPnl, title }), + realizedNetFolder({ netPnl: r.netPnl, title }), + { name: "Profit", tree: realizedMetricFolder({ pattern: r.profit, metricTitle: "Profit", color: colors.profit, title }) }, + { name: "Loss", tree: realizedMetricFolder({ pattern: r.loss, metricTitle: "Loss", color: colors.loss, title }) }, ], }; } @@ -768,23 +508,8 @@ function realizedSubfolderBasic(r, title) { return { name: "Realized", tree: [ - { - name: "P&L", - title: title("Realized P&L"), - bottom: [ - dots({ series: r.profit.base.usd, name: "Profit", color: colors.profit, unit: Unit.usd }), - dots({ series: r.loss.base.usd, name: "Loss", color: colors.loss, unit: Unit.usd, defaultActive: false }), - ], - }, - { name: "Rolling", tree: singleRollingRealizedTreeBasic(r.profit, r.loss, title) }, - { - name: "Cumulative", - title: title("Cumulative Realized P&L"), - bottom: [ - line({ series: r.profit.cumulative.usd, name: "Profit", color: colors.profit, unit: Unit.usd }), - line({ series: r.loss.cumulative.usd, name: "Loss", color: colors.loss, unit: Unit.usd }), - ], - }, + { name: "Profit", tree: realizedMetricFolder({ pattern: r.profit, metricTitle: "Profit", color: colors.profit, title }) }, + { name: "Loss", tree: realizedMetricFolder({ pattern: r.loss, metricTitle: "Loss", color: colors.loss, title }) }, ], }; } @@ -828,20 +553,9 @@ export function createProfitabilitySectionWithProfitLoss({ cohort, title }) { { name: "Unrealized", tree: [ - { - name: "P&L", - tree: [ - { - name: "USD", - title: title("Unrealized P&L"), - bottom: [ - ...pnlLines({ profit: u.profit.usd, loss: u.loss.usd, negLoss: u.loss.negative }, Unit.usd), - priceLine({ unit: Unit.usd, defaultActive: false }), - ], - }, - ], - }, { name: "NUPL", title: title("NUPL"), bottom: nuplSeries(u.nupl) }, + { name: "Profit", title: title("Unrealized Profit"), bottom: [line({ series: u.profit.usd, name: "Profit", color: colors.profit, unit: Unit.usd })] }, + { name: "Loss", title: title("Unrealized Loss"), bottom: [line({ series: u.loss.usd, name: "Loss", color: colors.loss, unit: Unit.usd })] }, ], }, realizedSubfolderBasic(cohort.tree.realized, title), @@ -860,14 +574,7 @@ export function createProfitabilitySectionAll({ cohort, title }) { return { name: "Profitability", tree: [ - { - name: "Unrealized", - tree: [ - { name: "P&L", tree: unrealizedPnlTreeAll(u, title) }, - { name: "Net P&L", tree: netUnrealizedTreeAll(u, title) }, - { name: "NUPL", title: title("NUPL"), bottom: nuplSeries(u.nupl) }, - ], - }, + { name: "Unrealized", tree: unrealizedTreeAll(u, title) }, realizedSubfolderFull(r, title), { name: "Invested Capital", @@ -890,14 +597,7 @@ export function createProfitabilitySectionFull({ cohort, title }) { return { name: "Profitability", tree: [ - { - name: "Unrealized", - tree: [ - { name: "P&L", tree: unrealizedPnlTreeFull(u, title) }, - { name: "Net P&L", tree: netUnrealizedTreeFull(u, title) }, - { name: "NUPL", title: title("NUPL"), bottom: nuplSeries(u.nupl) }, - ], - }, + { name: "Unrealized", tree: unrealizedTreeFull(u, title) }, realizedSubfolderFull(r, title), { name: "Invested Capital", @@ -941,14 +641,7 @@ export function createProfitabilitySectionLongTerm({ cohort, title }) { return { name: "Profitability", tree: [ - { - name: "Unrealized", - tree: [ - { name: "P&L", tree: unrealizedPnlTreeLongTerm(u, title) }, - { name: "Net P&L", tree: netUnrealizedTreeFull(u, title) }, - { name: "NUPL", title: title("NUPL"), bottom: nuplSeries(u.nupl) }, - ], - }, + { name: "Unrealized", tree: unrealizedTreeLongTerm(u, title) }, realizedSubfolderFull(r, title), { name: "Invested Capital", @@ -971,14 +664,7 @@ export function createProfitabilitySectionWithInvestedCapitalPct({ cohort, title return { name: "Profitability", tree: [ - { - name: "Unrealized", - tree: [ - { name: "P&L", tree: unrealizedPnlTreeMid(u, title) }, - { name: "Net P&L", title: title("Net Unrealized P&L"), bottom: netUnrealizedMid(u) }, - { name: "NUPL", title: title("NUPL"), bottom: nuplSeries(u.nupl) }, - ], - }, + { name: "Unrealized", tree: unrealizedTreeMid(u, title) }, realizedSubfolderMid(r, title), ], }; @@ -1018,20 +704,9 @@ export function createProfitabilitySectionAddress({ cohort, title }) { { name: "Unrealized", tree: [ - { - name: "P&L", - tree: [ - { - name: "USD", - title: title("Unrealized P&L"), - bottom: [ - ...pnlLines({ profit: u.profit.usd, loss: u.loss.usd, negLoss: u.loss.negative }, Unit.usd), - priceLine({ unit: Unit.usd, defaultActive: false }), - ], - }, - ], - }, { name: "NUPL", title: title("NUPL"), bottom: nuplSeries(u.nupl) }, + { name: "Profit", title: title("Unrealized Profit"), bottom: [line({ series: u.profit.usd, name: "Profit", color: colors.profit, unit: Unit.usd })] }, + { name: "Loss", title: title("Unrealized Loss"), bottom: [line({ series: u.loss.usd, name: "Loss", color: colors.loss, unit: Unit.usd })] }, ], }, realizedSubfolderBasic(cohort.tree.realized, title), @@ -1298,7 +973,7 @@ function groupedRealizedSubfolderFull(list, all, title) { } /** - * Grouped unrealized P&L (USD only — for all cohorts that at least have nupl) + * Grouped NUPL chart * @template {{ name: string, color: Color, tree: { unrealized: { nupl: NuplPattern } } }} T * @template {{ name: string, color: Color, tree: { unrealized: { nupl: NuplPattern } } }} A * @param {readonly T[]} list @@ -1319,52 +994,14 @@ function groupedNuplCharts(list, all, title) { } /** - * Grouped unrealized for full cohorts with relToMcap - * @param {readonly (CohortFull | CohortLongTerm)[]} list + * Grouped unrealized: Net → NUPL → Profit → Loss (no relative) + * @param {readonly (CohortAgeRange | CohortWithAdjusted)[]} list * @param {CohortAll} all * @param {(name: string) => string} title * @returns {PartialOptionsTree} */ -function groupedPnlChartsWithMarketCap(list, all, title) { +function groupedUnrealizedMid(list, all, title) { return [ - { - name: "Profit", - tree: [ - { - name: "USD", - title: title("Unrealized Profit"), - bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => - line({ series: tree.unrealized.profit.usd, name, color, unit: Unit.usd }), - ), - }, - { - name: "% of Mcap", - title: title("Unrealized Profit (% of Mcap)"), - bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => - percentRatio({ pattern: tree.unrealized.profit.toMcap, name, color }), - ), - }, - ], - }, - { - name: "Loss", - tree: [ - { - name: "USD", - title: title("Unrealized Loss"), - bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => - line({ series: tree.unrealized.loss.usd, name, color, unit: Unit.usd }), - ), - }, - { - name: "% of Mcap", - title: title("Unrealized Loss (% of Mcap)"), - bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => - percentRatio({ pattern: tree.unrealized.loss.toMcap, name, color }), - ), - }, - ], - }, { name: "Net P&L", title: title("Net Unrealized P&L"), @@ -1372,18 +1009,7 @@ function groupedPnlChartsWithMarketCap(list, all, title) { baseline({ series: tree.unrealized.netPnl.usd, name, color, unit: Unit.usd }), ), }, - ]; -} - -/** - * Grouped unrealized for AgeRange/MaxAge (profit/loss without relToMcap) - * @param {readonly (CohortAgeRange | CohortWithAdjusted)[]} list - * @param {CohortAll} all - * @param {(name: string) => string} title - * @returns {PartialOptionsTree} - */ -function groupedPnlChartsWithOwnMarketCap(list, all, title) { - return [ + ...groupedNuplCharts(list, all, title), { name: "Profit", title: title("Unrealized Profit"), @@ -1398,107 +1024,112 @@ function groupedPnlChartsWithOwnMarketCap(list, all, title) { line({ series: tree.unrealized.loss.usd, name, color, unit: Unit.usd }), ), }, + ]; +} + +/** + * Grouped unrealized: Net → NUPL → Profit → Loss → Relative(Market Cap) + * @param {readonly (CohortFull | CohortLongTerm)[]} list + * @param {CohortAll} all + * @param {(name: string) => string} title + * @returns {PartialOptionsTree} + */ +function groupedUnrealizedWithMarketCap(list, all, title) { + return [ + ...groupedUnrealizedMid(list, all, title), { - name: "Net P&L", - title: title("Net Unrealized P&L"), - bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => - baseline({ series: tree.unrealized.netPnl.usd, name, color, unit: Unit.usd }), - ), + name: "% of Market Cap", + tree: [ + { + name: "Profit", + title: title("Unrealized Profit (% of Market Cap)"), + bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => + percentRatio({ pattern: tree.unrealized.profit.toMcap, name, color }), + ), + }, + { + name: "Loss", + title: title("Unrealized Loss (% of Market Cap)"), + bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => + percentRatio({ pattern: tree.unrealized.loss.toMcap, name, color }), + ), + }, + ], }, ]; } /** - * Grouped unrealized for LongTerm (profit/loss with relToOwnMcap + relToOwnGross) + * Grouped unrealized for LongTerm: Net → NUPL → Profit → Loss → Relative(Own P&L, Market Cap, Own Mcap) * @param {readonly CohortLongTerm[]} list * @param {CohortAll} all * @param {(name: string) => string} title * @returns {PartialOptionsTree} */ -function groupedPnlChartsLongTerm(list, all, title) { +function groupedUnrealizedLongTerm(list, all, title) { return [ + ...groupedUnrealizedMid(list, all, title), { - name: "Profit", + name: "Relative", tree: [ { - name: "USD", - title: title("Unrealized Profit"), - bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => - line({ series: tree.unrealized.profit.usd, name, color, unit: Unit.usd }), - ), + name: "Own P&L", + tree: [ + { + name: "Net", + title: title("Net Unrealized P&L (% of Own P&L)"), + bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => + percentRatioBaseline({ pattern: tree.unrealized.netPnl.toOwnGrossPnl, name, color }), + ), + }, + { + name: "Profit", + title: title("Unrealized Profit (% of Own P&L)"), + bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => + percentRatio({ pattern: tree.unrealized.profit.toOwnGrossPnl, name, color }), + ), + }, + { + name: "Loss", + title: title("Unrealized Loss (% of Own P&L)"), + bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => + percentRatio({ pattern: tree.unrealized.loss.toOwnGrossPnl, name, color }), + ), + }, + ], }, { - name: "% of Own Mcap", - title: title("Unrealized Profit (% of Own Mcap)"), - bottom: flatMapCohorts(list, ({ name, color, tree }) => - percentRatio({ pattern: tree.unrealized.profit.toOwnMcap, name, color }), - ), - }, - { - name: "% of Own P&L", - title: title("Unrealized Profit (% of Own P&L)"), - bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => - percentRatio({ pattern: tree.unrealized.profit.toOwnGrossPnl, name, color }), - ), - }, - ], - }, - { - name: "Loss", - tree: [ - { - name: "USD", - title: title("Unrealized Loss"), - bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => - line({ series: tree.unrealized.loss.usd, name, color, unit: Unit.usd }), - ), - }, - { - name: "% of Mcap", - title: title("Unrealized Loss (% of Mcap)"), + name: "Market Cap", + title: title("Unrealized Loss (% of Market Cap)"), bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => percentRatio({ pattern: tree.unrealized.loss.toMcap, name, color }), ), }, { - name: "% of Own Mcap", - title: title("Unrealized Loss (% of Own Mcap)"), - bottom: flatMapCohorts(list, ({ name, color, tree }) => - percentRatio({ pattern: tree.unrealized.loss.toOwnMcap, name, color }), - ), - }, - { - name: "% of Own P&L", - title: title("Unrealized Loss (% of Own P&L)"), - bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => - percentRatio({ pattern: tree.unrealized.loss.toOwnGrossPnl, name, color }), - ), - }, - ], - }, - { - name: "Net P&L", - tree: [ - { - name: "USD", - title: title("Net Unrealized P&L"), - bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => - baseline({ series: tree.unrealized.netPnl.usd, name, color, unit: Unit.usd }), - ), - }, - { - name: "% of Own Mcap", - title: title("Net Unrealized P&L (% of Own Mcap)"), - bottom: flatMapCohorts(list, ({ name, color, tree }) => - percentRatioBaseline({ pattern: tree.unrealized.netPnl.toOwnMcap, name, color }), - ), - }, - { - name: "% of Own P&L", - title: title("Net Unrealized P&L (% of Own P&L)"), - bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => - percentRatioBaseline({ pattern: tree.unrealized.netPnl.toOwnGrossPnl, name, color }), - ), + name: "Own Market Cap", + tree: [ + { + name: "Net", + title: title("Net Unrealized P&L (% of Own Market Cap)"), + bottom: flatMapCohorts(list, ({ name, color, tree }) => + percentRatioBaseline({ pattern: tree.unrealized.netPnl.toOwnMcap, name, color }), + ), + }, + { + name: "Profit", + title: title("Unrealized Profit (% of Own Market Cap)"), + bottom: flatMapCohorts(list, ({ name, color, tree }) => + percentRatio({ pattern: tree.unrealized.profit.toOwnMcap, name, color }), + ), + }, + { + name: "Loss", + title: title("Unrealized Loss (% of Own Market Cap)"), + bottom: flatMapCohorts(list, ({ name, color, tree }) => + percentRatio({ pattern: tree.unrealized.loss.toOwnMcap, name, color }), + ), + }, + ], }, ], }, @@ -1573,6 +1204,7 @@ export function createGroupedProfitabilitySectionWithProfitLoss({ list, all, tit { name: "Unrealized", tree: [ + ...groupedNuplCharts(list, all, title), { name: "Profit", title: title("Unrealized Profit"), @@ -1587,7 +1219,6 @@ export function createGroupedProfitabilitySectionWithProfitLoss({ list, all, tit line({ series: tree.unrealized.loss.usd, name, color, unit: Unit.usd }), ), }, - ...groupedNuplCharts(list, all, title), ], }, groupedRealizedSubfolder(list, all, title), @@ -1619,13 +1250,7 @@ export function createGroupedProfitabilitySectionWithInvestedCapitalPct({ list, return { name: "Profitability", tree: [ - { - name: "Unrealized", - tree: [ - ...groupedPnlChartsWithOwnMarketCap(list, all, title), - ...groupedNuplCharts(list, all, title), - ], - }, + { name: "Unrealized", tree: groupedUnrealizedMid(list, all, title) }, groupedRealizedSubfolder(list, all, title), ], }; @@ -1640,13 +1265,7 @@ export function createGroupedProfitabilitySectionWithNupl({ list, all, title }) return { name: "Profitability", tree: [ - { - name: "Unrealized", - tree: [ - ...groupedPnlChartsWithMarketCap(list, all, title), - ...groupedNuplCharts(list, all, title), - ], - }, + { name: "Unrealized", tree: groupedUnrealizedWithMarketCap(list, all, title) }, groupedRealizedSubfolder(list, all, title), ], }; @@ -1661,13 +1280,7 @@ export function createGroupedProfitabilitySectionLongTerm({ list, all, title }) return { name: "Profitability", tree: [ - { - name: "Unrealized", - tree: [ - ...groupedPnlChartsLongTerm(list, all, title), - ...groupedNuplCharts(list, all, title), - ], - }, + { name: "Unrealized", tree: groupedUnrealizedLongTerm(list, all, title) }, groupedRealizedSubfolderFull(list, all, title), groupedSentiment(list, all, title), ], diff --git a/website/scripts/options/series.js b/website/scripts/options/series.js index 029f6849f..0882bd46d 100644 --- a/website/scripts/options/series.js +++ b/website/scripts/options/series.js @@ -190,6 +190,15 @@ export function dotted(args) { return line(_args); } +/** + * @param {Omit[0], 'style'>} args + */ +export function dashed(args) { + const _args = /** @type {Parameters[0]} */ (args); + _args.style = 2; + return line(_args); +} + /** * @param {Omit[0], 'style'>} args */