From 9e3fe4e5578e09daedd5648659be9e002b07e97a Mon Sep 17 00:00:00 2001 From: nym21 Date: Tue, 3 Feb 2026 00:08:37 +0100 Subject: [PATCH] website: snapshot --- crates/brk_client/src/lib.rs | 10 +- modules/brk-client/index.js | 44 +- packages/brk_client/brk_client/__init__.py | 58 +- website/scripts/chart/colors.js | 39 ++ website/scripts/options/cointime.js | 568 ++++++++++-------- website/scripts/options/investing.js | 301 ++++++++-- website/scripts/options/market.js | 481 +++++++++++++++ website/scripts/options/market/averages.js | 202 ------- website/scripts/options/market/bands.js | 85 --- website/scripts/options/market/index.js | 175 ------ website/scripts/options/market/momentum.js | 111 ---- website/scripts/options/market/onchain.js | 81 --- website/scripts/options/market/performance.js | 142 ----- website/scripts/options/market/volatility.js | 119 ---- website/scripts/options/network.js | 6 +- website/scripts/options/partial.js | 2 +- website/scripts/options/shared.js | 5 +- website/scripts/options/unused.js | 37 +- 18 files changed, 1171 insertions(+), 1295 deletions(-) create mode 100644 website/scripts/options/market.js delete mode 100644 website/scripts/options/market/averages.js delete mode 100644 website/scripts/options/market/bands.js delete mode 100644 website/scripts/options/market/index.js delete mode 100644 website/scripts/options/market/momentum.js delete mode 100644 website/scripts/options/market/onchain.js delete mode 100644 website/scripts/options/market/performance.js delete mode 100644 website/scripts/options/market/volatility.js diff --git a/crates/brk_client/src/lib.rs b/crates/brk_client/src/lib.rs index 2498af893..9f2bfa9ef 100644 --- a/crates/brk_client/src/lib.rs +++ b/crates/brk_client/src/lib.rs @@ -829,7 +829,7 @@ impl MetricPattern for MetricPattern30 { fn get(&self pub struct MetricPattern31By { client: Arc, name: Arc, _marker: std::marker::PhantomData } impl MetricPattern31By { - pub fn loadedaddressindex(&self) -> MetricEndpointBuilder { _ep(&self.client, &self.name, Index::FundedAddressIndex) } + pub fn fundedaddressindex(&self) -> MetricEndpointBuilder { _ep(&self.client, &self.name, Index::FundedAddressIndex) } } pub struct MetricPattern31 { name: Arc, pub by: MetricPattern31By } @@ -5112,7 +5112,7 @@ pub struct MetricsTree_Distribution { pub total_addr_count: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern, pub new_addr_count: MetricsTree_Distribution_NewAddrCount, pub growth_rate: MetricsTree_Distribution_GrowthRate, - pub loadedaddressindex: MetricPattern31, + pub fundedaddressindex: MetricPattern31, pub emptyaddressindex: MetricPattern32, } @@ -5130,7 +5130,7 @@ impl MetricsTree_Distribution { total_addr_count: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern::new(client.clone(), "total_addr_count".to_string()), new_addr_count: MetricsTree_Distribution_NewAddrCount::new(client.clone(), format!("{base_path}_new_addr_count")), growth_rate: MetricsTree_Distribution_GrowthRate::new(client.clone(), format!("{base_path}_growth_rate")), - loadedaddressindex: MetricPattern31::new(client.clone(), "loadedaddressindex".to_string()), + fundedaddressindex: MetricPattern31::new(client.clone(), "fundedaddressindex".to_string()), emptyaddressindex: MetricPattern32::new(client.clone(), "emptyaddressindex".to_string()), } } @@ -5165,14 +5165,14 @@ impl MetricsTree_Distribution_AnyAddressIndexes { /// Metrics tree node. pub struct MetricsTree_Distribution_AddressesData { - pub loaded: MetricPattern31, + pub funded: MetricPattern31, pub empty: MetricPattern32, } impl MetricsTree_Distribution_AddressesData { pub fn new(client: Arc, base_path: String) -> Self { Self { - loaded: MetricPattern31::new(client.clone(), "loadedaddressdata".to_string()), + funded: MetricPattern31::new(client.clone(), "fundedaddressdata".to_string()), empty: MetricPattern32::new(client.clone(), "emptyaddressdata".to_string()), } } diff --git a/modules/brk-client/index.js b/modules/brk-client/index.js index d5afb60c8..aff00aa38 100644 --- a/modules/brk-client/index.js +++ b/modules/brk-client/index.js @@ -63,7 +63,7 @@ * @property {?string=} witnessProgram - Witness program in hex */ /** - * Unified index for any address type (loaded or empty) + * Unified index for any address type (funded or empty) * * @typedef {TypeIndex} AnyAddressIndex */ @@ -276,6 +276,19 @@ * * @typedef {("json"|"csv")} Format */ +/** + * Data for a funded (non-empty) address with current balance + * + * @typedef {Object} FundedAddressData + * @property {number} txCount - Total transaction count + * @property {number} fundedTxoCount - Number of transaction outputs funded to this address + * @property {number} spentTxoCount - Number of transaction outputs spent by this address + * @property {Sats} received - Satoshis received by this address + * @property {Sats} sent - Satoshis sent by this address + * @property {CentsSats} realizedCapRaw - The realized capitalization: Σ(price × sats) + * @property {CentsSquaredSats} investorCapRaw - The investor capitalization: Σ(price² × sats) + */ +/** @typedef {TypeIndex} FundedAddressIndex */ /** @typedef {number} HalvingEpoch */ /** * A single hashrate data point. @@ -326,7 +339,7 @@ * Aggregation dimension for querying metrics. Includes time-based (date, week, month, year), * block-based (height, txindex), and address/output type indexes. * - * @typedef {("dateindex"|"decadeindex"|"difficultyepoch"|"emptyoutputindex"|"halvingepoch"|"height"|"txinindex"|"monthindex"|"opreturnindex"|"txoutindex"|"p2aaddressindex"|"p2msoutputindex"|"p2pk33addressindex"|"p2pk65addressindex"|"p2pkhaddressindex"|"p2shaddressindex"|"p2traddressindex"|"p2wpkhaddressindex"|"p2wshaddressindex"|"quarterindex"|"semesterindex"|"txindex"|"unknownoutputindex"|"weekindex"|"yearindex"|"loadedaddressindex"|"emptyaddressindex"|"pairoutputindex")} Index + * @typedef {("dateindex"|"decadeindex"|"difficultyepoch"|"emptyoutputindex"|"halvingepoch"|"height"|"txinindex"|"monthindex"|"opreturnindex"|"txoutindex"|"p2aaddressindex"|"p2msoutputindex"|"p2pk33addressindex"|"p2pk65addressindex"|"p2pkhaddressindex"|"p2shaddressindex"|"p2traddressindex"|"p2wpkhaddressindex"|"p2wshaddressindex"|"quarterindex"|"semesterindex"|"txindex"|"unknownoutputindex"|"weekindex"|"yearindex"|"fundedaddressindex"|"emptyaddressindex"|"pairoutputindex")} Index */ /** * Information about an available index and its query aliases @@ -344,19 +357,6 @@ * @typedef {Object} LimitParam * @property {Limit=} limit */ -/** - * Data for a loaded (non-empty) address with current balance - * - * @typedef {Object} LoadedAddressData - * @property {number} txCount - Total transaction count - * @property {number} fundedTxoCount - Number of transaction outputs funded to this address - * @property {number} spentTxoCount - Number of transaction outputs spent by this address - * @property {Sats} received - Satoshis received by this address - * @property {Sats} sent - Satoshis sent by this address - * @property {CentsSats} realizedCapRaw - The realized capitalization: Σ(price × sats) - * @property {CentsSquaredSats} investorCapRaw - The investor capitalization: Σ(price² × sats) - */ -/** @typedef {TypeIndex} LoadedAddressIndex */ /** * Lowest price value for a time period * @@ -1247,7 +1247,7 @@ const _i27 = /** @type {const} */ (["txindex"]); const _i28 = /** @type {const} */ (["unknownoutputindex"]); const _i29 = /** @type {const} */ (["weekindex"]); const _i30 = /** @type {const} */ (["yearindex"]); -const _i31 = /** @type {const} */ (["loadedaddressindex"]); +const _i31 = /** @type {const} */ (["fundedaddressindex"]); const _i32 = /** @type {const} */ (["emptyaddressindex"]); /** @@ -1365,7 +1365,7 @@ function createMetricPattern29(client, name) { return _mp(client, name, _i29); } /** @template T @typedef {{ name: string, by: { readonly yearindex: MetricEndpointBuilder }, indexes: () => readonly Index[], get: (index: Index) => MetricEndpointBuilder|undefined }} MetricPattern30 */ /** @template T @param {BrkClientBase} client @param {string} name @returns {MetricPattern30} */ function createMetricPattern30(client, name) { return _mp(client, name, _i30); } -/** @template T @typedef {{ name: string, by: { readonly loadedaddressindex: MetricEndpointBuilder }, indexes: () => readonly Index[], get: (index: Index) => MetricEndpointBuilder|undefined }} MetricPattern31 */ +/** @template T @typedef {{ name: string, by: { readonly fundedaddressindex: MetricEndpointBuilder }, indexes: () => readonly Index[], get: (index: Index) => MetricEndpointBuilder|undefined }} MetricPattern31 */ /** @template T @param {BrkClientBase} client @param {string} name @returns {MetricPattern31} */ function createMetricPattern31(client, name) { return _mp(client, name, _i31); } /** @template T @typedef {{ name: string, by: { readonly emptyaddressindex: MetricEndpointBuilder }, indexes: () => readonly Index[], get: (index: Index) => MetricEndpointBuilder|undefined }} MetricPattern32 */ @@ -4481,7 +4481,7 @@ function createUtxoPattern(client, acc) { * @property {AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern} totalAddrCount * @property {MetricsTree_Distribution_NewAddrCount} newAddrCount * @property {MetricsTree_Distribution_GrowthRate} growthRate - * @property {MetricPattern31} loadedaddressindex + * @property {MetricPattern31} fundedaddressindex * @property {MetricPattern32} emptyaddressindex */ @@ -4499,7 +4499,7 @@ function createUtxoPattern(client, acc) { /** * @typedef {Object} MetricsTree_Distribution_AddressesData - * @property {MetricPattern31} loaded + * @property {MetricPattern31} funded * @property {MetricPattern32} empty */ @@ -4903,7 +4903,7 @@ class BrkClient extends BrkClientBase { "unknownoutputindex", "weekindex", "yearindex", - "loadedaddressindex", + "fundedaddressindex", "emptyaddressindex", "pairoutputindex" ]); @@ -6625,7 +6625,7 @@ class BrkClient extends BrkClientBase { p2wsh: createMetricPattern24(this, 'anyaddressindex'), }, addressesData: { - loaded: createMetricPattern31(this, 'loadedaddressdata'), + funded: createMetricPattern31(this, 'fundedaddressdata'), empty: createMetricPattern32(this, 'emptyaddressdata'), }, utxoCohorts: { @@ -6909,7 +6909,7 @@ class BrkClient extends BrkClientBase { p2tr: createAverageBaseMaxMedianMinPct10Pct25Pct75Pct90Pattern(this, 'p2tr_growth_rate'), p2a: createAverageBaseMaxMedianMinPct10Pct25Pct75Pct90Pattern(this, 'p2a_growth_rate'), }, - loadedaddressindex: createMetricPattern31(this, 'loadedaddressindex'), + fundedaddressindex: createMetricPattern31(this, 'fundedaddressindex'), emptyaddressindex: createMetricPattern32(this, 'emptyaddressindex'), }, supply: { diff --git a/packages/brk_client/brk_client/__init__.py b/packages/brk_client/brk_client/__init__.py index ff6a405c6..969cfada7 100644 --- a/packages/brk_client/brk_client/__init__.py +++ b/packages/brk_client/brk_client/__init__.py @@ -25,7 +25,7 @@ Sats = int TypeIndex = int # Transaction ID (hash) Txid = str -# Unified index for any address type (loaded or empty) +# Unified index for any address type (funded or empty) AnyAddressIndex = TypeIndex # Bitcoin amount as floating point (1 BTC = 100,000,000 satoshis) Bitcoin = float @@ -67,12 +67,12 @@ EmptyAddressIndex = TypeIndex EmptyOutputIndex = TypeIndex # Fee rate in sats/vB FeeRate = float +FundedAddressIndex = TypeIndex HalvingEpoch = int # Hex-encoded string Hex = str # Highest price value for a time period High = CentsUnsigned -LoadedAddressIndex = TypeIndex # Lowest price value for a time period Low = CentsUnsigned # Virtual size in vbytes (weight / 4, rounded up) @@ -157,7 +157,7 @@ WeekIndex = int YearIndex = int # Aggregation dimension for querying metrics. Includes time-based (date, week, month, year), # block-based (height, txindex), and address/output type indexes. -Index = Literal["dateindex", "decadeindex", "difficultyepoch", "emptyoutputindex", "halvingepoch", "height", "txinindex", "monthindex", "opreturnindex", "txoutindex", "p2aaddressindex", "p2msoutputindex", "p2pk33addressindex", "p2pk65addressindex", "p2pkhaddressindex", "p2shaddressindex", "p2traddressindex", "p2wpkhaddressindex", "p2wshaddressindex", "quarterindex", "semesterindex", "txindex", "unknownoutputindex", "weekindex", "yearindex", "loadedaddressindex", "emptyaddressindex", "pairoutputindex"] +Index = Literal["dateindex", "decadeindex", "difficultyepoch", "emptyoutputindex", "halvingepoch", "height", "txinindex", "monthindex", "opreturnindex", "txoutindex", "p2aaddressindex", "p2msoutputindex", "p2pk33addressindex", "p2pk65addressindex", "p2pkhaddressindex", "p2shaddressindex", "p2traddressindex", "p2wpkhaddressindex", "p2wshaddressindex", "quarterindex", "semesterindex", "txindex", "unknownoutputindex", "weekindex", "yearindex", "fundedaddressindex", "emptyaddressindex", "pairoutputindex"] # Hierarchical tree node for organizing metrics into categories TreeNode = Union[dict[str, "TreeNode"], "MetricLeafWithSchema"] class AddressChainStats(TypedDict): @@ -455,6 +455,27 @@ class EmptyAddressData(TypedDict): funded_txo_count: int transfered: Sats +class FundedAddressData(TypedDict): + """ + Data for a funded (non-empty) address with current balance + + Attributes: + tx_count: Total transaction count + funded_txo_count: Number of transaction outputs funded to this address + spent_txo_count: Number of transaction outputs spent by this address + received: Satoshis received by this address + sent: Satoshis sent by this address + realized_cap_raw: The realized capitalization: Σ(price × sats) + investor_cap_raw: The investor capitalization: Σ(price² × sats) + """ + tx_count: int + funded_txo_count: int + spent_txo_count: int + received: Sats + sent: Sats + realized_cap_raw: CentsSats + investor_cap_raw: CentsSquaredSats + class HashrateEntry(TypedDict): """ A single hashrate data point. @@ -512,27 +533,6 @@ class IndexInfo(TypedDict): class LimitParam(TypedDict): limit: Limit -class LoadedAddressData(TypedDict): - """ - Data for a loaded (non-empty) address with current balance - - Attributes: - tx_count: Total transaction count - funded_txo_count: Number of transaction outputs funded to this address - spent_txo_count: Number of transaction outputs spent by this address - received: Satoshis received by this address - sent: Satoshis sent by this address - realized_cap_raw: The realized capitalization: Σ(price × sats) - investor_cap_raw: The investor capitalization: Σ(price² × sats) - """ - tx_count: int - funded_txo_count: int - spent_txo_count: int - received: Sats - sent: Sats - realized_cap_raw: CentsSats - investor_cap_raw: CentsSquaredSats - class MempoolBlock(TypedDict): """ Block info in a mempool.space like format for fee estimation. @@ -1406,7 +1406,7 @@ _i27 = ('txindex',) _i28 = ('unknownoutputindex',) _i29 = ('weekindex',) _i30 = ('yearindex',) -_i31 = ('loadedaddressindex',) +_i31 = ('fundedaddressindex',) _i32 = ('emptyaddressindex',) def _ep(c: BrkClientBase, n: str, i: Index) -> MetricEndpointBuilder[Any]: @@ -1805,7 +1805,7 @@ class MetricPattern30(Generic[T]): class _MetricPattern31By(Generic[T]): def __init__(self, c: BrkClientBase, n: str): self._c, self._n = c, n - def loadedaddressindex(self) -> MetricEndpointBuilder[T]: return _ep(self._c, self._n, 'loadedaddressindex') + def fundedaddressindex(self) -> MetricEndpointBuilder[T]: return _ep(self._c, self._n, 'fundedaddressindex') class MetricPattern31(Generic[T]): by: _MetricPattern31By[T] @@ -3905,7 +3905,7 @@ class MetricsTree_Distribution_AddressesData: """Metrics tree node.""" def __init__(self, client: BrkClientBase, base_path: str = ''): - self.loaded: MetricPattern31[LoadedAddressData] = MetricPattern31(client, 'loadedaddressdata') + self.funded: MetricPattern31[FundedAddressData] = MetricPattern31(client, 'fundedaddressdata') self.empty: MetricPattern32[EmptyAddressData] = MetricPattern32(client, 'emptyaddressdata') class MetricsTree_Distribution_UtxoCohorts_All_Relative: @@ -4284,7 +4284,7 @@ class MetricsTree_Distribution: self.total_addr_count: AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern = AllP2aP2pk33P2pk65P2pkhP2shP2trP2wpkhP2wshPattern(client, 'total_addr_count') self.new_addr_count: MetricsTree_Distribution_NewAddrCount = MetricsTree_Distribution_NewAddrCount(client) self.growth_rate: MetricsTree_Distribution_GrowthRate = MetricsTree_Distribution_GrowthRate(client) - self.loadedaddressindex: MetricPattern31[LoadedAddressIndex] = MetricPattern31(client, 'loadedaddressindex') + self.fundedaddressindex: MetricPattern31[FundedAddressIndex] = MetricPattern31(client, 'fundedaddressindex') self.emptyaddressindex: MetricPattern32[EmptyAddressIndex] = MetricPattern32(client, 'emptyaddressindex') class MetricsTree_Supply_Circulating: @@ -4370,7 +4370,7 @@ class BrkClient(BrkClientBase): "unknownoutputindex", "weekindex", "yearindex", - "loadedaddressindex", + "fundedaddressindex", "emptyaddressindex", "pairoutputindex" ] diff --git a/website/scripts/chart/colors.js b/website/scripts/chart/colors.js index 8f12450f5..2d64a7552 100644 --- a/website/scripts/chart/colors.js +++ b/website/scripts/chart/colors.js @@ -177,6 +177,45 @@ export const colors = { _2016: red, _2015: pink, }, + + /** Returns/lookback colors by period */ + returns: { + _1d: red, + _1w: orange, + _1m: yellow, + _3m: lime, + _6m: green, + _1y: teal, + _2y: cyan, + _3y: sky, + _4y: blue, + _5y: indigo, + _6y: violet, + _8y: purple, + _10y: fuchsia, + }, + + /** Moving average colors by period */ + ma: { + _1w: red, + _8d: orange, + _12d: amber, + _13d: amber, + _21d: yellow, + _26d: lime, + _1m: lime, + _34d: green, + _55d: emerald, + _89d: teal, + _111d: cyan, + _144d: sky, + _200d: blue, + _350d: indigo, + _1y: violet, + _2y: purple, + _200w: fuchsia, + _4y: pink, + }, }; /** diff --git a/website/scripts/options/cointime.js b/website/scripts/options/cointime.js index 68dabfcf5..791fad05a 100644 --- a/website/scripts/options/cointime.js +++ b/website/scripts/options/cointime.js @@ -1,5 +1,5 @@ import { Unit } from "../utils/units.js"; -import { line, price } from "./series.js"; +import { dots, line, price } from "./series.js"; import { satsBtcUsd, createPriceRatioCharts } from "./shared.js"; /** @@ -21,160 +21,193 @@ export function createCointimeSection(ctx) { } = cointime; const { all } = distribution.utxoCohorts; - // Cointime prices data - const cointimePrices = [ + // Reference lines for cap comparisons + const capReferenceLines = /** @type {const} */ ([ + { metric: supply.marketCap, name: "Market", color: colors.default }, + { + metric: all.realized.realizedCap, + name: "Realized", + color: colors.orange, + }, + ]); + + const prices = /** @type {const} */ ([ { pricePattern: pricing.trueMarketMean, ratio: pricing.trueMarketMeanRatio, name: "True Market Mean", - title: "True Market Mean", color: colors.blue, }, { pricePattern: pricing.vaultedPrice, ratio: pricing.vaultedPriceRatio, name: "Vaulted", - title: "Vaulted Price", color: colors.lime, }, { pricePattern: pricing.activePrice, ratio: pricing.activePriceRatio, name: "Active", - title: "Active Price", color: colors.rose, }, { pricePattern: pricing.cointimePrice, ratio: pricing.cointimePriceRatio, name: "Cointime", - title: "Cointime Price", color: colors.yellow, }, - ]; + ]); - // Cointime capitalizations data - const cointimeCapitalizations = [ + const caps = /** @type {const} */ ([ + { metric: cap.vaultedCap, name: "Vaulted", color: colors.lime }, + { metric: cap.activeCap, name: "Active", color: colors.rose }, + { metric: cap.cointimeCap, name: "Cointime", color: colors.yellow }, + { metric: cap.investorCap, name: "Investor", color: colors.fuchsia }, + { metric: cap.thermoCap, name: "Thermo", color: colors.emerald }, + ]); + + const supplyBreakdown = /** @type {const} */ ([ + { pattern: all.supply.total, name: "Total", color: colors.orange }, { - metric: cap.vaultedCap, + pattern: cointimeSupply.vaultedSupply, name: "Vaulted", - title: "Vaulted Cap", color: colors.lime, }, { - metric: cap.activeCap, + pattern: cointimeSupply.activeSupply, name: "Active", - title: "Active Cap", color: colors.rose, }, + ]); + + const coinblocks = /** @type {const} */ ([ { - metric: cap.cointimeCap, - name: "Cointime", - title: "Cointime Cap", - color: colors.yellow, + pattern: all.activity.coinblocksDestroyed, + name: "Destroyed", + title: "Coinblocks Destroyed", + color: colors.red, }, { - metric: cap.investorCap, - name: "Investor", - title: "Investor Cap", - color: colors.fuchsia, + pattern: activity.coinblocksCreated, + name: "Created", + title: "Coinblocks Created", + color: colors.orange, }, { - metric: cap.thermoCap, - name: "Thermo", - title: "Thermo Cap", - color: colors.emerald, + pattern: activity.coinblocksStored, + name: "Stored", + title: "Coinblocks Stored", + color: colors.green, }, - ]; + ]); + + // Colors aligned with coinblocks: Destroyed=red, Created=orange, Stored=green + const cointimeValues = /** @type {const} */ ([ + { + pattern: value.cointimeValueCreated, + name: "Created", + title: "Cointime Value Created", + color: colors.orange, + }, + { + pattern: value.cointimeValueDestroyed, + name: "Destroyed", + title: "Cointime Value Destroyed", + color: colors.red, + }, + { + pattern: value.cointimeValueStored, + name: "Stored", + title: "Cointime Value Stored", + color: colors.green, + }, + ]); + + const vocdd = /** @type {const} */ ({ + pattern: value.vocdd, + name: "VOCDD", + title: "Value of Coin Days Destroyed", + color: colors.purple, + }); return { name: "Cointime", tree: [ - // Prices + // Prices - the core pricing models { name: "Prices", tree: [ { name: "Compare", title: "Cointime Prices", - top: cointimePrices.map(({ pricePattern, name, color }) => - price({ metric: pricePattern, name, color }), - ), + top: [ + price({ metric: all.realized.realizedPrice, name: "Realized", color: colors.orange }), + price({ metric: all.realized.investorPrice, name: "Investor", color: colors.fuchsia }), + ...prices.map(({ pricePattern, name, color }) => + price({ metric: pricePattern, name, color }), + ), + ], }, - ...cointimePrices.map(({ pricePattern, ratio, name, color, title }) => ({ + ...prices.map(({ pricePattern, ratio, name, color }) => ({ name, tree: createPriceRatioCharts(ctx, { - context: title, + context: `${name} Price`, legend: name, pricePattern, ratio, color, + priceReferences: [price({ metric: all.realized.realizedPrice, name: "Realized", color: colors.orange, defaultActive: false })], }), })), ], }, - // Capitalization + // Caps - market capitalizations from different models { - name: "Capitalization", + name: "Caps", tree: [ { name: "Compare", title: "Cointime Caps", bottom: [ - line({ - metric: supply.marketCap, - name: "Market", - color: colors.default, - unit: Unit.usd, - }), - line({ - metric: all.realized.realizedCap, - name: "Realized", - color: colors.orange, - unit: Unit.usd, - }), - ...cointimeCapitalizations.map(({ metric, name, color }) => + ...capReferenceLines.map(({ metric, name, color }) => + line({ metric, name, color, unit: Unit.usd }), + ), + ...caps.map(({ metric, name, color }) => line({ metric, name, color, unit: Unit.usd }), ), ], }, - ...cointimeCapitalizations.map(({ metric, name, color, title }) => ({ + ...caps.map(({ metric, name, color }) => ({ name, - title, + title: `${name} Cap`, bottom: [ line({ metric, name, color, unit: Unit.usd }), - line({ - metric: supply.marketCap, - name: "Market", - color: colors.default, - unit: Unit.usd, - }), - line({ - metric: all.realized.realizedCap, - name: "Realized", - color: colors.orange, - unit: Unit.usd, - }), + ...capReferenceLines.map((ref) => + line({ + metric: ref.metric, + name: ref.name, + color: ref.color, + unit: Unit.usd, + }), + ), ], })), ], }, - // Supply + // Supply - active vs vaulted breakdown { name: "Supply", - title: "Cointime Supply", - bottom: [ - ...satsBtcUsd({ pattern: all.supply.total, name: "All", color: colors.orange }), - ...satsBtcUsd({ pattern: cointimeSupply.vaultedSupply, name: "Vaulted", color: colors.lime }), - ...satsBtcUsd({ pattern: cointimeSupply.activeSupply, name: "Active", color: colors.rose }), - ], + title: "Active vs Vaulted Supply", + bottom: supplyBreakdown.flatMap(({ pattern, name, color }) => + satsBtcUsd({ pattern, name, color }), + ), }, - // Liveliness & Vaultedness + // Liveliness - the foundational cointime ratios { - name: "Liveliness & Vaultedness", + name: "Activity", title: "Liveliness & Vaultedness", bottom: [ line({ @@ -191,73 +224,196 @@ export function createCointimeSection(ctx) { }), line({ metric: activity.activityToVaultednessRatio, - name: "Liveliness / Vaultedness", + name: "L/V Ratio", color: colors.purple, unit: Unit.ratio, + defaultActive: false, }), ], }, - // Coinblocks + // Coinblocks - created, destroyed, stored { name: "Coinblocks", - title: "Coinblocks", - bottom: [ - // Destroyed comes from the all cohort's activity - line({ - metric: all.activity.coinblocksDestroyed.sum, - name: "Destroyed", - color: colors.red, - unit: Unit.coinblocks, - }), - line({ - metric: all.activity.coinblocksDestroyed.cumulative, - name: "Cumulative Destroyed", - color: colors.red, - defaultActive: false, - unit: Unit.coinblocks, - }), - // Created and stored from cointime - line({ - metric: activity.coinblocksCreated.sum, - name: "Created", - color: colors.orange, - unit: Unit.coinblocks, - }), - line({ - metric: activity.coinblocksCreated.cumulative, - name: "Cumulative Created", - color: colors.orange, - defaultActive: false, - unit: Unit.coinblocks, - }), - line({ - metric: activity.coinblocksStored.sum, - name: "Stored", - color: colors.green, - unit: Unit.coinblocks, - }), - line({ - metric: activity.coinblocksStored.cumulative, - name: "Cumulative Stored", - color: colors.green, - defaultActive: false, - unit: Unit.coinblocks, - }), + tree: [ + { + name: "Compare", + tree: [ + { + name: "Sum", + title: "Coinblocks", + bottom: coinblocks.map(({ pattern, name, color }) => + line({ + metric: pattern.sum, + name, + color, + unit: Unit.coinblocks, + }), + ), + }, + { + name: "Cumulative", + title: "Coinblocks (Total)", + bottom: coinblocks.map(({ pattern, name, color }) => + line({ + metric: pattern.cumulative, + name, + color, + unit: Unit.coinblocks, + }), + ), + }, + ], + }, + ...coinblocks.map(({ pattern, name, title, color }) => ({ + name, + tree: [ + { + name: "Sum", + title, + bottom: [ + line({ + metric: pattern.sum, + name, + color, + unit: Unit.coinblocks, + }), + ], + }, + { + name: "Cumulative", + title: `${title} (Total)`, + bottom: [ + line({ + metric: pattern.cumulative, + name, + color, + unit: Unit.coinblocks, + }), + ], + }, + ], + })), ], }, - // Reserve Risk + // Value - cointime value flows { - name: "Reserve Risk", + name: "Value", tree: [ { - name: "Ratio", + name: "Compare", + tree: [ + { + name: "Sum", + title: "Cointime Value", + bottom: [ + ...cointimeValues.map(({ pattern, name, color }) => + line({ metric: pattern.sum, name, color, unit: Unit.usd }), + ), + line({ + metric: vocdd.pattern.sum, + name: vocdd.name, + color: vocdd.color, + unit: Unit.usd, + }), + ], + }, + { + name: "Cumulative", + title: "Cointime Value (Total)", + bottom: [ + ...cointimeValues.map(({ pattern, name, color }) => + line({ + metric: pattern.cumulative, + name, + color, + unit: Unit.usd, + }), + ), + line({ + metric: vocdd.pattern.cumulative, + name: vocdd.name, + color: vocdd.color, + unit: Unit.usd, + }), + ], + }, + ], + }, + ...cointimeValues.map(({ pattern, name, title, color }) => ({ + name, + tree: [ + { + name: "Sum", + title, + bottom: [ + line({ metric: pattern.sum, name, color, unit: Unit.usd }), + ], + }, + { + name: "Cumulative", + title: `${title} (Total)`, + bottom: [ + line({ + metric: pattern.cumulative, + name, + color, + unit: Unit.usd, + }), + ], + }, + ], + })), + { + name: vocdd.name, + tree: [ + { + name: "Sum", + title: vocdd.title, + bottom: [ + line({ + metric: vocdd.pattern.sum, + name: vocdd.name, + color: vocdd.color, + unit: Unit.usd, + }), + line({ + metric: reserveRisk.vocdd365dSma, + name: "365d SMA", + color: colors.cyan, + unit: Unit.usd, + }), + ], + }, + { + name: "Cumulative", + title: `${vocdd.title} (Total)`, + bottom: [ + line({ + metric: vocdd.pattern.cumulative, + name: vocdd.name, + color: vocdd.color, + unit: Unit.usd, + }), + ], + }, + ], + }, + ], + }, + + // Indicators - derived decision metrics + { + name: "Indicators", + tree: [ + { + name: "Reserve Risk", title: "Reserve Risk", bottom: [ line({ metric: reserveRisk.reserveRisk, - name: "Reserve Risk", + name: "Ratio", color: colors.orange, unit: Unit.ratio, }), @@ -269,162 +425,76 @@ export function createCointimeSection(ctx) { bottom: [ line({ metric: reserveRisk.hodlBank, - name: "HODL Bank", + name: "Value", color: colors.blue, - unit: Unit.ratio, - }), - ], - }, - { - name: "VOCDD 365d SMA", - title: "VOCDD 365d SMA", - bottom: [ - line({ - metric: reserveRisk.vocdd365dSma, - name: "VOCDD 365d SMA", - color: colors.purple, - unit: Unit.ratio, + unit: Unit.usd, }), ], }, ], }, - // Cointime Value + // Cointime-Adjusted - comparing base vs adjusted metrics { - name: "Value", + name: "Cointime-Adjusted", tree: [ - { - name: "Created", - title: "Cointime Value Created", - bottom: [ - line({ - metric: value.cointimeValueCreated.sum, - name: "Created", - color: colors.green, - unit: Unit.usd, - }), - line({ - metric: value.cointimeValueCreated.cumulative, - name: "Cumulative", - color: colors.green, - unit: Unit.usd, - defaultActive: false, - }), - ], - }, - { - name: "Destroyed", - title: "Cointime Value Destroyed", - bottom: [ - line({ - metric: value.cointimeValueDestroyed.sum, - name: "Destroyed", - color: colors.red, - unit: Unit.usd, - }), - line({ - metric: value.cointimeValueDestroyed.cumulative, - name: "Cumulative", - color: colors.red, - unit: Unit.usd, - defaultActive: false, - }), - ], - }, - { - name: "Stored", - title: "Cointime Value Stored", - bottom: [ - line({ - metric: value.cointimeValueStored.sum, - name: "Stored", - color: colors.blue, - unit: Unit.usd, - }), - line({ - metric: value.cointimeValueStored.cumulative, - name: "Cumulative", - color: colors.blue, - unit: Unit.usd, - defaultActive: false, - }), - ], - }, - { - name: "VOCDD", - title: "VOCDD (Value of Coin Days Destroyed)", - bottom: [ - line({ - metric: value.vocdd.sum, - name: "VOCDD", - color: colors.orange, - unit: Unit.usd, - }), - line({ - metric: value.vocdd.cumulative, - name: "Cumulative", - color: colors.orange, - unit: Unit.usd, - defaultActive: false, - }), - ], - }, - ], - }, - - // Adjusted metrics - { - name: "Adjusted", - tree: [ - // Inflation { name: "Inflation", - title: "Adjusted Inflation", + title: "Cointime-Adjusted Inflation", bottom: [ - line({ + dots({ metric: supply.inflation, name: "Base", color: colors.orange, unit: Unit.percentage, }), - line({ + dots({ metric: adjusted.cointimeAdjInflationRate, - name: "Adjusted", + name: "Cointime-Adjusted", color: colors.purple, unit: Unit.percentage, }), ], }, - // Velocity { name: "Velocity", - title: "Adjusted Velocity", - bottom: [ - line({ - metric: supply.velocity.btc, + tree: [ + { name: "BTC", - color: colors.orange, - unit: Unit.ratio, - }), - line({ - metric: adjusted.cointimeAdjTxBtcVelocity, - name: "Adj. BTC", - color: colors.red, - unit: Unit.ratio, - }), - line({ - metric: supply.velocity.usd, + title: "Cointime-Adjusted BTC Velocity", + bottom: [ + line({ + metric: supply.velocity.btc, + name: "Base", + color: colors.orange, + unit: Unit.ratio, + }), + line({ + metric: adjusted.cointimeAdjTxBtcVelocity, + name: "Cointime-Adjusted", + color: colors.red, + unit: Unit.ratio, + }), + ], + }, + { name: "USD", - color: colors.emerald, - unit: Unit.ratio, - }), - line({ - metric: adjusted.cointimeAdjTxUsdVelocity, - name: "Adj. USD", - color: colors.lime, - unit: Unit.ratio, - }), + title: "Cointime-Adjusted USD Velocity", + bottom: [ + line({ + metric: supply.velocity.usd, + name: "Base", + color: colors.emerald, + unit: Unit.ratio, + }), + line({ + metric: adjusted.cointimeAdjTxUsdVelocity, + name: "Cointime-Adjusted", + color: colors.lime, + unit: Unit.ratio, + }), + ], + }, ], }, ], diff --git a/website/scripts/options/investing.js b/website/scripts/options/investing.js index 58cb63afc..38c564c75 100644 --- a/website/scripts/options/investing.js +++ b/website/scripts/options/investing.js @@ -5,8 +5,22 @@ import { line, baseline, price, dotted } from "./series.js"; import { satsBtcUsd } from "./shared.js"; import { periodIdToName } from "./utils.js"; -const SHORT_PERIODS = /** @type {const} */ (["_1w", "_1m", "_3m", "_6m", "_1y"]); -const LONG_PERIODS = /** @type {const} */ (["_2y", "_3y", "_4y", "_5y", "_6y", "_8y", "_10y"]); +const SHORT_PERIODS = /** @type {const} */ ([ + "_1w", + "_1m", + "_3m", + "_6m", + "_1y", +]); +const LONG_PERIODS = /** @type {const} */ ([ + "_2y", + "_3y", + "_4y", + "_5y", + "_6y", + "_8y", + "_10y", +]); /** @typedef {typeof SHORT_PERIODS[number]} ShortPeriodKey */ /** @typedef {typeof LONG_PERIODS[number]} LongPeriodKey */ @@ -20,7 +34,9 @@ const LONG_PERIODS = /** @type {const} */ (["_2y", "_3y", "_4y", "_5y", "_6y", " */ const withCagr = (entry, cagr) => ({ ...entry, cagr }); -const YEARS_2020S = /** @type {const} */ ([2026, 2025, 2024, 2023, 2022, 2021, 2020]); +const YEARS_2020S = /** @type {const} */ ([ + 2026, 2025, 2024, 2023, 2022, 2021, 2020, +]); const YEARS_2010S = /** @type {const} */ ([2019, 2018, 2017, 2016, 2015]); /** @param {AllPeriodKey} key */ @@ -142,7 +158,12 @@ function createCompareFolder(context, items) { title: `Returns: ${context}`, top: topPane, bottom: items.map(({ name, color, returns }) => - baseline({ metric: returns, name, color: [color, color], unit: Unit.percentage }), + baseline({ + metric: returns, + name, + color: [color, color], + unit: Unit.percentage, + }), ), }, createProfitabilityFolder(context, items), @@ -165,23 +186,51 @@ function createCompareFolder(context, items) { * @param {object[]} returnsBottom - Bottom pane items for returns chart */ function createSingleEntryTree(colors, item, returnsBottom) { - const { name, titlePrefix = name, color, costBasis, daysInProfit, daysInLoss, stack } = item; + const { + name, + titlePrefix = name, + color, + costBasis, + daysInProfit, + daysInLoss, + stack, + } = item; const top = [price({ metric: costBasis, name: "Cost Basis", color })]; return { name, tree: [ { name: "Cost Basis", title: `Cost Basis: ${titlePrefix}`, top }, - { name: "Returns", title: `Returns: ${titlePrefix}`, top, bottom: returnsBottom }, + { + name: "Returns", + title: `Returns: ${titlePrefix}`, + top, + bottom: returnsBottom, + }, { name: "Profitability", title: `Profitability: ${titlePrefix}`, top, bottom: [ - line({ metric: daysInProfit, name: "Days in Profit", color: colors.green, unit: Unit.days }), - line({ metric: daysInLoss, name: "Days in Loss", color: colors.red, unit: Unit.days }), + line({ + metric: daysInProfit, + name: "Days in Profit", + color: colors.green, + unit: Unit.days, + }), + line({ + metric: daysInLoss, + name: "Days in Loss", + color: colors.red, + unit: Unit.days, + }), ], }, - { name: "Accumulated", title: `Accumulated Value: ${titlePrefix}`, top, bottom: satsBtcUsd({ pattern: stack, name: "Value" }) }, + { + name: "Accumulated", + title: `Accumulated Value: ${titlePrefix}`, + top, + bottom: satsBtcUsd({ pattern: stack, name: "Value" }), + }, ], }; } @@ -195,8 +244,20 @@ function createShortSingleEntry(colors, item) { const { returns, minReturn, maxReturn } = item; return createSingleEntryTree(colors, item, [ baseline({ metric: returns, name: "Current", unit: Unit.percentage }), - dotted({ metric: maxReturn, name: "Max", color: colors.green, unit: Unit.percentage, defaultActive: false }), - dotted({ metric: minReturn, name: "Min", color: colors.red, unit: Unit.percentage, defaultActive: false }), + dotted({ + metric: maxReturn, + name: "Max", + color: colors.green, + unit: Unit.percentage, + defaultActive: false, + }), + dotted({ + metric: minReturn, + name: "Min", + color: colors.red, + unit: Unit.percentage, + defaultActive: false, + }), ]); } @@ -210,8 +271,20 @@ function createLongSingleEntry(colors, item) { return createSingleEntryTree(colors, item, [ baseline({ metric: returns, name: "Current", unit: Unit.percentage }), baseline({ metric: cagr, name: "CAGR", unit: Unit.cagr }), - dotted({ metric: maxReturn, name: "Max", color: colors.green, unit: Unit.percentage, defaultActive: false }), - dotted({ metric: minReturn, name: "Min", color: colors.red, unit: Unit.percentage, defaultActive: false }), + dotted({ + metric: maxReturn, + name: "Max", + color: colors.green, + unit: Unit.percentage, + defaultActive: false, + }), + dotted({ + metric: minReturn, + name: "Min", + color: colors.red, + unit: Unit.percentage, + defaultActive: false, + }), ]); } @@ -228,7 +301,11 @@ export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) { /** @param {AllPeriodKey} key */ const topPane = (key) => [ - price({ metric: dca.periodAveragePrice[key], name: "DCA", color: colors.green }), + price({ + metric: dca.periodAveragePrice[key], + name: "DCA", + color: colors.green, + }), price({ metric: lookback[key], name: "Lump Sum", color: colors.orange }), ]; @@ -246,8 +323,17 @@ export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) { title: `Max Return: ${name} DCA vs Lump Sum`, top: topPane(key), bottom: [ - baseline({ metric: dca.periodMaxReturn[key], name: "DCA", unit: Unit.percentage }), - baseline({ metric: dca.periodLumpSumMaxReturn[key], name: "Lump Sum", color: [colors.cyan, colors.orange], unit: Unit.percentage }), + baseline({ + metric: dca.periodMaxReturn[key], + name: "DCA", + unit: Unit.percentage, + }), + baseline({ + metric: dca.periodLumpSumMaxReturn[key], + name: "Lump Sum", + color: [colors.cyan, colors.orange], + unit: Unit.percentage, + }), ], }, { @@ -255,8 +341,17 @@ export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) { title: `Min Return: ${name} DCA vs Lump Sum`, top: topPane(key), bottom: [ - baseline({ metric: dca.periodMinReturn[key], name: "DCA", unit: Unit.percentage }), - baseline({ metric: dca.periodLumpSumMinReturn[key], name: "Lump Sum", color: [colors.cyan, colors.orange], unit: Unit.percentage }), + baseline({ + metric: dca.periodMinReturn[key], + name: "DCA", + unit: Unit.percentage, + }), + baseline({ + metric: dca.periodLumpSumMinReturn[key], + name: "Lump Sum", + color: [colors.cyan, colors.orange], + unit: Unit.percentage, + }), ], }, ]; @@ -270,8 +365,17 @@ export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) { title: `Returns: ${name} DCA vs Lump Sum`, top: topPane(key), bottom: [ - baseline({ metric: dca.periodReturns[key], name: "DCA", unit: Unit.percentage }), - baseline({ metric: dca.periodLumpSumReturns[key], name: "Lump Sum", color: [colors.cyan, colors.orange], unit: Unit.percentage }), + baseline({ + metric: dca.periodReturns[key], + name: "DCA", + unit: Unit.percentage, + }), + baseline({ + metric: dca.periodLumpSumReturns[key], + name: "Lump Sum", + color: [colors.cyan, colors.orange], + unit: Unit.percentage, + }), ], }, ...returnsMinMax(name, key), @@ -287,10 +391,28 @@ export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) { title: `Returns: ${name} DCA vs Lump Sum`, top: topPane(key), bottom: [ - baseline({ metric: dca.periodReturns[key], name: "DCA", unit: Unit.percentage }), - baseline({ metric: dca.periodLumpSumReturns[key], name: "Lump Sum", color: [colors.cyan, colors.orange], unit: Unit.percentage }), - baseline({ metric: dca.periodCagr[key], name: "DCA CAGR", unit: Unit.cagr }), - baseline({ metric: returns.cagr[key], name: "Lump Sum CAGR", color: [colors.cyan, colors.orange], unit: Unit.cagr }), + baseline({ + metric: dca.periodReturns[key], + name: "DCA", + unit: Unit.percentage, + }), + baseline({ + metric: dca.periodLumpSumReturns[key], + name: "Lump Sum", + color: [colors.cyan, colors.orange], + unit: Unit.percentage, + }), + baseline({ + metric: dca.periodCagr[key], + name: "DCA", + unit: Unit.cagr, + }), + baseline({ + metric: returns.cagr[key], + name: "Lump Sum", + color: [colors.cyan, colors.orange], + unit: Unit.cagr, + }), ], }, ...returnsMinMax(name, key), @@ -306,8 +428,18 @@ export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) { title: `Days in Profit: ${name} DCA vs Lump Sum`, top: topPane(key), bottom: [ - line({ metric: dca.periodDaysInProfit[key], name: "DCA", color: colors.green, unit: Unit.days }), - line({ metric: dca.periodLumpSumDaysInProfit[key], name: "Lump Sum", color: colors.orange, unit: Unit.days }), + line({ + metric: dca.periodDaysInProfit[key], + name: "DCA", + color: colors.green, + unit: Unit.days, + }), + line({ + metric: dca.periodLumpSumDaysInProfit[key], + name: "Lump Sum", + color: colors.orange, + unit: Unit.days, + }), ], }, { @@ -315,8 +447,18 @@ export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) { title: `Days in Loss: ${name} DCA vs Lump Sum`, top: topPane(key), bottom: [ - line({ metric: dca.periodDaysInLoss[key], name: "DCA", color: colors.green, unit: Unit.days }), - line({ metric: dca.periodLumpSumDaysInLoss[key], name: "Lump Sum", color: colors.orange, unit: Unit.days }), + line({ + metric: dca.periodDaysInLoss[key], + name: "DCA", + color: colors.green, + unit: Unit.days, + }), + line({ + metric: dca.periodLumpSumDaysInLoss[key], + name: "Lump Sum", + color: colors.orange, + unit: Unit.days, + }), ], }, ], @@ -328,8 +470,16 @@ export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) { title: `Accumulated Value ($100/day): ${name} DCA vs Lump Sum`, top: topPane(key), bottom: [ - ...satsBtcUsd({ pattern: dca.periodStack[key], name: "DCA", color: colors.green }), - ...satsBtcUsd({ pattern: dca.periodLumpSumStack[key], name: "Lump Sum", color: colors.orange }), + ...satsBtcUsd({ + pattern: dca.periodStack[key], + name: "DCA", + color: colors.green, + }), + ...satsBtcUsd({ + pattern: dca.periodLumpSumStack[key], + name: "Lump Sum", + color: colors.orange, + }), ], }); @@ -338,7 +488,12 @@ export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) { const name = periodName(key); return { name, - tree: [costBasisChart(name, key), shortReturnsFolder(name, key), profitabilityFolder(name, key), stackChart(name, key)], + tree: [ + costBasisChart(name, key), + shortReturnsFolder(name, key), + profitabilityFolder(name, key), + stackChart(name, key), + ], }; }; @@ -347,7 +502,12 @@ export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) { const name = periodName(key); return { name, - tree: [costBasisChart(name, key), longReturnsFolder(name, key), profitabilityFolder(name, key), stackChart(name, key)], + tree: [ + costBasisChart(name, key), + longReturnsFolder(name, key), + profitabilityFolder(name, key), + stackChart(name, key), + ], }; }; @@ -355,8 +515,16 @@ export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) { name: "DCA vs Lump Sum", title: "Compare Investment Strategies", tree: [ - { name: "Short Term", title: "Up to 1 Year", tree: SHORT_PERIODS.map(createShortPeriodEntry) }, - { name: "Long Term", title: "2+ Years", tree: LONG_PERIODS.map(createLongPeriodEntry) }, + { + name: "Short Term", + title: "Up to 1 Year", + tree: SHORT_PERIODS.map(createShortPeriodEntry), + }, + { + name: "Long Term", + title: "2+ Years", + tree: LONG_PERIODS.map(createLongPeriodEntry), + }, ], }; } @@ -380,26 +548,41 @@ function createPeriodSection(ctx, { dca, lookback, returns }) { color: colors.dcaPeriods[key], costBasis: isLumpSum ? lookback[key] : dca.periodAveragePrice[key], returns: isLumpSum ? dca.periodLumpSumReturns[key] : dca.periodReturns[key], - minReturn: isLumpSum ? dca.periodLumpSumMinReturn[key] : dca.periodMinReturn[key], - maxReturn: isLumpSum ? dca.periodLumpSumMaxReturn[key] : dca.periodMaxReturn[key], - daysInProfit: isLumpSum ? dca.periodLumpSumDaysInProfit[key] : dca.periodDaysInProfit[key], - daysInLoss: isLumpSum ? dca.periodLumpSumDaysInLoss[key] : dca.periodDaysInLoss[key], + minReturn: isLumpSum + ? dca.periodLumpSumMinReturn[key] + : dca.periodMinReturn[key], + maxReturn: isLumpSum + ? dca.periodLumpSumMaxReturn[key] + : dca.periodMaxReturn[key], + daysInProfit: isLumpSum + ? dca.periodLumpSumDaysInProfit[key] + : dca.periodDaysInProfit[key], + daysInLoss: isLumpSum + ? dca.periodLumpSumDaysInLoss[key] + : dca.periodDaysInLoss[key], stack: isLumpSum ? dca.periodLumpSumStack[key] : dca.periodStack[key], }); /** @param {LongPeriodKey} key @returns {LongEntryItem} */ - const buildLongEntry = (key) => withCagr( - buildBaseEntry(key), - isLumpSum ? returns.cagr[key] : dca.periodCagr[key], - ); + const buildLongEntry = (key) => + withCagr( + buildBaseEntry(key), + isLumpSum ? returns.cagr[key] : dca.periodCagr[key], + ); /** @param {BaseEntryItem} entry */ const createShortEntry = (entry) => - createShortSingleEntry(colors, { ...entry, titlePrefix: `${entry.name} ${suffix}` }); + createShortSingleEntry(colors, { + ...entry, + titlePrefix: `${entry.name} ${suffix}`, + }); /** @param {LongEntryItem} entry */ const createLongEntry = (entry) => - createLongSingleEntry(colors, { ...entry, titlePrefix: `${entry.name} ${suffix}` }); + createLongSingleEntry(colors, { + ...entry, + titlePrefix: `${entry.name} ${suffix}`, + }); const shortEntries = SHORT_PERIODS.map(buildBaseEntry); const longEntries = LONG_PERIODS.map(buildLongEntry); @@ -408,16 +591,25 @@ function createPeriodSection(ctx, { dca, lookback, returns }) { name: `${suffix} by Period`, title: `${suffix} Performance by Investment Period`, tree: [ - createCompareFolder(`All Periods ${suffix}`, [...shortEntries, ...longEntries]), + createCompareFolder(`All Periods ${suffix}`, [ + ...shortEntries, + ...longEntries, + ]), { name: "Short Term", title: "Up to 1 Year", - tree: [createCompareFolder(`Short Term ${suffix}`, shortEntries), ...shortEntries.map(createShortEntry)], + tree: [ + createCompareFolder(`Short Term ${suffix}`, shortEntries), + ...shortEntries.map(createShortEntry), + ], }, { name: "Long Term", title: "2+ Years", - tree: [createCompareFolder(`Long Term ${suffix}`, longEntries), ...longEntries.map(createLongEntry)], + tree: [ + createCompareFolder(`Long Term ${suffix}`, longEntries), + ...longEntries.map(createLongEntry), + ], }, ], }; @@ -461,12 +653,21 @@ export function createDcaByStartYearSection(ctx, { dca }) { title, tree: [ createCompareFolder(`${name} DCA`, entries), - ...entries.map((entry) => createShortSingleEntry(colors, { ...entry, titlePrefix: `${entry.name} DCA` })), + ...entries.map((entry) => + createShortSingleEntry(colors, { + ...entry, + titlePrefix: `${entry.name} DCA`, + }), + ), ], }); - const entries2020s = YEARS_2020S.map((year) => buildYearEntry(colors, dca, year)); - const entries2010s = YEARS_2010S.map((year) => buildYearEntry(colors, dca, year)); + const entries2020s = YEARS_2020S.map((year) => + buildYearEntry(colors, dca, year), + ); + const entries2010s = YEARS_2010S.map((year) => + buildYearEntry(colors, dca, year), + ); return { name: "DCA by Start Year", diff --git a/website/scripts/options/market.js b/website/scripts/options/market.js new file mode 100644 index 000000000..05de32869 --- /dev/null +++ b/website/scripts/options/market.js @@ -0,0 +1,481 @@ +/** Market section */ + +import { includes } from "../utils/array.js"; +import { Unit } from "../utils/units.js"; +import { priceLine, priceLines } from "./constants.js"; +import { baseline, histogram, line, price } from "./series.js"; +import { createPriceRatioCharts } from "./shared.js"; +import { periodIdToName } from "./utils.js"; + +/** + * @typedef {Object} Period + * @property {string} id + * @property {Color} color + * @property {AnyMetricPattern} returns + * @property {AnyPricePattern} lookback + * @property {boolean} [defaultActive] + */ + +/** + * @typedef {Period & { cagr: AnyMetricPattern }} PeriodWithCagr + */ + +/** + * @typedef {Object} MaPeriod + * @property {string} id + * @property {Color} color + * @property {ActivePriceRatioPattern} ratio + */ + +const commonMaIds = /** @type {const} */ (["1w", "1m", "200d", "1y", "200w", "4y"]); + +/** + * @param {PartialContext} ctx + * @param {string} label + * @param {MaPeriod[]} averages + */ +function createMaSubSection(ctx, label, averages) { + const common = averages.filter((a) => includes(commonMaIds, a.id)); + const more = averages.filter((a) => !includes(commonMaIds, a.id)); + + /** @param {MaPeriod} a */ + const toFolder = (a) => ({ + name: periodIdToName(a.id, true), + tree: createPriceRatioCharts(ctx, { + context: `${periodIdToName(a.id, true)} ${label}`, + legend: "average", + pricePattern: a.ratio.price, + ratio: a.ratio, + color: a.color, + }), + }); + + return { + name: label, + tree: [ + { + name: "Compare", + title: `Price ${label}s`, + top: averages.map((a) => price({ metric: a.ratio.price, name: a.id, color: a.color })), + }, + ...common.map(toFolder), + { name: "More...", tree: more.map(toFolder) }, + ], + }; +} + +/** + * @param {Colors} colors + * @param {string} name + * @param {string} title + * @param {Unit} unit + * @param {{ _1w: AnyMetricPattern, _1m: AnyMetricPattern, _1y: AnyMetricPattern }} metrics + */ +function volatilityChart(colors, name, title, unit, metrics) { + return { + name, + title, + bottom: [ + line({ metric: metrics._1w, name: "1w", color: colors.red, unit }), + line({ metric: metrics._1m, name: "1m", color: colors.orange, unit }), + line({ metric: metrics._1y, name: "1y", color: colors.lime, unit }), + ], + }; +} + +/** + * @param {string} name + * @param {Period[]} periods + */ +function returnsSubSection(name, periods) { + return { + name, + tree: [ + { + name: "Compare", + title: `${name} Returns`, + bottom: periods.map((p) => baseline({ metric: p.returns, name: p.id, color: p.color, unit: Unit.percentage })), + }, + ...periods.map((p) => ({ + name: periodIdToName(p.id, true), + title: `${periodIdToName(p.id, true)} Returns`, + bottom: [baseline({ metric: p.returns, name: "Total", unit: Unit.percentage })], + })), + ], + }; +} + +/** + * @param {string} name + * @param {PeriodWithCagr[]} periods + */ +function returnsSubSectionWithCagr(name, periods) { + return { + name, + tree: [ + { + name: "Compare", + title: `${name} Returns`, + bottom: periods.map((p) => baseline({ metric: p.returns, name: p.id, color: p.color, unit: Unit.percentage })), + }, + ...periods.map((p) => ({ + name: periodIdToName(p.id, true), + title: `${periodIdToName(p.id, true)} Returns`, + bottom: [ + baseline({ metric: p.returns, name: "Total", unit: Unit.percentage }), + baseline({ metric: p.cagr, name: "annual", unit: Unit.cagr }), + ], + })), + ], + }; +} + +/** + * @param {string} name + * @param {Period[]} periods + */ +function historicalSubSection(name, periods) { + return { + name, + tree: [ + { + name: "Compare", + title: `${name} Historical`, + top: periods.map((p) => price({ metric: p.lookback, name: p.id, color: p.color })), + }, + ...periods.map((p) => ({ + name: periodIdToName(p.id, true), + title: `${periodIdToName(p.id, true)} Ago`, + top: [price({ metric: p.lookback, name: "Price" })], + })), + ], + }; +} + +/** + * Create Market section + * @param {PartialContext} ctx + * @returns {PartialOptionsGroup} + */ +export function createMarketSection(ctx) { + const { colors, brk } = ctx; + const { market, supply, price: priceMetrics } = brk.metrics; + const { movingAverage: ma, ath, returns, volatility, range, indicators, lookback } = market; + + /** @type {Period[]} */ + const shortPeriods = [ + { id: "1d", color: colors.returns._1d, returns: returns.priceReturns._1d, lookback: lookback._1d }, + { id: "1w", color: colors.returns._1w, returns: returns.priceReturns._1w, lookback: lookback._1w }, + { id: "1m", color: colors.returns._1m, returns: returns.priceReturns._1m, lookback: lookback._1m }, + { id: "3m", color: colors.returns._3m, returns: returns.priceReturns._3m, lookback: lookback._3m, defaultActive: false }, + { id: "6m", color: colors.returns._6m, returns: returns.priceReturns._6m, lookback: lookback._6m, defaultActive: false }, + { id: "1y", color: colors.returns._1y, returns: returns.priceReturns._1y, lookback: lookback._1y }, + ]; + + /** @type {PeriodWithCagr[]} */ + const longPeriods = [ + { id: "2y", color: colors.returns._2y, returns: returns.priceReturns._2y, cagr: returns.cagr._2y, lookback: lookback._2y, defaultActive: false }, + { id: "3y", color: colors.returns._3y, returns: returns.priceReturns._3y, cagr: returns.cagr._3y, lookback: lookback._3y, defaultActive: false }, + { id: "4y", color: colors.returns._4y, returns: returns.priceReturns._4y, cagr: returns.cagr._4y, lookback: lookback._4y }, + { id: "5y", color: colors.returns._5y, returns: returns.priceReturns._5y, cagr: returns.cagr._5y, lookback: lookback._5y, defaultActive: false }, + { id: "6y", color: colors.returns._6y, returns: returns.priceReturns._6y, cagr: returns.cagr._6y, lookback: lookback._6y, defaultActive: false }, + { id: "8y", color: colors.returns._8y, returns: returns.priceReturns._8y, cagr: returns.cagr._8y, lookback: lookback._8y, defaultActive: false }, + { id: "10y", color: colors.returns._10y, returns: returns.priceReturns._10y, cagr: returns.cagr._10y, lookback: lookback._10y, defaultActive: false }, + ]; + + /** @type {MaPeriod[]} */ + const sma = [ + { id: "1w", color: colors.ma._1w, ratio: ma.price1wSma }, + { id: "8d", color: colors.ma._8d, ratio: ma.price8dSma }, + { id: "13d", color: colors.ma._13d, ratio: ma.price13dSma }, + { id: "21d", color: colors.ma._21d, ratio: ma.price21dSma }, + { id: "1m", color: colors.ma._1m, ratio: ma.price1mSma }, + { id: "34d", color: colors.ma._34d, ratio: ma.price34dSma }, + { id: "55d", color: colors.ma._55d, ratio: ma.price55dSma }, + { id: "89d", color: colors.ma._89d, ratio: ma.price89dSma }, + { id: "111d", color: colors.ma._111d, ratio: ma.price111dSma }, + { id: "144d", color: colors.ma._144d, ratio: ma.price144dSma }, + { id: "200d", color: colors.ma._200d, ratio: ma.price200dSma }, + { id: "350d", color: colors.ma._350d, ratio: ma.price350dSma }, + { id: "1y", color: colors.ma._1y, ratio: ma.price1ySma }, + { id: "2y", color: colors.ma._2y, ratio: ma.price2ySma }, + { id: "200w", color: colors.ma._200w, ratio: ma.price200wSma }, + { id: "4y", color: colors.ma._4y, ratio: ma.price4ySma }, + ]; + + /** @type {MaPeriod[]} */ + const ema = [ + { id: "1w", color: colors.ma._1w, ratio: ma.price1wEma }, + { id: "8d", color: colors.ma._8d, ratio: ma.price8dEma }, + { id: "12d", color: colors.ma._12d, ratio: ma.price12dEma }, + { id: "13d", color: colors.ma._13d, ratio: ma.price13dEma }, + { id: "21d", color: colors.ma._21d, ratio: ma.price21dEma }, + { id: "26d", color: colors.ma._26d, ratio: ma.price26dEma }, + { id: "1m", color: colors.ma._1m, ratio: ma.price1mEma }, + { id: "34d", color: colors.ma._34d, ratio: ma.price34dEma }, + { id: "55d", color: colors.ma._55d, ratio: ma.price55dEma }, + { id: "89d", color: colors.ma._89d, ratio: ma.price89dEma }, + { id: "144d", color: colors.ma._144d, ratio: ma.price144dEma }, + { id: "200d", color: colors.ma._200d, ratio: ma.price200dEma }, + { id: "1y", color: colors.ma._1y, ratio: ma.price1yEma }, + { id: "2y", color: colors.ma._2y, ratio: ma.price2yEma }, + { id: "200w", color: colors.ma._200w, ratio: ma.price200wEma }, + { id: "4y", color: colors.ma._4y, ratio: ma.price4yEma }, + ]; + + // SMA vs EMA comparison periods (common periods only) + const smaVsEma = [ + { id: "1w", name: "1 Week", color: colors.ma._1w, sma: ma.price1wSma, ema: ma.price1wEma }, + { id: "1m", name: "1 Month", color: colors.ma._1m, sma: ma.price1mSma, ema: ma.price1mEma }, + { id: "200d", name: "200 Day", color: colors.ma._200d, sma: ma.price200dSma, ema: ma.price200dEma }, + { id: "1y", name: "1 Year", color: colors.ma._1y, sma: ma.price1ySma, ema: ma.price1yEma }, + { id: "200w", name: "200 Week", color: colors.ma._200w, sma: ma.price200wSma, ema: ma.price200wEma }, + { id: "4y", name: "4 Year", color: colors.ma._4y, sma: ma.price4ySma, ema: ma.price4yEma }, + ]; + + return { + name: "Market", + tree: [ + { name: "Price", title: "Bitcoin Price" }, + + { + name: "Sats/$", + title: "Sats per Dollar", + bottom: [line({ metric: priceMetrics.sats.split.close, name: "Sats/$", unit: Unit.sats })], + }, + + { + name: "Capitalization", + title: "Market Capitalization", + bottom: [line({ metric: supply.marketCap, name: "Capitalization", unit: Unit.usd })], + }, + + { + name: "All Time High", + tree: [ + { + name: "Drawdown", + title: "ATH Drawdown", + top: [price({ metric: ath.priceAth, name: "ATH" })], + bottom: [line({ metric: ath.priceDrawdown, name: "Drawdown", color: colors.red, unit: Unit.percentage })], + }, + { + name: "Time Since", + title: "Time Since ATH", + top: [price({ metric: ath.priceAth, name: "ATH" })], + bottom: [ + line({ metric: ath.daysSincePriceAth, name: "Since", unit: Unit.days }), + line({ metric: ath.yearsSincePriceAth, name: "Since", unit: Unit.years }), + line({ metric: ath.maxDaysBetweenPriceAths, name: "Max", color: colors.red, unit: Unit.days }), + line({ metric: ath.maxYearsBetweenPriceAths, name: "Max", color: colors.red, unit: Unit.years }), + ], + }, + ], + }, + + { + name: "Returns", + tree: [ + { + name: "Compare", + title: "Returns Comparison", + bottom: [...shortPeriods, ...longPeriods].map((p) => + baseline({ + metric: p.returns, + name: p.id, + color: p.color, + unit: Unit.percentage, + defaultActive: p.defaultActive, + }), + ), + }, + returnsSubSection("Short-term", shortPeriods), + returnsSubSectionWithCagr("Long-term", longPeriods), + ], + }, + + { + name: "Volatility", + tree: [ + volatilityChart(colors, "Index", "Volatility Index", Unit.percentage, { + _1w: volatility.price1wVolatility, + _1m: volatility.price1mVolatility, + _1y: volatility.price1yVolatility, + }), + { + name: "True Range", + title: "True Range", + bottom: [ + line({ metric: range.priceTrueRange, name: "Daily", color: colors.yellow, unit: Unit.usd }), + line({ metric: range.priceTrueRange2wSum, name: "2w Sum", color: colors.orange, unit: Unit.usd, defaultActive: false }), + ], + }, + { + name: "Choppiness", + title: "Choppiness Index", + bottom: [ + line({ metric: range.price2wChoppinessIndex, name: "2w", color: colors.red, unit: Unit.index }), + ...priceLines({ ctx, unit: Unit.index, numbers: [61.8, 38.2] }), + ], + }, + volatilityChart(colors, "Sharpe Ratio", "Sharpe Ratio", Unit.ratio, { + _1w: volatility.sharpe1w, + _1m: volatility.sharpe1m, + _1y: volatility.sharpe1y, + }), + volatilityChart(colors, "Sortino Ratio", "Sortino Ratio", Unit.ratio, { + _1w: volatility.sortino1w, + _1m: volatility.sortino1m, + _1y: volatility.sortino1y, + }), + ], + }, + + { + name: "Moving Averages", + tree: [ + { + name: "SMA vs EMA", + tree: [ + { + name: "All Periods", + title: "SMA vs EMA Comparison", + top: smaVsEma.flatMap((p) => [ + price({ metric: p.sma.price, name: `${p.id} SMA`, color: p.color }), + price({ metric: p.ema.price, name: `${p.id} EMA`, color: p.color, style: 1 }), + ]), + }, + ...smaVsEma.map((p) => ({ + name: p.name, + title: `${p.name} SMA vs EMA`, + top: [ + price({ metric: p.sma.price, name: "SMA", color: p.color }), + price({ metric: p.ema.price, name: "EMA", color: p.color, style: 1 }), + ], + })), + ], + }, + createMaSubSection(ctx, "SMA", sma), + createMaSubSection(ctx, "EMA", ema), + ], + }, + + { + name: "Bands", + tree: [ + { + name: "MinMax", + tree: [ + { id: "1w", name: "1 Week", min: range.price1wMin, max: range.price1wMax }, + { id: "2w", name: "2 Week", min: range.price2wMin, max: range.price2wMax }, + { id: "1m", name: "1 Month", min: range.price1mMin, max: range.price1mMax }, + { id: "1y", name: "1 Year", min: range.price1yMin, max: range.price1yMax }, + ].map((p) => ({ + name: p.id, + title: `${p.name} MinMax`, + top: [ + price({ metric: p.min, name: "Min", key: "price-min", color: colors.red }), + price({ metric: p.max, name: "Max", key: "price-max", color: colors.green }), + ], + })), + }, + { + name: "Mayer Multiple", + title: "Mayer Multiple", + top: [ + price({ metric: ma.price200dSma.price, name: "200d SMA", color: colors.yellow }), + price({ metric: ma.price200dSmaX24, name: "200d SMA x2.4", color: colors.green }), + price({ metric: ma.price200dSmaX08, name: "200d SMA x0.8", color: colors.red }), + ], + }, + ], + }, + + { + name: "Momentum", + tree: [ + { + name: "RSI", + title: "RSI (14d)", + bottom: [ + line({ metric: indicators.rsi14d, name: "RSI", color: colors.indigo, unit: Unit.index }), + line({ metric: indicators.rsi14dMin, name: "Min", color: colors.red, defaultActive: false, unit: Unit.index }), + line({ metric: indicators.rsi14dMax, name: "Max", color: colors.green, defaultActive: false, unit: Unit.index }), + priceLine({ ctx, unit: Unit.index, number: 70 }), + priceLine({ ctx, unit: Unit.index, number: 50, defaultActive: false }), + priceLine({ ctx, unit: Unit.index, number: 30 }), + ], + }, + { + name: "StochRSI", + title: "Stochastic RSI", + bottom: [ + line({ metric: indicators.stochRsiK, name: "K", color: colors.blue, unit: Unit.index }), + line({ metric: indicators.stochRsiD, name: "D", color: colors.orange, unit: Unit.index }), + ...priceLines({ ctx, unit: Unit.index, numbers: [80, 20] }), + ], + }, + { + name: "MACD", + title: "MACD", + bottom: [ + line({ metric: indicators.macdLine, name: "MACD", color: colors.blue, unit: Unit.usd }), + line({ metric: indicators.macdSignal, name: "Signal", color: colors.orange, unit: Unit.usd }), + histogram({ metric: indicators.macdHistogram, name: "Histogram", unit: Unit.usd }), + ], + }, + ], + }, + + { + name: "Historical", + tree: [ + { + name: "Compare", + title: "Historical Comparison", + top: [...shortPeriods, ...longPeriods].map((p) => + price({ + metric: p.lookback, + name: p.id, + color: p.color, + defaultActive: p.defaultActive, + }), + ), + }, + historicalSubSection("Short-term", shortPeriods), + historicalSubSection("Long-term", longPeriods), + ], + }, + + { + name: "Indicators", + tree: [ + { + name: "Pi Cycle", + title: "Pi Cycle", + top: [ + price({ metric: ma.price111dSma.price, name: "111d SMA", color: colors.green }), + price({ metric: ma.price350dSmaX2, name: "350d SMA x2", color: colors.red }), + ], + bottom: [baseline({ metric: indicators.piCycle, name: "Pi Cycle", unit: Unit.ratio, base: 1 })], + }, + { + name: "Puell Multiple", + title: "Puell Multiple", + bottom: [line({ metric: indicators.puellMultiple, name: "Puell", color: colors.green, unit: Unit.ratio })], + }, + { + name: "NVT", + title: "NVT Ratio", + bottom: [line({ metric: indicators.nvt, name: "NVT", color: colors.orange, unit: Unit.ratio })], + }, + { + name: "Gini", + title: "Gini Coefficient", + bottom: [line({ metric: indicators.gini, name: "Gini", color: colors.red, unit: Unit.ratio })], + }, + ], + }, + ], + }; +} diff --git a/website/scripts/options/market/averages.js b/website/scripts/options/market/averages.js deleted file mode 100644 index b319feb84..000000000 --- a/website/scripts/options/market/averages.js +++ /dev/null @@ -1,202 +0,0 @@ -/** Moving averages section */ - -import { price } from "../series.js"; -import { createPriceRatioCharts } from "../shared.js"; -import { periodIdToName } from "../utils.js"; - -/** - * @param {Colors} colors - * @param {MarketMovingAverage} ma - */ -function buildSmaAverages(colors, ma) { - return /** @type {const} */ ([ - ["1w", 7, "red", ma.price1wSma], - ["8d", 8, "orange", ma.price8dSma], - ["13d", 13, "amber", ma.price13dSma], - ["21d", 21, "yellow", ma.price21dSma], - ["1m", 30, "lime", ma.price1mSma], - ["34d", 34, "green", ma.price34dSma], - ["55d", 55, "emerald", ma.price55dSma], - ["89d", 89, "teal", ma.price89dSma], - ["111d", 111, "cyan", ma.price111dSma], - ["144d", 144, "sky", ma.price144dSma], - ["200d", 200, "blue", ma.price200dSma], - ["350d", 350, "indigo", ma.price350dSma], - ["1y", 365, "violet", ma.price1ySma], - ["2y", 730, "purple", ma.price2ySma], - ["200w", 1400, "fuchsia", ma.price200wSma], - ["4y", 1460, "pink", ma.price4ySma], - ]).map(([id, days, colorKey, ratio]) => ({ - id, - name: periodIdToName(id, true), - days, - color: colors[colorKey], - ratio, - })); -} - -/** - * @param {Colors} colors - * @param {MarketMovingAverage} ma - */ -function buildEmaAverages(colors, ma) { - return /** @type {const} */ ([ - ["1w", 7, "red", ma.price1wEma], - ["8d", 8, "orange", ma.price8dEma], - ["12d", 12, "amber", ma.price12dEma], - ["13d", 13, "yellow", ma.price13dEma], - ["21d", 21, "lime", ma.price21dEma], - ["26d", 26, "green", ma.price26dEma], - ["1m", 30, "emerald", ma.price1mEma], - ["34d", 34, "teal", ma.price34dEma], - ["55d", 55, "cyan", ma.price55dEma], - ["89d", 89, "sky", ma.price89dEma], - ["144d", 144, "blue", ma.price144dEma], - ["200d", 200, "indigo", ma.price200dEma], - ["1y", 365, "violet", ma.price1yEma], - ["2y", 730, "purple", ma.price2yEma], - ["200w", 1400, "fuchsia", ma.price200wEma], - ["4y", 1460, "pink", ma.price4yEma], - ]).map(([id, days, colorKey, ratio]) => ({ - id, - name: periodIdToName(id, true), - days, - color: colors[colorKey], - ratio, - })); -} - -/** Common period IDs to show at top level */ -const COMMON_PERIODS = ["1w", "1m", "200d", "1y", "200w", "4y"]; - -/** Periods to compare SMA vs EMA */ -const COMPARISON_PERIODS = ["1w", "1m", "200d", "1y", "200w", "4y"]; - -/** - * Create SMA vs EMA comparison section - * @param {ReturnType} smaAverages - * @param {ReturnType} emaAverages - */ -function createCompareSection(smaAverages, emaAverages) { - // Find matching SMA/EMA pairs - const pairs = COMPARISON_PERIODS.map((id) => { - const sma = smaAverages.find((a) => a.id === id); - const ema = emaAverages.find((a) => a.id === id); - if (!sma || !ema) return null; - return { id, sma, ema }; - }).filter( - /** @type {(p: any) => p is { id: string, sma: ReturnType[number], ema: ReturnType[number] }} */ ( - p, - ) => p !== null, - ); - - return { - name: "Compare", - tree: [ - { - name: "All Periods", - title: "SMA vs EMA Comparison", - top: pairs.flatMap(({ sma, ema }) => [ - price({ - metric: sma.ratio.price, - name: `${sma.id} SMA`, - color: sma.color, - }), - price({ - metric: ema.ratio.price, - name: `${ema.id} EMA`, - color: ema.color, - style: 1, - }), - ]), - }, - ...pairs.map(({ id, sma, ema }) => ({ - name: periodIdToName(id, true), - title: `${periodIdToName(id, true)} SMA vs EMA`, - top: [ - price({ metric: sma.ratio.price, name: "SMA", color: sma.color }), - price({ - metric: ema.ratio.price, - name: "EMA", - color: ema.color, - style: 1, - }), - ], - })), - ], - }; -} - -/** - * @param {PartialContext} ctx - * @param {MarketMovingAverage} movingAverage - */ -export function createAveragesSection(ctx, movingAverage) { - const { colors } = ctx; - const smaAverages = buildSmaAverages(colors, movingAverage); - const emaAverages = buildEmaAverages(colors, movingAverage); - - /** - * @param {string} label - * @param {ReturnType | ReturnType} averages - */ - const createSubSection = (label, averages) => { - const commonAverages = averages.filter(({ id }) => - COMMON_PERIODS.includes(id), - ); - const moreAverages = averages.filter( - ({ id }) => !COMMON_PERIODS.includes(id), - ); - - return { - name: label, - tree: [ - { - name: "Compare", - title: `Price ${label}s`, - top: averages.map(({ id, color, ratio }) => - price({ - metric: ratio.price, - name: id, - color, - }), - ), - }, - // Common periods at top level - ...commonAverages.map(({ name, color, ratio }) => ({ - name, - tree: createPriceRatioCharts(ctx, { - context: `${name} ${label}`, - legend: "average", - pricePattern: ratio.price, - ratio, - color, - }), - })), - // Less common periods in "More..." folder - { - name: "More...", - tree: moreAverages.map(({ name, color, ratio }) => ({ - name, - tree: createPriceRatioCharts(ctx, { - context: `${name} ${label}`, - legend: "average", - pricePattern: ratio.price, - ratio, - color, - }), - })), - }, - ], - }; - }; - - return { - name: "Moving Averages", - tree: [ - createCompareSection(smaAverages, emaAverages), - createSubSection("SMA", smaAverages), - createSubSection("EMA", emaAverages), - ], - }; -} diff --git a/website/scripts/options/market/bands.js b/website/scripts/options/market/bands.js deleted file mode 100644 index f3bf5ec02..000000000 --- a/website/scripts/options/market/bands.js +++ /dev/null @@ -1,85 +0,0 @@ -import { price } from "../series.js"; - -/** - * Create Bands section - * @param {PartialContext} ctx - * @param {Object} args - * @param {Market["range"]} args.range - * @param {Market["movingAverage"]} args.movingAverage - */ -export function createBandsSection(ctx, { range, movingAverage }) { - const { colors } = ctx; - - return { - name: "Bands", - tree: [ - { - name: "MinMax", - tree: [ - { - id: "1w", - title: "1 Week", - min: range.price1wMin, - max: range.price1wMax, - }, - { - id: "2w", - title: "2 Week", - min: range.price2wMin, - max: range.price2wMax, - }, - { - id: "1m", - title: "1 Month", - min: range.price1mMin, - max: range.price1mMax, - }, - { - id: "1y", - title: "1 Year", - min: range.price1yMin, - max: range.price1yMax, - }, - ].map(({ id, title, min, max }) => ({ - name: id, - title: `${title} MinMax`, - top: [ - price({ - metric: min, - name: "Min", - key: `price-min`, - color: colors.red, - }), - price({ - metric: max, - name: "Max", - key: `price-max`, - color: colors.green, - }), - ], - })), - }, - { - name: "Mayer Multiple", - title: "Mayer Multiple", - top: [ - price({ - metric: movingAverage.price200dSma.price, - name: "200d SMA", - color: colors.yellow, - }), - price({ - metric: movingAverage.price200dSmaX24, - name: "200d SMA x2.4", - color: colors.green, - }), - price({ - metric: movingAverage.price200dSmaX08, - name: "200d SMA x0.8", - color: colors.red, - }), - ], - }, - ], - }; -} diff --git a/website/scripts/options/market/index.js b/website/scripts/options/market/index.js deleted file mode 100644 index 49d42d20a..000000000 --- a/website/scripts/options/market/index.js +++ /dev/null @@ -1,175 +0,0 @@ -/** Market section - Main entry point */ - -import { Unit } from "../../utils/units.js"; -import { line, price } from "../series.js"; -import { createAveragesSection } from "./averages.js"; -import { createReturnsSection } from "./performance.js"; -import { createMomentumSection } from "./momentum.js"; -import { createVolatilitySection } from "./volatility.js"; -import { createBandsSection } from "./bands.js"; -import { createValuationSection } from "./onchain.js"; - -/** - * Create Market section - * @param {PartialContext} ctx - * @returns {PartialOptionsGroup} - */ -export function createMarketSection(ctx) { - const { colors, brk } = ctx; - const { market, supply } = brk.metrics; - const { movingAverage, ath, returns, volatility, range, indicators } = market; - - return { - name: "Market", - tree: [ - // Price - { - name: "Price", - title: "Bitcoin Price", - }, - // Oracle section is localhost-only debug - uses non-price-pattern metrics - // ...(localhost - // ? /** @type {PartialOptionsTree} */ ([ - // { - // name: "Oracle", - // title: "Oracle Price", - // top: /** @type {any} */ ([ - // candlestick({ - // metric: priceMetrics.oracle.closeOhlcDollars, - // name: "Close", - // unit: Unit.usd, - // }), - // candlestick({ - // metric: priceMetrics.oracle.midOhlcDollars, - // name: "Mid", - // unit: Unit.usd, - // }), - // line({ - // metric: priceMetrics.oracle.phaseDailyDollars.median, - // name: "o. p50", - // unit: Unit.usd, - // color: colors.yellow, - // }), - // line({ - // metric: priceMetrics.oracle.phaseV2DailyDollars.median, - // name: "o2. p50", - // unit: Unit.usd, - // color: colors.orange, - // }), - // line({ - // metric: priceMetrics.oracle.phaseV2PeakDailyDollars.median, - // name: "o2.2 p50", - // unit: Unit.usd, - // color: colors.orange, - // }), - // line({ - // metric: priceMetrics.oracle.phaseV3DailyDollars.median, - // name: "o3. p50", - // unit: Unit.usd, - // color: colors.red, - // }), - // line({ - // metric: priceMetrics.oracle.phaseV3PeakDailyDollars.median, - // name: "o3.2 p50", - // unit: Unit.usd, - // color: colors.red, - // }), - // line({ - // metric: priceMetrics.oracle.phaseDailyDollars.max, - // name: "o. max", - // unit: Unit.usd, - // color: colors.lime, - // }), - // line({ - // metric: priceMetrics.oracle.phaseV2DailyDollars.max, - // name: "o.2 max", - // unit: Unit.usd, - // color: colors.emerald, - // }), - // line({ - // metric: priceMetrics.oracle.phaseDailyDollars.min, - // name: "o. min", - // unit: Unit.usd, - // color: colors.rose, - // }), - // line({ - // metric: priceMetrics.oracle.phaseV2DailyDollars.min, - // name: "o.2 min", - // unit: Unit.usd, - // color: colors.purple, - // }), - // ]), - // }, - // ]) - // : []), - - // Capitalization - { - name: "Capitalization", - title: "Market Cap", - bottom: [ - line({ - metric: supply.marketCap, - name: "Capitalization", - unit: Unit.usd, - }), - ], - }, - - // All Time High - { - name: "All Time High", - title: "All Time High", - top: [price({ metric: ath.priceAth, name: "ATH" })], - bottom: [ - line({ - metric: ath.priceDrawdown, - name: "Drawdown", - color: colors.red, - unit: Unit.percentage, - }), - line({ - metric: ath.daysSincePriceAth, - name: "Since", - unit: Unit.days, - }), - line({ - metric: ath.yearsSincePriceAth, - name: "Since", - unit: Unit.years, - }), - line({ - metric: ath.maxDaysBetweenPriceAths, - name: "Max", - color: colors.red, - unit: Unit.days, - }), - line({ - metric: ath.maxYearsBetweenPriceAths, - name: "Max", - color: colors.red, - unit: Unit.years, - }), - ], - }, - - // Moving Averages - createAveragesSection(ctx, movingAverage), - - // Returns - createReturnsSection(ctx, returns), - - // Volatility - createVolatilitySection(ctx, { volatility, range }), - - // Momentum - createMomentumSection(ctx, indicators), - - // Bands - createBandsSection(ctx, { range, movingAverage }), - - // Valuation - createValuationSection(ctx, { indicators, movingAverage }), - ], - }; -} diff --git a/website/scripts/options/market/momentum.js b/website/scripts/options/market/momentum.js deleted file mode 100644 index 49bb3ce57..000000000 --- a/website/scripts/options/market/momentum.js +++ /dev/null @@ -1,111 +0,0 @@ -/** Momentum indicators (RSI, StochRSI, Stochastic, MACD) */ - -import { Unit } from "../../utils/units.js"; -import { priceLine, priceLines } from "../constants.js"; -import { line, histogram } from "../series.js"; - -/** - * Create Momentum section - * @param {PartialContext} ctx - * @param {Market["indicators"]} indicators - */ -export function createMomentumSection(ctx, indicators) { - const { colors } = ctx; - - return { - name: "Momentum", - tree: [ - { - name: "RSI", - title: "RSI (14d)", - bottom: [ - line({ - metric: indicators.rsi14d, - name: "RSI", - color: colors.indigo, - unit: Unit.index, - }), - line({ - metric: indicators.rsi14dMin, - name: "Min", - color: colors.red, - defaultActive: false, - unit: Unit.index, - }), - line({ - metric: indicators.rsi14dMax, - name: "Max", - color: colors.green, - defaultActive: false, - unit: Unit.index, - }), - priceLine({ ctx, unit: Unit.index, number: 70 }), - priceLine({ - ctx, - unit: Unit.index, - number: 50, - defaultActive: false, - }), - priceLine({ ctx, unit: Unit.index, number: 30 }), - ], - }, - { - name: "StochRSI", - title: "Stochastic RSI", - bottom: [ - // line({ - // metric: indicators.stochRsi, - // name: "Stoch RSI", - // color: colors.purple, - // unit: Unit.index, - // }), - line({ - metric: indicators.stochRsiK, - name: "K", - color: colors.blue, - unit: Unit.index, - }), - line({ - metric: indicators.stochRsiD, - name: "D", - color: colors.orange, - unit: Unit.index, - }), - ...priceLines({ ctx, unit: Unit.index, numbers: [80, 20] }), - ], - }, - // { - // name: "Stochastic", - // title: "Stochastic Oscillator", - // bottom: [ - // line({ metric: indicators.stochK, name: "K", color: colors.blue, unit: Unit.index }), - // line({ metric: indicators.stochD, name: "D", color: colors.orange, unit: Unit.index }), - // priceLines({ ctx, unit: Unit.index, numbers: [80, 20] }), - // ], - // }, - { - name: "MACD", - title: "MACD", - bottom: [ - line({ - metric: indicators.macdLine, - name: "MACD", - color: colors.blue, - unit: Unit.usd, - }), - line({ - metric: indicators.macdSignal, - name: "Signal", - color: colors.orange, - unit: Unit.usd, - }), - histogram({ - metric: indicators.macdHistogram, - name: "Histogram", - unit: Unit.usd, - }), - ], - }, - ], - }; -} diff --git a/website/scripts/options/market/onchain.js b/website/scripts/options/market/onchain.js deleted file mode 100644 index 02359b146..000000000 --- a/website/scripts/options/market/onchain.js +++ /dev/null @@ -1,81 +0,0 @@ -/** On-chain indicators (Pi Cycle, Puell, NVT, Gini) */ - -import { Unit } from "../../utils/units.js"; -import { baseline, line, price } from "../series.js"; - -/** - * Create Valuation section - * @param {PartialContext} ctx - * @param {Object} args - * @param {Market["indicators"]} args.indicators - * @param {Market["movingAverage"]} args.movingAverage - */ -export function createValuationSection(ctx, { indicators, movingAverage }) { - const { colors } = ctx; - - return { - name: "Valuation", - tree: [ - { - name: "Pi Cycle", - title: "Pi Cycle", - top: [ - price({ - metric: movingAverage.price111dSma.price, - name: "111d SMA", - color: colors.green, - }), - price({ - metric: movingAverage.price350dSmaX2, - name: "350d SMA x2", - color: colors.red, - }), - ], - bottom: [ - baseline({ - metric: indicators.piCycle, - name: "Pi Cycle", - unit: Unit.ratio, - base: 1, - }), - ], - }, - { - name: "Puell Multiple", - title: "Puell Multiple", - bottom: [ - line({ - metric: indicators.puellMultiple, - name: "Puell", - color: colors.green, - unit: Unit.ratio, - }), - ], - }, - { - name: "NVT", - title: "NVT Ratio", - bottom: [ - line({ - metric: indicators.nvt, - name: "NVT", - color: colors.orange, - unit: Unit.ratio, - }), - ], - }, - { - name: "Gini", - title: "Gini Coefficient", - bottom: [ - line({ - metric: indicators.gini, - name: "Gini", - color: colors.red, - unit: Unit.ratio, - }), - ], - }, - ], - }; -} diff --git a/website/scripts/options/market/performance.js b/website/scripts/options/market/performance.js deleted file mode 100644 index 9dcb8ed05..000000000 --- a/website/scripts/options/market/performance.js +++ /dev/null @@ -1,142 +0,0 @@ -/** Performance section */ - -import { Unit } from "../../utils/units.js"; -import { priceLine } from "../constants.js"; -import { baseline } from "../series.js"; -import { periodIdToName } from "../utils.js"; - -/** - * Create Returns section - * @param {PartialContext} ctx - * @param {Market["returns"]} returns - */ -export function createReturnsSection(ctx, returns) { - const { colors } = ctx; - - const shortTermPeriods = /** @type {const} */ ([ - ["1d", "_1d", undefined], - ["1w", "_1w", undefined], - ["1m", "_1m", undefined], - ]); - - const mediumTermPeriods = /** @type {const} */ ([ - ["3m", "_3m", undefined], - ["6m", "_6m", undefined], - ["1y", "_1y", undefined], - ]); - - const longTermPeriods = /** @type {const} */ ([ - ["2y", "_2y", "_2y"], - ["3y", "_3y", "_3y"], - ["4y", "_4y", "_4y"], - ["5y", "_5y", "_5y"], - ["6y", "_6y", "_6y"], - ["8y", "_8y", "_8y"], - ["10y", "_10y", "_10y"], - ]); - - /** - * @template {keyof typeof returns.priceReturns} K - * @param {readonly [string, K, K | undefined]} period - */ - const createPeriodChart = ([id, returnKey, cagrKey]) => { - const priceReturns = returns.priceReturns[/** @type {K} */ (returnKey)]; - const cagr = cagrKey - ? returns.cagr[/** @type {keyof typeof returns.cagr} */ (cagrKey)] - : undefined; - const name = periodIdToName(id, true); - return { - name, - title: `${name} Returns`, - bottom: [ - baseline({ - metric: priceReturns, - name: "Total", - unit: Unit.percentage, - }), - ...(cagr - ? [ - baseline({ - metric: cagr, - name: "CAGR", - color: [colors.cyan, colors.orange], - unit: Unit.percentage, - }), - ] - : []), - ], - }; - }; - - return { - name: "Returns", - tree: [ - // Compare all periods - { - name: "Compare", - title: "Returns Comparison", - bottom: [ - baseline({ - metric: returns.priceReturns._1d, - name: "1d", - color: colors.red, - unit: Unit.percentage, - }), - baseline({ - metric: returns.priceReturns._1w, - name: "1w", - color: colors.orange, - unit: Unit.percentage, - }), - baseline({ - metric: returns.priceReturns._1m, - name: "1m", - color: colors.yellow, - unit: Unit.percentage, - }), - baseline({ - metric: returns.priceReturns._3m, - name: "3m", - color: colors.lime, - unit: Unit.percentage, - defaultActive: false, - }), - baseline({ - metric: returns.priceReturns._6m, - name: "6m", - color: colors.green, - unit: Unit.percentage, - defaultActive: false, - }), - baseline({ - metric: returns.priceReturns._1y, - name: "1y", - color: colors.teal, - unit: Unit.percentage, - }), - baseline({ - metric: returns.priceReturns._4y, - name: "4y", - color: colors.blue, - unit: Unit.percentage, - }), - ], - }, - // Short-term (1d, 1w, 1m) - { - name: "Short-term", - tree: shortTermPeriods.map(createPeriodChart), - }, - // Medium-term (3m, 6m, 1y) - { - name: "Medium-term", - tree: mediumTermPeriods.map(createPeriodChart), - }, - // Long-term (2y+) - { - name: "Long-term", - tree: longTermPeriods.map(createPeriodChart), - }, - ], - }; -} diff --git a/website/scripts/options/market/volatility.js b/website/scripts/options/market/volatility.js deleted file mode 100644 index 694f0d3e2..000000000 --- a/website/scripts/options/market/volatility.js +++ /dev/null @@ -1,119 +0,0 @@ -/** Volatility indicators (Index, True Range, Choppiness, Sharpe, Sortino) */ - -import { Unit } from "../../utils/units.js"; -import { priceLine, priceLines } from "../constants.js"; -import { line } from "../series.js"; - -/** - * Create Volatility section - * @param {PartialContext} ctx - * @param {Object} args - * @param {Market["volatility"]} args.volatility - * @param {Market["range"]} args.range - */ -export function createVolatilitySection(ctx, { volatility, range }) { - const { colors } = ctx; - - return { - name: "Volatility", - tree: [ - { - name: "Index", - title: "Volatility Index", - bottom: [ - line({ - metric: volatility.price1wVolatility, - name: "1w", - color: colors.red, - unit: Unit.percentage, - }), - line({ - metric: volatility.price1mVolatility, - name: "1m", - color: colors.orange, - unit: Unit.percentage, - }), - line({ - metric: volatility.price1yVolatility, - name: "1y", - color: colors.lime, - unit: Unit.percentage, - }), - ], - }, - { - name: "True Range", - title: "True Range", - bottom: [ - line({ - metric: range.priceTrueRange, - name: "Value", - color: colors.yellow, - unit: Unit.usd, - }), - ], - }, - { - name: "Choppiness", - title: "Choppiness Index", - bottom: [ - line({ - metric: range.price2wChoppinessIndex, - name: "2w", - color: colors.red, - unit: Unit.index, - }), - ...priceLines({ ctx, unit: Unit.index, numbers: [61.8, 38.2] }), - ], - }, - { - name: "Sharpe Ratio", - title: "Sharpe Ratio", - bottom: [ - line({ - metric: volatility.sharpe1w, - name: "1w", - color: colors.red, - unit: Unit.ratio, - }), - line({ - metric: volatility.sharpe1m, - name: "1m", - color: colors.orange, - unit: Unit.ratio, - }), - line({ - metric: volatility.sharpe1y, - name: "1y", - color: colors.lime, - unit: Unit.ratio, - }), - ], - }, - { - name: "Sortino Ratio", - title: "Sortino Ratio", - bottom: [ - line({ - metric: volatility.sortino1w, - name: "1w", - color: colors.red, - unit: Unit.ratio, - }), - line({ - metric: volatility.sortino1m, - name: "1m", - color: colors.orange, - unit: Unit.ratio, - }), - line({ - metric: volatility.sortino1y, - name: "1y", - color: colors.lime, - unit: Unit.ratio, - }), - ], - }, - ], - }; -} diff --git a/website/scripts/options/network.js b/website/scripts/options/network.js index f09593802..6e5f88d3e 100644 --- a/website/scripts/options/network.js +++ b/website/scripts/options/network.js @@ -618,7 +618,6 @@ export function createNetworkSection(ctx) { pattern: transactions.volume.receivedSum, name: "Received", color: colors.cyan, - defaultActive: false, }), ], }, @@ -794,7 +793,6 @@ export function createNetworkSection(ctx) { ...fromBaseStatsPattern({ pattern: blocks.interval, unit: Unit.secs, - avgActive: false, }), priceLine({ ctx, unit: Unit.secs, name: "Target", number: 600 }), ], @@ -1003,8 +1001,8 @@ export function createNetworkSection(ctx) { }), }, { - name: "Throughput", - title: "Throughput", + name: "Activity Rate", + title: "Activity Rate", bottom: [ dots({ metric: transactions.volume.txPerSec, diff --git a/website/scripts/options/partial.js b/website/scripts/options/partial.js index 3a9118fb7..cb45ed5e2 100644 --- a/website/scripts/options/partial.js +++ b/website/scripts/options/partial.js @@ -15,7 +15,7 @@ import { createCohortFolderAddress, createAddressCohortFolder, } from "./distribution/index.js"; -import { createMarketSection } from "./market/index.js"; +import { createMarketSection } from "./market.js"; import { createNetworkSection } from "./network.js"; import { createMiningSection } from "./mining.js"; import { createCointimeSection } from "./cointime.js"; diff --git a/website/scripts/options/shared.js b/website/scripts/options/shared.js index c16ac2540..9140896fa 100644 --- a/website/scripts/options/shared.js +++ b/website/scripts/options/shared.js @@ -401,16 +401,17 @@ export function createZScoresFolder( * @param {string} [args.ratioName] - Optional custom name for ratio chart (default: "ratio") * @param {string} [args.priceTitle] - Optional override for price chart title (default: context) * @param {string} [args.zScoresSuffix] - Optional suffix appended to context for z-scores (e.g., "MVRV" gives "2y Z-Score: STH MVRV") + * @param {FetchedPriceSeriesBlueprint[]} [args.priceReferences] - Optional additional price series to show in Price chart * @returns {PartialOptionsTree} */ -export function createPriceRatioCharts(ctx, { context, legend, pricePattern, ratio, color, ratioName, priceTitle, zScoresSuffix }) { +export function createPriceRatioCharts(ctx, { context, legend, pricePattern, ratio, color, ratioName, priceTitle, zScoresSuffix, priceReferences }) { const titleFn = formatCohortTitle(context); const zScoresTitleFn = zScoresSuffix ? formatCohortTitle(`${context} ${zScoresSuffix}`) : titleFn; return [ { name: "Price", title: priceTitle ?? context, - top: [price({ metric: pricePattern, name: legend, color })], + top: [price({ metric: pricePattern, name: legend, color }), ...(priceReferences ?? [])], }, createRatioChart(ctx, { title: titleFn, diff --git a/website/scripts/options/unused.js b/website/scripts/options/unused.js index c866f9585..203523814 100644 --- a/website/scripts/options/unused.js +++ b/website/scripts/options/unused.js @@ -28,28 +28,29 @@ function walk(node, map, path) { for (const [key, value] of Object.entries(node)) { const kn = key.toLowerCase(); if ( - kn === "mvrv" || - kn === "time" || - kn === "height" || - kn === "constants" || + // kn === "mvrv" || + // kn === "time" || + // kn === "height" || + // kn === "constants" || kn === "blockhash" || - kn === "oracle" || - kn === "split" || - kn === "ohlc" || - kn === "outpoint" || + kn === "date" || + // kn === "oracle" || + // kn === "split" || + // kn === "ohlc" || + // kn === "outpoint" || kn === "positions" || - kn === "outputtype" || + // kn === "outputtype" || kn === "heighttopool" || - kn === "txid" || - kn.startsWith("satblocks") || - kn.startsWith("satdays") || - kn.endsWith("state") || - kn.endsWith("cents") || + // kn === "txid" || + kn.startsWith("timestamp") || + // kn.startsWith("satdays") || + // kn.endsWith("state") || + // kn.endsWith("cents") || kn.endsWith("index") || - kn.endsWith("indexes") || - kn.endsWith("raw") || - kn.endsWith("bytes") || - (kn.startsWith("_") && kn.endsWith("start")) + kn.endsWith("indexes") + // kn.endsWith("raw") || + // kn.endsWith("bytes") || + // (kn.startsWith("_") && kn.endsWith("start")) ) continue; walk(/** @type {TreeNode | null | undefined} */ (value), map, [