diff --git a/website/scripts/options/distribution/activity.js b/website/scripts/options/distribution/activity.js index 3aa93362f..7ea1beeaa 100644 --- a/website/scripts/options/distribution/activity.js +++ b/website/scripts/options/distribution/activity.js @@ -13,7 +13,7 @@ import { Unit } from "../../utils/units.js"; import { line, baseline, dotsBaseline, dots } from "../series.js"; -import { satsBtcUsd } from "../shared.js"; +import { satsBtcUsd, mapCohortsWithAll, flatMapCohortsWithAll } from "../shared.js"; import { colors } from "../../utils/colors.js"; // ============================================================================ @@ -56,6 +56,7 @@ function soprSeries(realized, rawName = "SOPR") { * Create grouped SOPR chart entries (Raw, 7d EMA, 30d EMA) * @template {{ color: Color, name: string }} T * @param {readonly T[]} list + * @param {T} all * @param {(item: T) => AnyMetricPattern} getSopr * @param {(item: T) => AnyMetricPattern} getSopr7d * @param {(item: T) => AnyMetricPattern} getSopr30d @@ -65,6 +66,7 @@ function soprSeries(realized, rawName = "SOPR") { */ function groupedSoprCharts( list, + all, getSopr, getSopr7d, getSopr30d, @@ -75,7 +77,7 @@ function groupedSoprCharts( { name: "Raw", title: title(`${titlePrefix}SOPR`), - bottom: list.map((item) => + bottom: mapCohortsWithAll(list, all, (item) => baseline({ metric: getSopr(item), name: item.name, @@ -88,7 +90,7 @@ function groupedSoprCharts( { name: "7d EMA", title: title(`${titlePrefix}SOPR 7d EMA`), - bottom: list.map((item) => + bottom: mapCohortsWithAll(list, all, (item) => baseline({ metric: getSopr7d(item), name: item.name, @@ -101,7 +103,7 @@ function groupedSoprCharts( { name: "30d EMA", title: title(`${titlePrefix}SOPR 30d EMA`), - bottom: list.map((item) => + bottom: mapCohortsWithAll(list, all, (item) => baseline({ metric: getSopr30d(item), name: item.name, @@ -118,10 +120,11 @@ function groupedSoprCharts( * Create value breakdown tree (Profit/Loss Created/Destroyed) * @template {{ color: Color, name: string, tree: { realized: AnyRealizedPattern } }} T * @param {readonly T[]} list + * @param {T} all * @param {(metric: string) => string} title * @returns {PartialOptionsTree} */ -function valueBreakdownTree(list, title) { +function valueBreakdownTree(list, all, title) { return [ { name: "Profit", @@ -129,7 +132,7 @@ function valueBreakdownTree(list, title) { { name: "Created", title: title("Profit Value Created"), - bottom: list.map(({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => line({ metric: tree.realized.profitValueCreated, name, @@ -141,7 +144,7 @@ function valueBreakdownTree(list, title) { { name: "Destroyed", title: title("Profit Value Destroyed"), - bottom: list.map(({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => line({ metric: tree.realized.profitValueDestroyed, name, @@ -158,7 +161,7 @@ function valueBreakdownTree(list, title) { { name: "Created", title: title("Loss Value Created"), - bottom: list.map(({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => line({ metric: tree.realized.lossValueCreated, name, @@ -170,7 +173,7 @@ function valueBreakdownTree(list, title) { { name: "Destroyed", title: title("Loss Value Destroyed"), - bottom: list.map(({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => line({ metric: tree.realized.lossValueDestroyed, name, @@ -188,15 +191,16 @@ function valueBreakdownTree(list, title) { * Create coins destroyed tree (Sum/Cumulative with Coinblocks/Coindays) * @template {{ color: Color, name: string, tree: { activity: { coinblocksDestroyed: CountPattern, coindaysDestroyed: CountPattern } } }} T * @param {readonly T[]} list + * @param {T} all * @param {(metric: string) => string} title * @returns {PartialOptionsTree} */ -function coinsDestroyedTree(list, title) { +function coinsDestroyedTree(list, all, title) { return [ { name: "Sum", title: title("Coins Destroyed"), - bottom: list.flatMap(({ color, name, tree }) => [ + bottom: flatMapCohortsWithAll(list, all, ({ color, name, tree }) => [ line({ metric: tree.activity.coinblocksDestroyed.sum, name, @@ -214,7 +218,7 @@ function coinsDestroyedTree(list, title) { { name: "Cumulative", title: title("Cumulative Coins Destroyed"), - bottom: list.flatMap(({ color, name, tree }) => [ + bottom: flatMapCohortsWithAll(list, all, ({ color, name, tree }) => [ line({ metric: tree.activity.coinblocksDestroyed.cumulative, name, @@ -276,14 +280,15 @@ function createSingleSoprTreeWithAdjusted(cohort, title) { /** * Create grouped SOPR tree with separate charts for each variant - * @template {readonly (UtxoCohortObject | CohortWithoutRelative)[]} T - * @param {T} list + * @param {readonly (UtxoCohortObject | CohortWithoutRelative)[]} list + * @param {CohortAll} all * @param {(metric: string) => string} title * @returns {PartialOptionsTree} */ -function createGroupedSoprTree(list, title) { +function createGroupedSoprTree(list, all, title) { return groupedSoprCharts( list, + all, (c) => c.tree.realized.sopr, (c) => c.tree.realized.sopr7dEma, (c) => c.tree.realized.sopr30dEma, @@ -295,15 +300,17 @@ function createGroupedSoprTree(list, title) { /** * Create grouped SOPR tree with Normal and Adjusted sub-sections * @param {readonly (CohortAll | CohortFull | CohortWithAdjusted)[]} list + * @param {CohortAll | CohortFull | CohortWithAdjusted} all * @param {(metric: string) => string} title * @returns {PartialOptionsTree} */ -function createGroupedSoprTreeWithAdjusted(list, title) { +function createGroupedSoprTreeWithAdjusted(list, all, title) { return [ { name: "Normal", tree: groupedSoprCharts( list, + all, (c) => c.tree.realized.sopr, (c) => c.tree.realized.sopr7dEma, (c) => c.tree.realized.sopr30dEma, @@ -315,6 +322,7 @@ function createGroupedSoprTreeWithAdjusted(list, title) { name: "Adjusted", tree: groupedSoprCharts( list, + all, (c) => c.tree.realized.adjustedSopr, (c) => c.tree.realized.adjustedSopr7dEma, (c) => c.tree.realized.adjustedSopr30dEma, @@ -577,15 +585,16 @@ export function createActivitySectionWithAdjusted({ cohort, title }) { * Create grouped flows tree (Profit Flow, Capitulation Flow) * @template {{ color: Color, name: string, tree: { realized: AnyRealizedPattern } }} T * @param {readonly T[]} list + * @param {T} all * @param {(metric: string) => string} title * @returns {PartialOptionsTree} */ -function groupedFlowsTree(list, title) { +function groupedFlowsTree(list, all, title) { return [ { name: "Profit", title: title("Profit Flow"), - bottom: list.map(({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => line({ metric: tree.realized.profitFlow, name, @@ -597,7 +606,7 @@ function groupedFlowsTree(list, title) { { name: "Capitulation", title: title("Capitulation Flow"), - bottom: list.map(({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => line({ metric: tree.realized.capitulationFlow, name, @@ -613,16 +622,17 @@ function groupedFlowsTree(list, title) { * Create grouped value tree (Flows, Created, Destroyed, Breakdown) * @template {{ color: Color, name: string, tree: { realized: AnyRealizedPattern } }} T * @param {readonly T[]} list + * @param {T} all * @param {(metric: string) => string} title * @returns {PartialOptionsTree} */ -function createGroupedValueTree(list, title) { +function createGroupedValueTree(list, all, title) { return [ - { name: "Flows", tree: groupedFlowsTree(list, title) }, + { name: "Flows", tree: groupedFlowsTree(list, all, title) }, { name: "Created", title: title("Value Created"), - bottom: list.map(({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => line({ metric: tree.realized.valueCreated, name, @@ -634,7 +644,7 @@ function createGroupedValueTree(list, title) { { name: "Destroyed", title: title("Value Destroyed"), - bottom: list.map(({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => line({ metric: tree.realized.valueDestroyed, name, @@ -643,22 +653,18 @@ function createGroupedValueTree(list, title) { }), ), }, - { name: "Breakdown", tree: valueBreakdownTree(list, title) }, + { name: "Breakdown", tree: valueBreakdownTree(list, all, title) }, ]; } /** - * Generic grouped activity section builder - * @template {readonly (UtxoCohortObject | CohortWithoutRelative)[]} T - * @param {Object} args - * @param {T} args.list - * @param {(metric: string) => string} args.title - * @param {PartialOptionsTree} [args.soprTree] - Optional SOPR tree override - * @param {PartialOptionsTree} [args.valueTree] - Optional value tree (defaults to basic created/destroyed) + * Grouped activity section builder + * @param {{ list: readonly (UtxoCohortObject | CohortWithoutRelative)[], all: CohortAll, title: (metric: string) => string, soprTree?: PartialOptionsTree, valueTree?: PartialOptionsTree }} args * @returns {PartialOptionsGroup} */ export function createGroupedActivitySection({ list, + all, title, soprTree, valueTree, @@ -672,14 +678,14 @@ export function createGroupedActivitySection({ { name: "14d EMA", title: title("Sent Volume 14d EMA"), - bottom: list.flatMap(({ color, name, tree }) => + bottom: flatMapCohortsWithAll(list, all, ({ color, name, tree }) => satsBtcUsd({ pattern: tree.activity.sent14dEma, name, color }), ), }, { name: "Sum", title: title("Sent Volume"), - bottom: list.flatMap(({ color, name, tree }) => + bottom: flatMapCohortsWithAll(list, all, ({ color, name, tree }) => satsBtcUsd({ pattern: { sats: tree.activity.sent.sats.sum, @@ -695,18 +701,18 @@ export function createGroupedActivitySection({ }, { name: "SOPR", - tree: soprTree ?? createGroupedSoprTree(list, title), + tree: soprTree ?? createGroupedSoprTree(list, all, title), }, { name: "Sell Side Risk", title: title("Sell Side Risk Ratio"), - bottom: createGroupedSellSideRiskSeries(list), + bottom: createGroupedSellSideRiskSeries(list, all), }, { name: "Value", - tree: valueTree ?? createGroupedValueTree(list, title), + tree: valueTree ?? createGroupedValueTree(list, all, title), }, - { name: "Coins Destroyed", tree: coinsDestroyedTree(list, title) }, + { name: "Coins Destroyed", tree: coinsDestroyedTree(list, all, title) }, ], }; } @@ -714,19 +720,20 @@ export function createGroupedActivitySection({ /** * Create grouped value tree with adjusted values (Flows, Normal, Adjusted, Breakdown) * @param {readonly (CohortAll | CohortFull | CohortWithAdjusted)[]} list + * @param {CohortAll | CohortFull | CohortWithAdjusted} all * @param {(metric: string) => string} title * @returns {PartialOptionsTree} */ -function createGroupedValueTreeWithAdjusted(list, title) { +function createGroupedValueTreeWithAdjusted(list, all, title) { return [ - { name: "Flows", tree: groupedFlowsTree(list, title) }, + { name: "Flows", tree: groupedFlowsTree(list, all, title) }, { name: "Normal", tree: [ { name: "Created", title: title("Value Created"), - bottom: list.map(({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => line({ metric: tree.realized.valueCreated, name, @@ -738,7 +745,7 @@ function createGroupedValueTreeWithAdjusted(list, title) { { name: "Destroyed", title: title("Value Destroyed"), - bottom: list.map(({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => line({ metric: tree.realized.valueDestroyed, name, @@ -755,7 +762,7 @@ function createGroupedValueTreeWithAdjusted(list, title) { { name: "Created", title: title("Adjusted Value Created"), - bottom: list.map(({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => line({ metric: tree.realized.adjustedValueCreated, name, @@ -767,7 +774,7 @@ function createGroupedValueTreeWithAdjusted(list, title) { { name: "Destroyed", title: title("Adjusted Value Destroyed"), - bottom: list.map(({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => line({ metric: tree.realized.adjustedValueDestroyed, name, @@ -778,21 +785,22 @@ function createGroupedValueTreeWithAdjusted(list, title) { }, ], }, - { name: "Breakdown", tree: valueBreakdownTree(list, title) }, + { name: "Breakdown", tree: valueBreakdownTree(list, all, title) }, ]; } /** * Grouped activity section with adjusted values (for cohorts with RealizedPattern3/4) - * @param {{ list: readonly (CohortAll | CohortFull | CohortWithAdjusted)[], title: (metric: string) => string }} args + * @param {{ list: readonly (CohortAll | CohortFull | CohortWithAdjusted)[], all: CohortAll, title: (metric: string) => string }} args * @returns {PartialOptionsGroup} */ -export function createGroupedActivitySectionWithAdjusted({ list, title }) { +export function createGroupedActivitySectionWithAdjusted({ list, all, title }) { return createGroupedActivitySection({ list, + all, title, - soprTree: createGroupedSoprTreeWithAdjusted(list, title), - valueTree: createGroupedValueTreeWithAdjusted(list, title), + soprTree: createGroupedSoprTreeWithAdjusted(list, all, title), + valueTree: createGroupedValueTreeWithAdjusted(list, all, title), }); } @@ -827,10 +835,11 @@ function createSingleSellSideRiskSeries(tree) { /** * Create sell side risk ratio series for grouped cohorts * @param {readonly CohortObject[]} list + * @param {CohortObject} all * @returns {AnyFetchedSeriesBlueprint[]} */ -function createGroupedSellSideRiskSeries(list) { - return list.flatMap(({ color, name, tree }) => [ +function createGroupedSellSideRiskSeries(list, all) { + return flatMapCohortsWithAll(list, all, ({ color, name, tree }) => [ line({ metric: tree.realized.sellSideRiskRatio, name, diff --git a/website/scripts/options/distribution/cost-basis.js b/website/scripts/options/distribution/cost-basis.js index 3f39bc158..494d0c08b 100644 --- a/website/scripts/options/distribution/cost-basis.js +++ b/website/scripts/options/distribution/cost-basis.js @@ -14,6 +14,7 @@ import { colors } from "../../utils/colors.js"; import { Unit } from "../../utils/units.js"; import { priceLines } from "../constants.js"; import { line, price } from "../series.js"; +import { mapCohortsWithAll } from "../shared.js"; /** * @param {PercentilesPattern} p @@ -185,12 +186,12 @@ function createSingleSummarySeriesWithPercentiles(cohort) { } /** - * @template {readonly { name: string, color: Color, tree: { realized: { realizedPrice: ActivePricePattern } } }[]} T - * @param {T} list + * @param {readonly CohortObject[]} list + * @param {CohortAll} all * @returns {FetchedPriceSeriesBlueprint[]} */ -function createGroupedSummarySeries(list) { - return list.map(({ name, color, tree }) => +function createGroupedSummarySeries(list, all) { + return mapCohortsWithAll(list, all, ({ name, color, tree }) => price({ metric: tree.realized.realizedPrice, name, color }), ); } @@ -305,35 +306,34 @@ export function createCostBasisSectionWithPercentiles({ cohort, title }) { } /** - * @template {readonly (UtxoCohortObject | CohortWithoutRelative)[]} T - * @param {{ list: T, title: (metric: string) => string }} args + * @param {{ list: readonly (UtxoCohortObject | CohortWithoutRelative)[], all: CohortAll, title: (metric: string) => string }} args * @returns {PartialOptionsGroup} */ -export function createGroupedCostBasisSection({ list, title }) { +export function createGroupedCostBasisSection({ list, all, title }) { return { name: "Cost Basis", tree: [ { name: "Summary", title: title("Cost Basis Summary"), - top: createGroupedSummarySeries(list), + top: createGroupedSummarySeries(list, all), }, ], }; } /** - * @param {{ list: readonly (CohortAll | CohortFull | CohortWithPercentiles)[], title: (metric: string) => string }} args + * @param {{ list: readonly (CohortAll | CohortFull | CohortWithPercentiles)[], all: CohortAll, title: (metric: string) => string }} args * @returns {PartialOptionsGroup} */ -export function createGroupedCostBasisSectionWithPercentiles({ list, title }) { +export function createGroupedCostBasisSectionWithPercentiles({ list, all, title }) { return { name: "Cost Basis", tree: [ { name: "Summary", title: title("Cost Basis Summary"), - top: createGroupedSummarySeries(list), + top: createGroupedSummarySeries(list, all), }, { name: "By Coin", @@ -341,28 +341,28 @@ export function createGroupedCostBasisSectionWithPercentiles({ list, title }) { { name: "Average", title: title("Realized Price Comparison"), - top: list.map(({ name, color, tree }) => + top: mapCohortsWithAll(list, all, ({ name, color, tree }) => price({ metric: tree.realized.realizedPrice, name, color }), ), }, { name: "Median", title: title("Cost Basis Median (BTC-weighted)"), - top: list.map(({ name, color, tree }) => + top: mapCohortsWithAll(list, all, ({ name, color, tree }) => price({ metric: tree.costBasis.percentiles.pct50, name, color }), ), }, { name: "Q3", title: title("Cost Basis Q3 (BTC-weighted)"), - top: list.map(({ name, color, tree }) => + top: mapCohortsWithAll(list, all, ({ name, color, tree }) => price({ metric: tree.costBasis.percentiles.pct75, name, color }), ), }, { name: "Q1", title: title("Cost Basis Q1 (BTC-weighted)"), - top: list.map(({ name, color, tree }) => + top: mapCohortsWithAll(list, all, ({ name, color, tree }) => price({ metric: tree.costBasis.percentiles.pct25, name, color }), ), }, @@ -374,14 +374,14 @@ export function createGroupedCostBasisSectionWithPercentiles({ list, title }) { { name: "Average", title: title("Investor Price Comparison"), - top: list.map(({ name, color, tree }) => + top: mapCohortsWithAll(list, all, ({ name, color, tree }) => price({ metric: tree.realized.investorPrice, name, color }), ), }, { name: "Median", title: title("Cost Basis Median (USD-weighted)"), - top: list.map(({ name, color, tree }) => + top: mapCohortsWithAll(list, all, ({ name, color, tree }) => price({ metric: tree.costBasis.investedCapital.pct50, name, @@ -392,7 +392,7 @@ export function createGroupedCostBasisSectionWithPercentiles({ list, title }) { { name: "Q3", title: title("Cost Basis Q3 (USD-weighted)"), - top: list.map(({ name, color, tree }) => + top: mapCohortsWithAll(list, all, ({ name, color, tree }) => price({ metric: tree.costBasis.investedCapital.pct75, name, @@ -403,7 +403,7 @@ export function createGroupedCostBasisSectionWithPercentiles({ list, title }) { { name: "Q1", title: title("Cost Basis Q1 (USD-weighted)"), - top: list.map(({ name, color, tree }) => + top: mapCohortsWithAll(list, all, ({ name, color, tree }) => price({ metric: tree.costBasis.investedCapital.pct25, name, @@ -420,7 +420,7 @@ export function createGroupedCostBasisSectionWithPercentiles({ list, title }) { name: "By Coin", title: title("Price Position (BTC-weighted)"), bottom: [ - ...list.map(({ name, color, tree }) => + ...mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.costBasis.spotCostBasisPercentile, name, @@ -435,7 +435,7 @@ export function createGroupedCostBasisSectionWithPercentiles({ list, title }) { name: "By Capital", title: title("Price Position (USD-weighted)"), bottom: [ - ...list.map(({ name, color, tree }) => + ...mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.costBasis.spotInvestedCapitalPercentile, name, diff --git a/website/scripts/options/distribution/holdings.js b/website/scripts/options/distribution/holdings.js index 73771eea4..60ab0ebe7 100644 --- a/website/scripts/options/distribution/holdings.js +++ b/website/scripts/options/distribution/holdings.js @@ -15,7 +15,7 @@ import { Unit } from "../../utils/units.js"; import { line, baseline } from "../series.js"; -import { satsBtcUsd, satsBtcUsdBaseline } from "../shared.js"; +import { satsBtcUsd, satsBtcUsdBaseline, mapCohorts, mapCohortsWithAll, flatMapCohortsWithAll } from "../shared.js"; import { colors } from "../../utils/colors.js"; import { priceLines } from "../constants.js"; @@ -60,51 +60,45 @@ function circulatingSupplyPctSeries(tree) { } /** - * Grouped UTXO count chart - * @template {{ name: string, color: Color, tree: { outputs: { utxoCount: AnyMetricPattern } } }} T - * @param {readonly T[]} list + * @param {readonly (UtxoCohortObject | CohortWithoutRelative)[]} list + * @param {CohortAll} all * @param {(metric: string) => string} title - * @returns {PartialChartOption} */ -function groupedUtxoCountChart(list, title) { +function groupedUtxoCountChart(list, all, title) { return { name: "UTXO Count", title: title("UTXO Count"), - bottom: list.map(({ name, color, tree }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.outputs.utxoCount, name, color, unit: Unit.count }), ), }; } /** - * Grouped 30d supply change chart - * @template {{ name: string, color: Color, tree: { supply: { _30dChange: AnyValuePattern } } }} T - * @param {readonly T[]} list + * @param {readonly (UtxoCohortObject | CohortWithoutRelative)[]} list + * @param {CohortAll} all * @param {(metric: string) => string} title - * @returns {PartialChartOption} */ -function grouped30dSupplyChangeChart(list, title) { +function grouped30dSupplyChangeChart(list, all, title) { return { name: "Supply", title: title("Supply 30d Change"), - bottom: list.flatMap(({ name, color, tree }) => + bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => satsBtcUsdBaseline({ pattern: tree.supply._30dChange, name, color }), ), }; } /** - * Grouped 30d UTXO count change chart - * @template {{ name: string, color: Color, tree: { outputs: { utxoCount30dChange: AnyMetricPattern } } }} T - * @param {readonly T[]} list + * @param {readonly (UtxoCohortObject | CohortWithoutRelative)[]} list + * @param {CohortAll} all * @param {(metric: string) => string} title - * @returns {PartialChartOption} */ -function grouped30dUtxoCountChangeChart(list, title) { +function grouped30dUtxoCountChangeChart(list, all, title) { return { name: "UTXO Count", title: title("UTXO Count 30d Change"), - bottom: list.map(({ name, color, tree }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => baseline({ metric: tree.outputs.utxoCount30dChange, name, unit: Unit.count, color }), ), }; @@ -444,10 +438,10 @@ export function createHoldingsSectionAddressAmount({ cohort, title }) { } /** - * @param {{ list: readonly CohortAddress[], title: (metric: string) => string }} args + * @param {{ list: readonly CohortAddress[], all: CohortAll, title: (metric: string) => string }} args * @returns {PartialOptionsGroup} */ -export function createGroupedHoldingsSectionAddress({ list, title }) { +export function createGroupedHoldingsSectionAddress({ list, all, title }) { return { name: "Holdings", tree: [ @@ -457,7 +451,7 @@ export function createGroupedHoldingsSectionAddress({ list, title }) { { name: "Total", title: title("Supply"), - bottom: list.flatMap(({ name, color, tree }) => + bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => satsBtcUsd({ pattern: tree.supply.total, name, color }), ), }, @@ -465,14 +459,14 @@ export function createGroupedHoldingsSectionAddress({ list, title }) { name: "In Profit", title: title("Supply In Profit"), bottom: [ - ...list.flatMap(({ name, color, tree }) => + ...flatMapCohortsWithAll(list, all, ({ name, color, tree }) => satsBtcUsd({ pattern: tree.unrealized.supplyInProfit, name, color, }), ), - ...list.map(({ name, color, tree }) => + ...mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.relative.supplyInProfitRelToOwnSupply, name, @@ -487,14 +481,14 @@ export function createGroupedHoldingsSectionAddress({ list, title }) { name: "In Loss", title: title("Supply In Loss"), bottom: [ - ...list.flatMap(({ name, color, tree }) => + ...flatMapCohortsWithAll(list, all, ({ name, color, tree }) => satsBtcUsd({ pattern: tree.unrealized.supplyInLoss, name, color, }), ), - ...list.map(({ name, color, tree }) => + ...mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.relative.supplyInLossRelToOwnSupply, name, @@ -507,23 +501,23 @@ export function createGroupedHoldingsSectionAddress({ list, title }) { }, ], }, - groupedUtxoCountChart(list, title), + groupedUtxoCountChart(list, all, title), { name: "Address Count", title: title("Address Count"), - bottom: list.map(({ name, color, addrCount }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, addrCount }) => line({ metric: addrCount.count, name, color, unit: Unit.count }), ), }, { name: "30d Changes", tree: [ - grouped30dSupplyChangeChart(list, title), - grouped30dUtxoCountChangeChart(list, title), + grouped30dSupplyChangeChart(list, all, title), + grouped30dUtxoCountChangeChart(list, all, title), { name: "Address Count", title: title("Address Count 30d Change"), - bottom: list.map(({ name, color, addrCount }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, addrCount }) => baseline({ metric: addrCount._30dChange, name, unit: Unit.count, color }), ), }, @@ -535,10 +529,10 @@ export function createGroupedHoldingsSectionAddress({ list, title }) { /** * Grouped holdings section for address amount cohorts (has relative supply + address count) - * @param {{ list: readonly AddressCohortObject[], title: (metric: string) => string }} args + * @param {{ list: readonly AddressCohortObject[], all: CohortAll, title: (metric: string) => string }} args * @returns {PartialOptionsGroup} */ -export function createGroupedHoldingsSectionAddressAmount({ list, title }) { +export function createGroupedHoldingsSectionAddressAmount({ list, all, title }) { return { name: "Holdings", tree: [ @@ -549,10 +543,10 @@ export function createGroupedHoldingsSectionAddressAmount({ list, title }) { name: "Total", title: title("Supply"), bottom: [ - ...list.flatMap(({ name, color, tree }) => + ...flatMapCohortsWithAll(list, all, ({ name, color, tree }) => satsBtcUsd({ pattern: tree.supply.total, name, color }), ), - ...list.map(({ name, color, tree }) => + ...mapCohorts(list, ({ name, color, tree }) => line({ metric: tree.relative.supplyRelToCirculatingSupply, name, @@ -566,14 +560,14 @@ export function createGroupedHoldingsSectionAddressAmount({ list, title }) { name: "In Profit", title: title("Supply In Profit"), bottom: [ - ...list.flatMap(({ name, color, tree }) => + ...flatMapCohortsWithAll(list, all, ({ name, color, tree }) => satsBtcUsd({ pattern: tree.unrealized.supplyInProfit, name, color, }), ), - ...list.map(({ name, color, tree }) => + ...mapCohorts(list, ({ name, color, tree }) => line({ metric: tree.relative.supplyInProfitRelToCirculatingSupply, name, @@ -581,7 +575,7 @@ export function createGroupedHoldingsSectionAddressAmount({ list, title }) { unit: Unit.pctSupply, }), ), - ...list.map(({ name, color, tree }) => + ...mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.relative.supplyInProfitRelToOwnSupply, name, @@ -596,14 +590,14 @@ export function createGroupedHoldingsSectionAddressAmount({ list, title }) { name: "In Loss", title: title("Supply In Loss"), bottom: [ - ...list.flatMap(({ name, color, tree }) => + ...flatMapCohortsWithAll(list, all, ({ name, color, tree }) => satsBtcUsd({ pattern: tree.unrealized.supplyInLoss, name, color, }), ), - ...list.map(({ name, color, tree }) => + ...mapCohorts(list, ({ name, color, tree }) => line({ metric: tree.relative.supplyInLossRelToCirculatingSupply, name, @@ -611,7 +605,7 @@ export function createGroupedHoldingsSectionAddressAmount({ list, title }) { unit: Unit.pctSupply, }), ), - ...list.map(({ name, color, tree }) => + ...mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.relative.supplyInLossRelToOwnSupply, name, @@ -624,23 +618,23 @@ export function createGroupedHoldingsSectionAddressAmount({ list, title }) { }, ], }, - groupedUtxoCountChart(list, title), + groupedUtxoCountChart(list, all, title), { name: "Address Count", title: title("Address Count"), - bottom: list.map(({ name, color, addrCount }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, addrCount }) => line({ metric: addrCount.count, name, color, unit: Unit.count }), ), }, { name: "30d Changes", tree: [ - grouped30dSupplyChangeChart(list, title), - grouped30dUtxoCountChangeChart(list, title), + grouped30dSupplyChangeChart(list, all, title), + grouped30dUtxoCountChangeChart(list, all, title), { name: "Address Count", title: title("Address Count 30d Change"), - bottom: list.map(({ name, color, addrCount }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, addrCount }) => baseline({ metric: addrCount._30dChange, name, unit: Unit.count, color }), ), }, @@ -651,11 +645,10 @@ export function createGroupedHoldingsSectionAddressAmount({ list, title }) { } /** - * @template {readonly (UtxoCohortObject | CohortWithoutRelative)[]} T - * @param {{ list: T, title: (metric: string) => string }} args + * @param {{ list: readonly (UtxoCohortObject | CohortWithoutRelative)[], all: CohortAll, title: (metric: string) => string }} args * @returns {PartialOptionsGroup} */ -export function createGroupedHoldingsSection({ list, title }) { +export function createGroupedHoldingsSection({ list, all, title }) { return { name: "Holdings", tree: [ @@ -665,14 +658,14 @@ export function createGroupedHoldingsSection({ list, title }) { { name: "Total", title: title("Supply"), - bottom: list.flatMap(({ name, color, tree }) => + bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => satsBtcUsd({ pattern: tree.supply.total, name, color }), ), }, { name: "In Profit", title: title("Supply In Profit"), - bottom: list.flatMap(({ name, color, tree }) => + bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => satsBtcUsd({ pattern: tree.unrealized.supplyInProfit, name, @@ -683,7 +676,7 @@ export function createGroupedHoldingsSection({ list, title }) { { name: "In Loss", title: title("Supply In Loss"), - bottom: list.flatMap(({ name, color, tree }) => + bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => satsBtcUsd({ pattern: tree.unrealized.supplyInLoss, name, @@ -693,12 +686,12 @@ export function createGroupedHoldingsSection({ list, title }) { }, ], }, - groupedUtxoCountChart(list, title), + groupedUtxoCountChart(list, all, title), { name: "30d Changes", tree: [ - grouped30dSupplyChangeChart(list, title), - grouped30dUtxoCountChangeChart(list, title), + grouped30dSupplyChangeChart(list, all, title), + grouped30dUtxoCountChangeChart(list, all, title), ], }, ], @@ -707,10 +700,10 @@ export function createGroupedHoldingsSection({ list, title }) { /** * Grouped holdings section with % of Own Supply only (for cohorts without % of Circulating) - * @param {{ list: readonly (CohortAgeRange | CohortBasicWithoutMarketCap)[], title: (metric: string) => string }} args + * @param {{ list: readonly (CohortAgeRange | CohortBasicWithoutMarketCap)[], all: CohortAll, title: (metric: string) => string }} args * @returns {PartialOptionsGroup} */ -export function createGroupedHoldingsSectionWithOwnSupply({ list, title }) { +export function createGroupedHoldingsSectionWithOwnSupply({ list, all, title }) { return { name: "Holdings", tree: [ @@ -721,14 +714,14 @@ export function createGroupedHoldingsSectionWithOwnSupply({ list, title }) { name: "In Profit", title: title("Supply In Profit"), bottom: [ - ...list.flatMap(({ name, color, tree }) => + ...flatMapCohortsWithAll(list, all, ({ name, color, tree }) => satsBtcUsd({ pattern: tree.unrealized.supplyInProfit, name, color, }), ), - ...list.map(({ name, color, tree }) => + ...mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.relative.supplyInProfitRelToOwnSupply, name, @@ -743,14 +736,14 @@ export function createGroupedHoldingsSectionWithOwnSupply({ list, title }) { name: "In Loss", title: title("Supply In Loss"), bottom: [ - ...list.flatMap(({ name, color, tree }) => + ...flatMapCohortsWithAll(list, all, ({ name, color, tree }) => satsBtcUsd({ pattern: tree.unrealized.supplyInLoss, name, color, }), ), - ...list.map(({ name, color, tree }) => + ...mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.relative.supplyInLossRelToOwnSupply, name, @@ -764,18 +757,18 @@ export function createGroupedHoldingsSectionWithOwnSupply({ list, title }) { { name: "Total", title: title("Supply"), - bottom: list.flatMap(({ name, color, tree }) => + bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => satsBtcUsd({ pattern: tree.supply.total, name, color }), ), }, ], }, - groupedUtxoCountChart(list, title), + groupedUtxoCountChart(list, all, title), { name: "30d Changes", tree: [ - grouped30dSupplyChangeChart(list, title), - grouped30dUtxoCountChangeChart(list, title), + grouped30dSupplyChangeChart(list, all, title), + grouped30dUtxoCountChangeChart(list, all, title), ], }, ], @@ -783,10 +776,10 @@ export function createGroupedHoldingsSectionWithOwnSupply({ list, title }) { } /** - * @param {{ list: readonly (CohortFull | CohortWithAdjusted | CohortBasicWithMarketCap | CohortMinAge)[], title: (metric: string) => string }} args + * @param {{ list: readonly (CohortFull | CohortWithAdjusted | CohortBasicWithMarketCap | CohortMinAge)[], all: CohortAll, title: (metric: string) => string }} args * @returns {PartialOptionsGroup} */ -export function createGroupedHoldingsSectionWithRelative({ list, title }) { +export function createGroupedHoldingsSectionWithRelative({ list, all, title }) { return { name: "Holdings", tree: [ @@ -797,10 +790,10 @@ export function createGroupedHoldingsSectionWithRelative({ list, title }) { name: "Total", title: title("Supply"), bottom: [ - ...list.flatMap(({ name, color, tree }) => + ...flatMapCohortsWithAll(list, all, ({ name, color, tree }) => satsBtcUsd({ pattern: tree.supply.total, name, color }), ), - ...list.map(({ name, color, tree }) => + ...mapCohorts(list, ({ name, color, tree }) => line({ metric: tree.relative.supplyRelToCirculatingSupply, name, @@ -814,14 +807,14 @@ export function createGroupedHoldingsSectionWithRelative({ list, title }) { name: "In Profit", title: title("Supply In Profit"), bottom: [ - ...list.flatMap(({ name, color, tree }) => + ...flatMapCohortsWithAll(list, all, ({ name, color, tree }) => satsBtcUsd({ pattern: tree.unrealized.supplyInProfit, name, color, }), ), - ...list.map(({ name, color, tree }) => + ...mapCohorts(list, ({ name, color, tree }) => line({ metric: tree.relative.supplyInProfitRelToCirculatingSupply, name, @@ -829,7 +822,7 @@ export function createGroupedHoldingsSectionWithRelative({ list, title }) { unit: Unit.pctSupply, }), ), - ...list.map(({ name, color, tree }) => + ...mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.relative.supplyInProfitRelToOwnSupply, name, @@ -844,14 +837,14 @@ export function createGroupedHoldingsSectionWithRelative({ list, title }) { name: "In Loss", title: title("Supply In Loss"), bottom: [ - ...list.flatMap(({ name, color, tree }) => + ...flatMapCohortsWithAll(list, all, ({ name, color, tree }) => satsBtcUsd({ pattern: tree.unrealized.supplyInLoss, name, color, }), ), - ...list.map(({ name, color, tree }) => + ...mapCohorts(list, ({ name, color, tree }) => line({ metric: tree.relative.supplyInLossRelToCirculatingSupply, name, @@ -859,7 +852,7 @@ export function createGroupedHoldingsSectionWithRelative({ list, title }) { unit: Unit.pctSupply, }), ), - ...list.map(({ name, color, tree }) => + ...mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.relative.supplyInLossRelToOwnSupply, name, @@ -872,12 +865,12 @@ export function createGroupedHoldingsSectionWithRelative({ list, title }) { }, ], }, - groupedUtxoCountChart(list, title), + groupedUtxoCountChart(list, all, title), { name: "30d Changes", tree: [ - grouped30dSupplyChangeChart(list, title), - grouped30dUtxoCountChangeChart(list, title), + grouped30dSupplyChangeChart(list, all, title), + grouped30dUtxoCountChangeChart(list, all, title), ], }, ], diff --git a/website/scripts/options/distribution/index.js b/website/scripts/options/distribution/index.js index 4a895e3c4..df6cfbecc 100644 --- a/website/scripts/options/distribution/index.js +++ b/website/scripts/options/distribution/index.js @@ -70,427 +70,458 @@ import { export { buildCohortData } from "./data.js"; // ============================================================================ -// Folder Builders +// Single Cohort Folder Builders // ============================================================================ /** - * All folder: for the special "All" cohort (adjustedSopr + percentiles + RelToMarketCap) - * @param {CohortAll} args + * All folder: for the special "All" cohort + * @param {CohortAll} cohort * @returns {PartialOptionsGroup} */ -export function createCohortFolderAll(args) { - const title = formatCohortTitle(args.name); +export function createCohortFolderAll(cohort) { + const title = formatCohortTitle(cohort.name); return { - name: args.name || "all", + name: cohort.name || "all", tree: [ - createHoldingsSectionAll({ cohort: args, title }), - createValuationSectionFull({ cohort: args, title }), - createPricesSectionFull({ cohort: args, title }), - createCostBasisSectionWithPercentiles({ cohort: args, title }), - createProfitabilitySectionAll({ cohort: args, title }), - createActivitySectionWithAdjusted({ cohort: args, title }), + createHoldingsSectionAll({ cohort, title }), + createValuationSectionFull({ cohort, title }), + createPricesSectionFull({ cohort, title }), + createCostBasisSectionWithPercentiles({ cohort, title }), + createProfitabilitySectionAll({ cohort, title }), + createActivitySectionWithAdjusted({ cohort, title }), ], }; } /** - * Full folder: adjustedSopr + percentiles + RelToMarketCap (term.short only) - * @param {CohortFull | CohortGroupFull} args + * Full folder: adjustedSopr + percentiles + RelToMarketCap + * @param {CohortFull} cohort * @returns {PartialOptionsGroup} */ -export function createCohortFolderFull(args) { - if ("list" in args) { - const { list } = args; - const title = formatCohortTitle(args.title); - return { - name: args.name || "all", - tree: [ - createGroupedHoldingsSectionWithRelative({ list, title }), - createGroupedValuationSectionWithOwnMarketCap({ list, title }), - createGroupedPricesSection({ list, title }), - createGroupedCostBasisSectionWithPercentiles({ list, title }), - createGroupedProfitabilitySectionWithNupl({ list, title }), - createGroupedActivitySectionWithAdjusted({ list, title }), - ], - }; - } - const title = formatCohortTitle(args.name); +export function createCohortFolderFull(cohort) { + const title = formatCohortTitle(cohort.name); return { - name: args.name || "all", + name: cohort.name || "all", tree: [ - createHoldingsSectionWithRelative({ cohort: args, title }), - createValuationSectionFull({ cohort: args, title }), - createPricesSectionFull({ cohort: args, title }), - createCostBasisSectionWithPercentiles({ cohort: args, title }), - createProfitabilitySectionFull({ cohort: args, title }), - createActivitySectionWithAdjusted({ cohort: args, title }), + createHoldingsSectionWithRelative({ cohort, title }), + createValuationSectionFull({ cohort, title }), + createPricesSectionFull({ cohort, title }), + createCostBasisSectionWithPercentiles({ cohort, title }), + createProfitabilitySectionFull({ cohort, title }), + createActivitySectionWithAdjusted({ cohort, title }), ], }; } /** - * Adjusted folder: adjustedSopr only, no percentiles (maxAge.*) - * Has Peak Regret metrics like minAge - * @param {CohortWithAdjusted | CohortGroupWithAdjusted} args + * Adjusted folder: adjustedSopr only, no percentiles + * @param {CohortWithAdjusted} cohort * @returns {PartialOptionsGroup} */ -export function createCohortFolderWithAdjusted(args) { - if ("list" in args) { - const { list } = args; - const title = formatCohortTitle(args.title); - return { - name: args.name || "all", - tree: [ - createGroupedHoldingsSectionWithRelative({ list, title }), - createGroupedValuationSection({ list, title }), - createGroupedPricesSection({ list, title }), - createGroupedCostBasisSection({ list, title }), - createGroupedProfitabilitySectionWithPeakRegret({ list, title }), - createGroupedActivitySectionWithAdjusted({ list, title }), - ], - }; - } - const title = formatCohortTitle(args.name); +export function createCohortFolderWithAdjusted(cohort) { + const title = formatCohortTitle(cohort.name); return { - name: args.name || "all", + name: cohort.name || "all", tree: [ - createHoldingsSectionWithRelative({ cohort: args, title }), - createValuationSection({ cohort: args, title }), - createPricesSectionBasic({ cohort: args, title }), - createCostBasisSection({ cohort: args, title }), - createProfitabilitySectionWithPeakRegret({ cohort: args, title }), - createActivitySectionWithAdjusted({ cohort: args, title }), + createHoldingsSectionWithRelative({ cohort, title }), + createValuationSection({ cohort, title }), + createPricesSectionBasic({ cohort, title }), + createCostBasisSection({ cohort, title }), + createProfitabilitySectionWithPeakRegret({ cohort, title }), + createActivitySectionWithAdjusted({ cohort, title }), ], }; } /** - * Folder for cohorts with nupl + percentiles (no longer used for term.long which has own folder) - * @param {CohortWithNuplPercentiles | CohortGroupWithNuplPercentiles} args + * Folder for cohorts with nupl + percentiles + * @param {CohortWithNuplPercentiles} cohort * @returns {PartialOptionsGroup} */ -export function createCohortFolderWithNupl(args) { - if ("list" in args) { - const { list } = args; - const title = formatCohortTitle(args.title); - return { - name: args.name || "all", - tree: [ - createGroupedHoldingsSectionWithRelative({ list, title }), - createGroupedValuationSection({ list, title }), - createGroupedPricesSection({ list, title }), - createGroupedCostBasisSectionWithPercentiles({ list, title }), - createGroupedProfitabilitySectionWithNupl({ list, title }), - createGroupedActivitySection({ list, title }), - ], - }; - } - const title = formatCohortTitle(args.name); +export function createCohortFolderWithNupl(cohort) { + const title = formatCohortTitle(cohort.name); return { - name: args.name || "all", + name: cohort.name || "all", tree: [ - createHoldingsSectionWithRelative({ cohort: args, title }), - createValuationSectionFull({ cohort: args, title }), - createPricesSectionFull({ cohort: args, title }), - createCostBasisSectionWithPercentiles({ cohort: args, title }), - createProfitabilitySectionWithNupl({ cohort: args, title }), - createActivitySection({ cohort: args, title }), + createHoldingsSectionWithRelative({ cohort, title }), + createValuationSectionFull({ cohort, title }), + createPricesSectionFull({ cohort, title }), + createCostBasisSectionWithPercentiles({ cohort, title }), + createProfitabilitySectionWithNupl({ cohort, title }), + createActivitySection({ cohort, title }), ], }; } /** - * LongTerm folder: term.long (has own market cap + NUPL + peak regret + P/L ratio) - * @param {CohortLongTerm | CohortGroupLongTerm} args + * LongTerm folder: has own market cap + NUPL + peak regret + P/L ratio + * @param {CohortLongTerm} cohort * @returns {PartialOptionsGroup} */ -export function createCohortFolderLongTerm(args) { - if ("list" in args) { - const { list } = args; - const title = formatCohortTitle(args.title); - return { - name: args.name || "all", - tree: [ - createGroupedHoldingsSectionWithRelative({ list, title }), - createGroupedValuationSectionWithOwnMarketCap({ list, title }), - createGroupedPricesSection({ list, title }), - createGroupedCostBasisSectionWithPercentiles({ list, title }), - createGroupedProfitabilitySectionLongTerm({ list, title }), - createGroupedActivitySection({ list, title }), - ], - }; - } - const title = formatCohortTitle(args.name); +export function createCohortFolderLongTerm(cohort) { + const title = formatCohortTitle(cohort.name); return { - name: args.name || "all", + name: cohort.name || "all", tree: [ - createHoldingsSectionWithRelative({ cohort: args, title }), - createValuationSectionFull({ cohort: args, title }), - createPricesSectionFull({ cohort: args, title }), - createCostBasisSectionWithPercentiles({ cohort: args, title }), - createProfitabilitySectionLongTerm({ cohort: args, title }), - createActivitySection({ cohort: args, title }), + createHoldingsSectionWithRelative({ cohort, title }), + createValuationSectionFull({ cohort, title }), + createPricesSectionFull({ cohort, title }), + createCostBasisSectionWithPercentiles({ cohort, title }), + createProfitabilitySectionLongTerm({ cohort, title }), + createActivitySection({ cohort, title }), ], }; } /** - * Age range folder: ageRange.* (no nupl via RelativePattern2) - * @param {CohortAgeRange | CohortGroupAgeRange} args + * Age range folder: no nupl + * @param {CohortAgeRange} cohort * @returns {PartialOptionsGroup} */ -export function createCohortFolderAgeRange(args) { - if ("list" in args) { - const { list } = args; - const title = formatCohortTitle(args.title); - return { - name: args.name || "all", - tree: [ - createGroupedHoldingsSectionWithOwnSupply({ list, title }), - createGroupedValuationSectionWithOwnMarketCap({ list, title }), - createGroupedPricesSection({ list, title }), - createGroupedCostBasisSectionWithPercentiles({ list, title }), - createGroupedProfitabilitySectionWithInvestedCapitalPct({ - list, - title, - }), - createGroupedActivitySection({ list, title }), - ], - }; - } - const title = formatCohortTitle(args.name); +export function createCohortFolderAgeRange(cohort) { + const title = formatCohortTitle(cohort.name); return { - name: args.name || "all", + name: cohort.name || "all", tree: [ - createHoldingsSectionWithOwnSupply({ cohort: args, title }), - createValuationSectionFull({ cohort: args, title }), - createPricesSectionFull({ cohort: args, title }), - createCostBasisSectionWithPercentiles({ cohort: args, title }), - createProfitabilitySectionWithInvestedCapitalPct({ cohort: args, title }), - createActivitySection({ cohort: args, title }), + createHoldingsSectionWithOwnSupply({ cohort, title }), + createValuationSectionFull({ cohort, title }), + createPricesSectionFull({ cohort, title }), + createCostBasisSectionWithPercentiles({ cohort, title }), + createProfitabilitySectionWithInvestedCapitalPct({ cohort, title }), + createActivitySection({ cohort, title }), ], }; } /** - * MinAge folder - has peakRegret in unrealized (minAge.*) - * @param {CohortMinAge | CohortGroupMinAge} args + * MinAge folder: has peakRegret in unrealized + * @param {CohortMinAge} cohort * @returns {PartialOptionsGroup} */ -export function createCohortFolderMinAge(args) { - if ("list" in args) { - const { list } = args; - const title = formatCohortTitle(args.title); - return { - name: args.name || "all", - tree: [ - createGroupedHoldingsSectionWithRelative({ list, title }), - createGroupedValuationSection({ list, title }), - createGroupedPricesSection({ list, title }), - createGroupedCostBasisSection({ list, title }), - createGroupedProfitabilitySectionWithPeakRegret({ list, title }), - createGroupedActivitySection({ list, title }), - ], - }; - } - const title = formatCohortTitle(args.name); +export function createCohortFolderMinAge(cohort) { + const title = formatCohortTitle(cohort.name); return { - name: args.name || "all", + name: cohort.name || "all", tree: [ - createHoldingsSectionWithRelative({ cohort: args, title }), - createValuationSection({ cohort: args, title }), - createPricesSectionBasic({ cohort: args, title }), - createCostBasisSection({ cohort: args, title }), - createProfitabilitySectionWithPeakRegret({ cohort: args, title }), - createActivitySection({ cohort: args, title }), + createHoldingsSectionWithRelative({ cohort, title }), + createValuationSection({ cohort, title }), + createPricesSectionBasic({ cohort, title }), + createCostBasisSection({ cohort, title }), + createProfitabilitySectionWithPeakRegret({ cohort, title }), + createActivitySection({ cohort, title }), ], }; } /** - * Basic folder WITH RelToMarketCap (geAmount.*, ltAmount.*) - * @param {CohortBasicWithMarketCap | CohortGroupBasicWithMarketCap} args + * Basic folder WITH RelToMarketCap + * @param {CohortBasicWithMarketCap} cohort * @returns {PartialOptionsGroup} */ -export function createCohortFolderBasicWithMarketCap(args) { - if ("list" in args) { - const { list } = args; - const title = formatCohortTitle(args.title); - return { - name: args.name || "all", - tree: [ - createGroupedHoldingsSectionWithRelative({ list, title }), - createGroupedValuationSection({ list, title }), - createGroupedPricesSection({ list, title }), - createGroupedCostBasisSection({ list, title }), - createGroupedProfitabilitySectionWithNupl({ list, title }), - createGroupedActivitySection({ list, title }), - ], - }; - } - const title = formatCohortTitle(args.name); +export function createCohortFolderBasicWithMarketCap(cohort) { + const title = formatCohortTitle(cohort.name); return { - name: args.name || "all", + name: cohort.name || "all", tree: [ - createHoldingsSectionWithRelative({ cohort: args, title }), - createValuationSection({ cohort: args, title }), - createPricesSectionBasic({ cohort: args, title }), - createCostBasisSection({ cohort: args, title }), - createProfitabilitySectionWithNupl({ cohort: args, title }), - createActivitySection({ cohort: args, title }), + createHoldingsSectionWithRelative({ cohort, title }), + createValuationSection({ cohort, title }), + createPricesSectionBasic({ cohort, title }), + createCostBasisSection({ cohort, title }), + createProfitabilitySectionWithNupl({ cohort, title }), + createActivitySection({ cohort, title }), ], }; } /** - * Basic folder WITHOUT RelToMarketCap (epoch.*, amountRange.*, year.*) - * @param {CohortBasicWithoutMarketCap | CohortGroupBasicWithoutMarketCap} args + * Basic folder WITHOUT RelToMarketCap + * @param {CohortBasicWithoutMarketCap} cohort * @returns {PartialOptionsGroup} */ -export function createCohortFolderBasicWithoutMarketCap(args) { - if ("list" in args) { - const { list } = args; - const title = formatCohortTitle(args.title); - return { - name: args.name || "all", - tree: [ - createGroupedHoldingsSectionWithOwnSupply({ list, title }), - createGroupedValuationSection({ list, title }), - createGroupedPricesSection({ list, title }), - createGroupedCostBasisSection({ list, title }), - createGroupedProfitabilitySectionBasicWithInvestedCapitalPct({ - list, - title, - }), - createGroupedActivitySection({ list, title }), - ], - }; - } - const title = formatCohortTitle(args.name); +export function createCohortFolderBasicWithoutMarketCap(cohort) { + const title = formatCohortTitle(cohort.name); return { - name: args.name || "all", + name: cohort.name || "all", tree: [ - createHoldingsSectionWithOwnSupply({ cohort: args, title }), - createValuationSection({ cohort: args, title }), - createPricesSectionBasic({ cohort: args, title }), - createCostBasisSection({ cohort: args, title }), - createProfitabilitySectionBasicWithInvestedCapitalPct({ - cohort: args, - title, - }), - createActivitySection({ cohort: args, title }), + createHoldingsSectionWithOwnSupply({ cohort, title }), + createValuationSection({ cohort, title }), + createPricesSectionBasic({ cohort, title }), + createCostBasisSection({ cohort, title }), + createProfitabilitySectionBasicWithInvestedCapitalPct({ cohort, title }), + createActivitySection({ cohort, title }), ], }; } /** - * Address folder: like basic but with address count (addressable type cohorts) - * Has invested capital percentage metrics - * @param {CohortAddress | CohortGroupAddress} args + * Address folder: like basic but with address count + * @param {CohortAddress} cohort * @returns {PartialOptionsGroup} */ -export function createCohortFolderAddress(args) { - if ("list" in args) { - const { list } = args; - const title = formatCohortTitle(args.title); - return { - name: args.name || "all", - tree: [ - createGroupedHoldingsSectionAddress({ list, title }), - createGroupedValuationSection({ list, title }), - createGroupedPricesSection({ list, title }), - createGroupedCostBasisSection({ list, title }), - createGroupedProfitabilitySectionBasicWithInvestedCapitalPct({ - list, - title, - }), - createGroupedActivitySection({ list, title }), - ], - }; - } - const title = formatCohortTitle(args.name); +export function createCohortFolderAddress(cohort) { + const title = formatCohortTitle(cohort.name); return { - name: args.name || "all", + name: cohort.name || "all", tree: [ - createHoldingsSectionAddress({ cohort: args, title }), - createValuationSection({ cohort: args, title }), - createPricesSectionBasic({ cohort: args, title }), - createCostBasisSection({ cohort: args, title }), - createProfitabilitySectionBasicWithInvestedCapitalPct({ - cohort: args, - title, - }), - createActivitySection({ cohort: args, title }), + createHoldingsSectionAddress({ cohort, title }), + createValuationSection({ cohort, title }), + createPricesSectionBasic({ cohort, title }), + createCostBasisSection({ cohort, title }), + createProfitabilitySectionBasicWithInvestedCapitalPct({ cohort, title }), + createActivitySection({ cohort, title }), ], }; } /** - * Folder for cohorts WITHOUT relative section (edge case types: empty, p2ms, unknown) - * @param {CohortWithoutRelative | CohortGroupWithoutRelative} args + * Folder for cohorts WITHOUT relative section + * @param {CohortWithoutRelative} cohort * @returns {PartialOptionsGroup} */ -export function createCohortFolderWithoutRelative(args) { - if ("list" in args) { - const { list } = args; - const title = formatCohortTitle(args.title); - return { - name: args.name || "all", - tree: [ - createGroupedHoldingsSection({ list, title }), - createGroupedValuationSection({ list, title }), - createGroupedPricesSection({ list, title }), - createGroupedCostBasisSection({ list, title }), - createGroupedProfitabilitySection({ list, title }), - createGroupedActivitySection({ list, title }), - ], - }; - } - const title = formatCohortTitle(args.name); +export function createCohortFolderWithoutRelative(cohort) { + const title = formatCohortTitle(cohort.name); return { - name: args.name || "all", + name: cohort.name || "all", tree: [ - createHoldingsSection({ cohort: args, title }), - createValuationSection({ cohort: args, title }), - createPricesSectionBasic({ cohort: args, title }), - createCostBasisSection({ cohort: args, title }), - createProfitabilitySection({ cohort: args, title }), - createActivitySection({ cohort: args, title }), + createHoldingsSection({ cohort, title }), + createValuationSection({ cohort, title }), + createPricesSectionBasic({ cohort, title }), + createCostBasisSection({ cohort, title }), + createProfitabilitySection({ cohort, title }), + createActivitySection({ cohort, title }), ], }; } /** - * Address amount cohort folder - for address balance cohorts (has NUPL + addrCount) - * @param {AddressCohortObject | AddressCohortGroupObject} args + * Address amount cohort folder: has NUPL + addrCount + * @param {AddressCohortObject} cohort * @returns {PartialOptionsGroup} */ -export function createAddressCohortFolder(args) { - if ("list" in args) { - const { list } = args; - const title = formatCohortTitle(args.title); - return { - name: args.name || "all", - tree: [ - createGroupedHoldingsSectionAddressAmount({ list, title }), - createGroupedValuationSection({ list, title }), - createGroupedPricesSection({ list, title }), - createGroupedCostBasisSection({ list, title }), - createGroupedProfitabilitySectionWithNupl({ list, title }), - createGroupedActivitySection({ list, title }), - ], - }; - } - const title = formatCohortTitle(args.name); +export function createAddressCohortFolder(cohort) { + const title = formatCohortTitle(cohort.name); return { - name: args.name || "all", + name: cohort.name || "all", tree: [ - createHoldingsSectionAddressAmount({ cohort: args, title }), - createValuationSection({ cohort: args, title }), - createPricesSectionBasic({ cohort: args, title }), - createCostBasisSection({ cohort: args, title }), - createProfitabilitySectionWithNupl({ cohort: args, title }), - createActivitySection({ cohort: args, title }), + createHoldingsSectionAddressAmount({ cohort, title }), + createValuationSection({ cohort, title }), + createPricesSectionBasic({ cohort, title }), + createCostBasisSection({ cohort, title }), + createProfitabilitySectionWithNupl({ cohort, title }), + createActivitySection({ cohort, title }), + ], + }; +} + +// ============================================================================ +// Grouped Cohort Folder Builders +// ============================================================================ + +/** + * @param {CohortGroupFull} args + * @returns {PartialOptionsGroup} + */ +export function createGroupedCohortFolderFull({ name, title: groupTitle, list, all }) { + const title = formatCohortTitle(groupTitle); + return { + name: name || "all", + tree: [ + createGroupedHoldingsSectionWithRelative({ list, all, title }), + createGroupedValuationSectionWithOwnMarketCap({ list, all, title }), + createGroupedPricesSection({ list, all, title }), + createGroupedCostBasisSectionWithPercentiles({ list, all, title }), + createGroupedProfitabilitySectionWithNupl({ list, all, title }), + createGroupedActivitySectionWithAdjusted({ list, all, title }), + ], + }; +} + +/** + * @param {CohortGroupWithAdjusted} args + * @returns {PartialOptionsGroup} + */ +export function createGroupedCohortFolderWithAdjusted({ name, title: groupTitle, list, all }) { + const title = formatCohortTitle(groupTitle); + return { + name: name || "all", + tree: [ + createGroupedHoldingsSectionWithRelative({ list, all, title }), + createGroupedValuationSection({ list, all, title }), + createGroupedPricesSection({ list, all, title }), + createGroupedCostBasisSection({ list, all, title }), + createGroupedProfitabilitySectionWithPeakRegret({ list, all, title }), + createGroupedActivitySectionWithAdjusted({ list, all, title }), + ], + }; +} + +/** + * @param {CohortGroupWithNuplPercentiles} args + * @returns {PartialOptionsGroup} + */ +export function createGroupedCohortFolderWithNupl({ name, title: groupTitle, list, all }) { + const title = formatCohortTitle(groupTitle); + return { + name: name || "all", + tree: [ + createGroupedHoldingsSectionWithRelative({ list, all, title }), + createGroupedValuationSection({ list, all, title }), + createGroupedPricesSection({ list, all, title }), + createGroupedCostBasisSectionWithPercentiles({ list, all, title }), + createGroupedProfitabilitySectionWithNupl({ list, all, title }), + createGroupedActivitySection({ list, all, title }), + ], + }; +} + +/** + * @param {CohortGroupLongTerm} args + * @returns {PartialOptionsGroup} + */ +export function createGroupedCohortFolderLongTerm({ name, title: groupTitle, list, all }) { + const title = formatCohortTitle(groupTitle); + return { + name: name || "all", + tree: [ + createGroupedHoldingsSectionWithRelative({ list, all, title }), + createGroupedValuationSectionWithOwnMarketCap({ list, all, title }), + createGroupedPricesSection({ list, all, title }), + createGroupedCostBasisSectionWithPercentiles({ list, all, title }), + createGroupedProfitabilitySectionLongTerm({ list, all, title }), + createGroupedActivitySection({ list, all, title }), + ], + }; +} + +/** + * @param {CohortGroupAgeRange} args + * @returns {PartialOptionsGroup} + */ +export function createGroupedCohortFolderAgeRange({ name, title: groupTitle, list, all }) { + const title = formatCohortTitle(groupTitle); + return { + name: name || "all", + tree: [ + createGroupedHoldingsSectionWithOwnSupply({ list, all, title }), + createGroupedValuationSectionWithOwnMarketCap({ list, all, title }), + createGroupedPricesSection({ list, all, title }), + createGroupedCostBasisSectionWithPercentiles({ list, all, title }), + createGroupedProfitabilitySectionWithInvestedCapitalPct({ list, all, title }), + createGroupedActivitySection({ list, all, title }), + ], + }; +} + +/** + * @param {CohortGroupMinAge} args + * @returns {PartialOptionsGroup} + */ +export function createGroupedCohortFolderMinAge({ name, title: groupTitle, list, all }) { + const title = formatCohortTitle(groupTitle); + return { + name: name || "all", + tree: [ + createGroupedHoldingsSectionWithRelative({ list, all, title }), + createGroupedValuationSection({ list, all, title }), + createGroupedPricesSection({ list, all, title }), + createGroupedCostBasisSection({ list, all, title }), + createGroupedProfitabilitySectionWithPeakRegret({ list, all, title }), + createGroupedActivitySection({ list, all, title }), + ], + }; +} + +/** + * @param {CohortGroupBasicWithMarketCap} args + * @returns {PartialOptionsGroup} + */ +export function createGroupedCohortFolderBasicWithMarketCap({ name, title: groupTitle, list, all }) { + const title = formatCohortTitle(groupTitle); + return { + name: name || "all", + tree: [ + createGroupedHoldingsSectionWithRelative({ list, all, title }), + createGroupedValuationSection({ list, all, title }), + createGroupedPricesSection({ list, all, title }), + createGroupedCostBasisSection({ list, all, title }), + createGroupedProfitabilitySectionWithNupl({ list, all, title }), + createGroupedActivitySection({ list, all, title }), + ], + }; +} + +/** + * @param {CohortGroupBasicWithoutMarketCap} args + * @returns {PartialOptionsGroup} + */ +export function createGroupedCohortFolderBasicWithoutMarketCap({ name, title: groupTitle, list, all }) { + const title = formatCohortTitle(groupTitle); + return { + name: name || "all", + tree: [ + createGroupedHoldingsSectionWithOwnSupply({ list, all, title }), + createGroupedValuationSection({ list, all, title }), + createGroupedPricesSection({ list, all, title }), + createGroupedCostBasisSection({ list, all, title }), + createGroupedProfitabilitySectionBasicWithInvestedCapitalPct({ list, all, title }), + createGroupedActivitySection({ list, all, title }), + ], + }; +} + +/** + * @param {CohortGroupAddress} args + * @returns {PartialOptionsGroup} + */ +export function createGroupedCohortFolderAddress({ name, title: groupTitle, list, all }) { + const title = formatCohortTitle(groupTitle); + return { + name: name || "all", + tree: [ + createGroupedHoldingsSectionAddress({ list, all, title }), + createGroupedValuationSection({ list, all, title }), + createGroupedPricesSection({ list, all, title }), + createGroupedCostBasisSection({ list, all, title }), + createGroupedProfitabilitySectionBasicWithInvestedCapitalPct({ list, all, title }), + createGroupedActivitySection({ list, all, title }), + ], + }; +} + +/** + * @param {CohortGroupWithoutRelative} args + * @returns {PartialOptionsGroup} + */ +export function createGroupedCohortFolderWithoutRelative({ name, title: groupTitle, list, all }) { + const title = formatCohortTitle(groupTitle); + return { + name: name || "all", + tree: [ + createGroupedHoldingsSection({ list, all, title }), + createGroupedValuationSection({ list, all, title }), + createGroupedPricesSection({ list, all, title }), + createGroupedCostBasisSection({ list, all, title }), + createGroupedProfitabilitySection({ list, all, title }), + createGroupedActivitySection({ list, all, title }), + ], + }; +} + +/** + * @param {AddressCohortGroupObject} args + * @returns {PartialOptionsGroup} + */ +export function createGroupedAddressCohortFolder({ name, title: groupTitle, list, all }) { + const title = formatCohortTitle(groupTitle); + return { + name: name || "all", + tree: [ + createGroupedHoldingsSectionAddressAmount({ list, all, title }), + createGroupedValuationSection({ list, all, title }), + createGroupedPricesSection({ list, all, title }), + createGroupedCostBasisSection({ list, all, title }), + createGroupedProfitabilitySectionWithNupl({ list, all, title }), + createGroupedActivitySection({ list, all, title }), ], }; } diff --git a/website/scripts/options/distribution/prices.js b/website/scripts/options/distribution/prices.js index 87aebb2c5..39bdcf568 100644 --- a/website/scripts/options/distribution/prices.js +++ b/website/scripts/options/distribution/prices.js @@ -14,7 +14,7 @@ */ import { colors } from "../../utils/colors.js"; -import { createPriceRatioCharts } from "../shared.js"; +import { createPriceRatioCharts, mapCohortsWithAll } from "../shared.js"; import { baseline, price } from "../series.js"; import { Unit } from "../../utils/units.js"; @@ -148,11 +148,10 @@ export function createPricesSectionBasic({ cohort, title }) { /** * Create prices section for grouped cohorts - * @template {readonly (CohortAll | CohortFull | CohortWithPercentiles | CohortWithAdjusted | CohortBasic | CohortAddress | CohortWithoutRelative)[]} T - * @param {{ list: T, title: (metric: string) => string }} args + * @param {{ list: readonly CohortObject[], all: CohortAll, title: (metric: string) => string }} args * @returns {PartialOptionsGroup} */ -export function createGroupedPricesSection({ list, title }) { +export function createGroupedPricesSection({ list, all, title }) { return { name: "Prices", tree: [ @@ -162,14 +161,14 @@ export function createGroupedPricesSection({ list, title }) { { name: "Price", title: title("Realized Price"), - top: list.map(({ name, color, tree }) => + top: mapCohortsWithAll(list, all, ({ name, color, tree }) => price({ metric: tree.realized.realizedPrice, name, color }), ), }, { name: "Ratio", title: title("Realized Price Ratio"), - bottom: list.map(({ name, color, tree }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => baseline({ metric: tree.realized.realizedPriceExtra.ratio, name, @@ -187,14 +186,14 @@ export function createGroupedPricesSection({ list, title }) { { name: "Price", title: title("Investor Price"), - top: list.map(({ name, color, tree }) => + top: mapCohortsWithAll(list, all, ({ name, color, tree }) => price({ metric: tree.realized.investorPrice, name, color }), ), }, { name: "Ratio", title: title("Investor Price Ratio"), - bottom: list.map(({ name, color, tree }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => baseline({ metric: tree.realized.investorPriceExtra.ratio, name, diff --git a/website/scripts/options/distribution/profitability.js b/website/scripts/options/distribution/profitability.js index 9b9314dfd..f3d40e16d 100644 --- a/website/scripts/options/distribution/profitability.js +++ b/website/scripts/options/distribution/profitability.js @@ -6,7 +6,7 @@ import { Unit } from "../../utils/units.js"; import { line, baseline, dots, dotsBaseline } from "../series.js"; import { colors } from "../../utils/colors.js"; import { priceLine, priceLines } from "../constants.js"; -import { satsBtcUsd, satsBtcUsdFrom } from "../shared.js"; +import { satsBtcUsd, satsBtcUsdFrom, mapCohorts, mapCohortsWithAll, flatMapCohortsWithAll } from "../shared.js"; // ============================================================================ // Core Series Builders (Composable Primitives) @@ -824,11 +824,6 @@ function realizedSubfolderWithExtras(tree, title) { // Single Cohort Section Builders // ============================================================================ -/** - * @typedef {Object} SectionConfig - * @property {(metric: string) => string} title - */ - /** * Basic profitability section (USD only unrealized) * @param {{ cohort: UtxoCohortObject | CohortWithoutRelative, title: (metric: string) => string }} args @@ -1198,36 +1193,19 @@ export function createProfitabilitySectionWithPeakRegret({ cohort, title }) { // Grouped Cohort Helpers // ============================================================================ -/** - * @template T - * @typedef {{ color: Color, name: string, tree: T }} CohortItem - */ - -/** - * Map cohorts to series - * @template T - * @template R - * @param {readonly CohortItem[]} list - * @param {(item: CohortItem) => R} fn - * @returns {R[]} - */ -function mapCohorts(list, fn) { - return list.map(fn); -} - /** * Grouped P&L charts (USD only) - * @template {{ unrealized: UnrealizedPattern }} T - * @param {readonly CohortItem[]} list + * @param {readonly CohortObject[]} list + * @param {CohortAll} all * @param {(metric: string) => string} title * @returns {PartialOptionsTree} */ -function groupedPnlCharts(list, title) { +function groupedPnlCharts(list, all, title) { return [ { name: "Profit", title: title("Unrealized Profit"), - bottom: mapCohorts(list, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => line({ metric: tree.unrealized.unrealizedProfit, name, @@ -1239,7 +1217,7 @@ function groupedPnlCharts(list, title) { { name: "Loss", title: title("Unrealized Loss"), - bottom: mapCohorts(list, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => line({ metric: tree.unrealized.negUnrealizedLoss, name, @@ -1251,7 +1229,7 @@ function groupedPnlCharts(list, title) { { name: "Net P&L", title: title("Net Unrealized P&L"), - bottom: mapCohorts(list, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => baseline({ metric: tree.unrealized.netUnrealizedPnl, name, @@ -1265,18 +1243,18 @@ function groupedPnlCharts(list, title) { /** * Grouped P&L with % of Market Cap - * @template {{ unrealized: UnrealizedPattern, relative: RelativeWithNupl }} T - * @param {readonly CohortItem[]} list + * @param {readonly (CohortFull | CohortBasicWithMarketCap | CohortMinAge | CohortLongTerm)[]} list + * @param {CohortAll} all * @param {(metric: string) => string} title * @returns {PartialOptionsTree} */ -function groupedPnlChartsWithMarketCap(list, title) { +function groupedPnlChartsWithMarketCap(list, all, title) { return [ { name: "Profit", title: title("Unrealized Profit"), bottom: [ - ...mapCohorts(list, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ color, name, tree }) => line({ metric: tree.unrealized.unrealizedProfit, name, @@ -1284,7 +1262,7 @@ function groupedPnlChartsWithMarketCap(list, title) { unit: Unit.usd, }), ), - ...mapCohorts(list, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ color, name, tree }) => baseline({ metric: tree.relative.unrealizedProfitRelToMarketCap, name, @@ -1298,7 +1276,7 @@ function groupedPnlChartsWithMarketCap(list, title) { name: "Loss", title: title("Unrealized Loss"), bottom: [ - ...mapCohorts(list, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ color, name, tree }) => line({ metric: tree.unrealized.negUnrealizedLoss, name, @@ -1306,7 +1284,7 @@ function groupedPnlChartsWithMarketCap(list, title) { unit: Unit.usd, }), ), - ...mapCohorts(list, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ color, name, tree }) => baseline({ metric: tree.relative.negUnrealizedLossRelToMarketCap, name, @@ -1320,7 +1298,7 @@ function groupedPnlChartsWithMarketCap(list, title) { name: "Net P&L", title: title("Net Unrealized P&L"), bottom: [ - ...mapCohorts(list, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ color, name, tree }) => baseline({ metric: tree.unrealized.netUnrealizedPnl, name, @@ -1328,7 +1306,7 @@ function groupedPnlChartsWithMarketCap(list, title) { unit: Unit.usd, }), ), - ...mapCohorts(list, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ color, name, tree }) => baseline({ metric: tree.relative.netUnrealizedPnlRelToMarketCap, name, @@ -1343,18 +1321,18 @@ function groupedPnlChartsWithMarketCap(list, title) { /** * Grouped P&L with % of Own Market Cap - * @template {{ unrealized: UnrealizedPattern, relative: RelativeWithOwnMarketCap }} T - * @param {readonly CohortItem[]} list + * @param {readonly CohortAgeRange[]} list + * @param {CohortAll} all * @param {(metric: string) => string} title * @returns {PartialOptionsTree} */ -function groupedPnlChartsWithOwnMarketCap(list, title) { +function groupedPnlChartsWithOwnMarketCap(list, all, title) { return [ { name: "Profit", title: title("Unrealized Profit"), bottom: [ - ...mapCohorts(list, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ color, name, tree }) => line({ metric: tree.unrealized.unrealizedProfit, name, @@ -1362,6 +1340,7 @@ function groupedPnlChartsWithOwnMarketCap(list, title) { unit: Unit.usd, }), ), + // OwnMarketCap properties don't exist on CohortAll - use mapCohorts ...mapCohorts(list, ({ color, name, tree }) => line({ metric: tree.relative.unrealizedProfitRelToOwnMarketCap, @@ -1370,7 +1349,7 @@ function groupedPnlChartsWithOwnMarketCap(list, title) { unit: Unit.pctOwnMcap, }), ), - ...mapCohorts(list, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ color, name, tree }) => line({ metric: tree.relative.unrealizedProfitRelToOwnTotalUnrealizedPnl, name, @@ -1384,7 +1363,7 @@ function groupedPnlChartsWithOwnMarketCap(list, title) { name: "Loss", title: title("Unrealized Loss"), bottom: [ - ...mapCohorts(list, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ color, name, tree }) => line({ metric: tree.unrealized.negUnrealizedLoss, name, @@ -1392,6 +1371,7 @@ function groupedPnlChartsWithOwnMarketCap(list, title) { unit: Unit.usd, }), ), + // OwnMarketCap properties don't exist on CohortAll - use mapCohorts ...mapCohorts(list, ({ color, name, tree }) => line({ metric: tree.relative.negUnrealizedLossRelToOwnMarketCap, @@ -1400,7 +1380,7 @@ function groupedPnlChartsWithOwnMarketCap(list, title) { unit: Unit.pctOwnMcap, }), ), - ...mapCohorts(list, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ color, name, tree }) => line({ metric: tree.relative.negUnrealizedLossRelToOwnTotalUnrealizedPnl, name, @@ -1414,7 +1394,7 @@ function groupedPnlChartsWithOwnMarketCap(list, title) { name: "Net P&L", title: title("Net Unrealized P&L"), bottom: [ - ...mapCohorts(list, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ color, name, tree }) => baseline({ metric: tree.unrealized.netUnrealizedPnl, name, @@ -1422,6 +1402,7 @@ function groupedPnlChartsWithOwnMarketCap(list, title) { unit: Unit.usd, }), ), + // OwnMarketCap properties don't exist on CohortAll - use mapCohorts ...mapCohorts(list, ({ color, name, tree }) => baseline({ metric: tree.relative.netUnrealizedPnlRelToOwnMarketCap, @@ -1430,7 +1411,7 @@ function groupedPnlChartsWithOwnMarketCap(list, title) { unit: Unit.pctOwnMcap, }), ), - ...mapCohorts(list, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ color, name, tree }) => baseline({ metric: tree.relative.netUnrealizedPnlRelToOwnTotalUnrealizedPnl, name, @@ -1446,16 +1427,17 @@ function groupedPnlChartsWithOwnMarketCap(list, title) { /** * Grouped P&L for LongTerm cohorts * @param {readonly CohortLongTerm[]} list + * @param {CohortAll} all * @param {(metric: string) => string} title * @returns {PartialOptionsTree} */ -function groupedPnlChartsLongTerm(list, title) { +function groupedPnlChartsLongTerm(list, all, title) { return [ { name: "Profit", title: title("Unrealized Profit"), bottom: [ - ...mapCohorts(list, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ color, name, tree }) => line({ metric: tree.unrealized.unrealizedProfit, name, @@ -1463,6 +1445,7 @@ function groupedPnlChartsLongTerm(list, title) { unit: Unit.usd, }), ), + // OwnMarketCap properties don't exist on CohortAll - use mapCohorts ...mapCohorts(list, ({ color, name, tree }) => line({ metric: tree.relative.unrealizedProfitRelToOwnMarketCap, @@ -1471,7 +1454,7 @@ function groupedPnlChartsLongTerm(list, title) { unit: Unit.pctOwnMcap, }), ), - ...mapCohorts(list, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ color, name, tree }) => line({ metric: tree.relative.unrealizedProfitRelToOwnTotalUnrealizedPnl, name, @@ -1485,7 +1468,7 @@ function groupedPnlChartsLongTerm(list, title) { name: "Loss", title: title("Unrealized Loss"), bottom: [ - ...mapCohorts(list, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ color, name, tree }) => line({ metric: tree.unrealized.negUnrealizedLoss, name, @@ -1493,7 +1476,7 @@ function groupedPnlChartsLongTerm(list, title) { unit: Unit.usd, }), ), - ...mapCohorts(list, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ color, name, tree }) => line({ metric: tree.relative.unrealizedLossRelToMarketCap, name, @@ -1501,6 +1484,7 @@ function groupedPnlChartsLongTerm(list, title) { unit: Unit.pctMcap, }), ), + // OwnMarketCap properties don't exist on CohortAll - use mapCohorts ...mapCohorts(list, ({ color, name, tree }) => line({ metric: tree.relative.negUnrealizedLossRelToOwnMarketCap, @@ -1509,7 +1493,7 @@ function groupedPnlChartsLongTerm(list, title) { unit: Unit.pctOwnMcap, }), ), - ...mapCohorts(list, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ color, name, tree }) => line({ metric: tree.relative.negUnrealizedLossRelToOwnTotalUnrealizedPnl, name, @@ -1523,7 +1507,7 @@ function groupedPnlChartsLongTerm(list, title) { name: "Net P&L", title: title("Net Unrealized P&L"), bottom: [ - ...mapCohorts(list, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ color, name, tree }) => baseline({ metric: tree.unrealized.netUnrealizedPnl, name, @@ -1531,6 +1515,7 @@ function groupedPnlChartsLongTerm(list, title) { unit: Unit.usd, }), ), + // OwnMarketCap properties don't exist on CohortAll - use mapCohorts ...mapCohorts(list, ({ color, name, tree }) => baseline({ metric: tree.relative.netUnrealizedPnlRelToOwnMarketCap, @@ -1539,7 +1524,7 @@ function groupedPnlChartsLongTerm(list, title) { unit: Unit.pctOwnMcap, }), ), - ...mapCohorts(list, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ color, name, tree }) => baseline({ metric: tree.relative.netUnrealizedPnlRelToOwnTotalUnrealizedPnl, name, @@ -1554,17 +1539,17 @@ function groupedPnlChartsLongTerm(list, title) { /** * Grouped invested capital (absolute only) - * @template {{ unrealized: UnrealizedPattern }} T - * @param {readonly CohortItem[]} list + * @param {readonly CohortObject[]} list + * @param {CohortAll} all * @param {(metric: string) => string} title * @returns {PartialOptionsTree} */ -function groupedInvestedCapitalAbsolute(list, title) { +function groupedInvestedCapitalAbsolute(list, all, title) { return [ { name: "In Profit", title: title("Invested Capital In Profit"), - bottom: mapCohorts(list, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => line({ metric: tree.unrealized.investedCapitalInProfit, name, @@ -1576,7 +1561,7 @@ function groupedInvestedCapitalAbsolute(list, title) { { name: "In Loss", title: title("Invested Capital In Loss"), - bottom: mapCohorts(list, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => line({ metric: tree.unrealized.investedCapitalInLoss, name, @@ -1590,18 +1575,18 @@ function groupedInvestedCapitalAbsolute(list, title) { /** * Grouped invested capital with % - * @template {{ unrealized: UnrealizedPattern, relative: RelativeWithInvestedCapitalPct }} T - * @param {readonly CohortItem[]} list + * @param {readonly (CohortBasicWithoutMarketCap | CohortAgeRange | CohortFull | CohortBasicWithMarketCap | CohortLongTerm | CohortMinAge)[]} list + * @param {CohortAll} all * @param {(metric: string) => string} title * @returns {PartialOptionsTree} */ -function groupedInvestedCapital(list, title) { +function groupedInvestedCapital(list, all, title) { return [ { name: "In Profit", title: title("Invested Capital In Profit"), bottom: [ - ...mapCohorts(list, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ color, name, tree }) => line({ metric: tree.unrealized.investedCapitalInProfit, name, @@ -1609,7 +1594,7 @@ function groupedInvestedCapital(list, title) { unit: Unit.usd, }), ), - ...mapCohorts(list, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ color, name, tree }) => baseline({ metric: tree.relative.investedCapitalInProfitPct, name, @@ -1624,7 +1609,7 @@ function groupedInvestedCapital(list, title) { name: "In Loss", title: title("Invested Capital In Loss"), bottom: [ - ...mapCohorts(list, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ color, name, tree }) => line({ metric: tree.unrealized.investedCapitalInLoss, name, @@ -1632,7 +1617,7 @@ function groupedInvestedCapital(list, title) { unit: Unit.usd, }), ), - ...mapCohorts(list, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ color, name, tree }) => baseline({ metric: tree.relative.investedCapitalInLossPct, name, @@ -1648,17 +1633,17 @@ function groupedInvestedCapital(list, title) { /** * Grouped realized P&L sum - * @template {{ realized: AnyRealizedPattern }} T - * @param {readonly CohortItem[]} list + * @param {readonly CohortObject[]} list + * @param {CohortAll} all * @param {(metric: string) => string} title * @returns {PartialOptionsTree} */ -function groupedRealizedPnlSum(list, title) { +function groupedRealizedPnlSum(list, all, title) { return [ { name: "Profit", title: title("Realized Profit"), - bottom: mapCohorts(list, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => line({ metric: tree.realized.realizedProfit.sum, name, @@ -1670,7 +1655,7 @@ function groupedRealizedPnlSum(list, title) { { name: "Loss", title: title("Realized Loss"), - bottom: mapCohorts(list, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => line({ metric: tree.realized.negRealizedLoss.sum, name, @@ -1682,7 +1667,7 @@ function groupedRealizedPnlSum(list, title) { { name: "Total", title: title("Total Realized P&L"), - bottom: mapCohorts(list, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => line({ metric: tree.realized.totalRealizedPnl, name, @@ -1694,7 +1679,7 @@ function groupedRealizedPnlSum(list, title) { { name: "Value", title: title("Realized Value"), - bottom: mapCohorts(list, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => line({ metric: tree.realized.realizedValue, name, @@ -1708,18 +1693,18 @@ function groupedRealizedPnlSum(list, title) { /** * Grouped realized P&L sum with P/L ratio - * @template {{ realized: RealizedWithExtras }} T - * @param {readonly CohortItem[]} list + * @param {readonly (CohortAgeRange | CohortLongTerm | CohortAll | CohortFull)[]} list + * @param {CohortAll} all * @param {(metric: string) => string} title * @returns {PartialOptionsTree} */ -function groupedRealizedPnlSumWithExtras(list, title) { +function groupedRealizedPnlSumWithExtras(list, all, title) { return [ - ...groupedRealizedPnlSum(list, title), + ...groupedRealizedPnlSum(list, all, title), { name: "P/L Ratio", title: title("Realized Profit/Loss Ratio"), - bottom: mapCohorts(list, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => baseline({ metric: tree.realized.realizedProfitToLossRatio, name, @@ -1733,17 +1718,17 @@ function groupedRealizedPnlSumWithExtras(list, title) { /** * Grouped realized cumulative - * @template {{ realized: AnyRealizedPattern }} T - * @param {readonly CohortItem[]} list + * @param {readonly CohortObject[]} list + * @param {CohortAll} all * @param {(metric: string) => string} title * @returns {PartialOptionsTree} */ -function groupedRealizedPnlCumulative(list, title) { +function groupedRealizedPnlCumulative(list, all, title) { return [ { name: "Profit", title: title("Cumulative Realized Profit"), - bottom: mapCohorts(list, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => line({ metric: tree.realized.realizedProfit.cumulative, name, @@ -1755,7 +1740,7 @@ function groupedRealizedPnlCumulative(list, title) { { name: "Loss", title: title("Cumulative Realized Loss"), - bottom: mapCohorts(list, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => line({ metric: tree.realized.negRealizedLoss.cumulative, name, @@ -1769,12 +1754,12 @@ function groupedRealizedPnlCumulative(list, title) { /** * Grouped sent in P/L - * @template {{ realized: AnyRealizedPattern }} T - * @param {readonly CohortItem[]} list + * @param {readonly CohortObject[]} list + * @param {CohortAll} all * @param {(metric: string) => string} title * @returns {PartialOptionsTree} */ -function groupedSentInPnl(list, title) { +function groupedSentInPnl(list, all, title) { return [ { name: "Sum", @@ -1783,7 +1768,7 @@ function groupedSentInPnl(list, title) { name: "In Profit", title: title("Sent In Profit"), bottom: [ - ...list.flatMap(({ color, name, tree }) => + ...flatMapCohortsWithAll(list, all, ({ color, name, tree }) => satsBtcUsd({ pattern: tree.realized.sentInProfit14dEma, name: `${name} 14d EMA`, @@ -1791,7 +1776,7 @@ function groupedSentInPnl(list, title) { defaultActive: false, }), ), - ...list.flatMap(({ color, name, tree }) => + ...flatMapCohortsWithAll(list, all, ({ color, name, tree }) => satsBtcUsdFrom({ source: tree.realized.sentInProfit, key: "sum", @@ -1805,7 +1790,7 @@ function groupedSentInPnl(list, title) { name: "In Loss", title: title("Sent In Loss"), bottom: [ - ...list.flatMap(({ color, name, tree }) => + ...flatMapCohortsWithAll(list, all, ({ color, name, tree }) => satsBtcUsd({ pattern: tree.realized.sentInLoss14dEma, name: `${name} 14d EMA`, @@ -1813,7 +1798,7 @@ function groupedSentInPnl(list, title) { defaultActive: false, }), ), - ...list.flatMap(({ color, name, tree }) => + ...flatMapCohortsWithAll(list, all, ({ color, name, tree }) => satsBtcUsdFrom({ source: tree.realized.sentInLoss, key: "sum", @@ -1831,7 +1816,7 @@ function groupedSentInPnl(list, title) { { name: "In Profit", title: title("Cumulative Sent In Profit"), - bottom: list.flatMap(({ color, name, tree }) => + bottom: flatMapCohortsWithAll(list, all, ({ color, name, tree }) => satsBtcUsdFrom({ source: tree.realized.sentInProfit, key: "cumulative", @@ -1843,7 +1828,7 @@ function groupedSentInPnl(list, title) { { name: "In Loss", title: title("Cumulative Sent In Loss"), - bottom: list.flatMap(({ color, name, tree }) => + bottom: flatMapCohortsWithAll(list, all, ({ color, name, tree }) => satsBtcUsdFrom({ source: tree.realized.sentInLoss, key: "cumulative", @@ -1859,19 +1844,19 @@ function groupedSentInPnl(list, title) { /** * Grouped sentiment - * @template {{ unrealized: UnrealizedPattern }} T - * @param {readonly CohortItem[]} list + * @param {readonly CohortObject[]} list + * @param {CohortAll} all * @param {(metric: string) => string} title * @returns {PartialOptionsGroup} */ -function groupedSentiment(list, title) { +function groupedSentiment(list, all, title) { return { name: "Sentiment", tree: [ { name: "Net", title: title("Net Sentiment"), - bottom: mapCohorts(list, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => baseline({ metric: tree.unrealized.netSentiment, name, @@ -1883,7 +1868,7 @@ function groupedSentiment(list, title) { { name: "Greed", title: title("Greed Index"), - bottom: mapCohorts(list, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => line({ metric: tree.unrealized.greedIndex, name, @@ -1895,7 +1880,7 @@ function groupedSentiment(list, title) { { name: "Pain", title: title("Pain Index"), - bottom: mapCohorts(list, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => line({ metric: tree.unrealized.painIndex, name, @@ -1910,20 +1895,20 @@ function groupedSentiment(list, title) { /** * Grouped realized subfolder - * @template {{ realized: AnyRealizedPattern }} T - * @param {readonly CohortItem[]} list + * @param {readonly CohortObject[]} list + * @param {CohortAll} all * @param {(metric: string) => string} title * @returns {PartialOptionsGroup} */ -function groupedRealizedSubfolder(list, title) { +function groupedRealizedSubfolder(list, all, title) { return { name: "Realized", tree: [ - { name: "P&L", tree: groupedRealizedPnlSum(list, title) }, + { name: "P&L", tree: groupedRealizedPnlSum(list, all, title) }, { name: "Net", title: title("Net Realized P&L"), - bottom: mapCohorts(list, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => baseline({ metric: tree.realized.netRealizedPnl.sum, name, @@ -1935,7 +1920,7 @@ function groupedRealizedSubfolder(list, title) { { name: "30d Change", title: title("Realized P&L 30d Change"), - bottom: mapCohorts(list, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => baseline({ metric: tree.realized.netRealizedPnlCumulative30dDelta, name, @@ -1947,11 +1932,11 @@ function groupedRealizedSubfolder(list, title) { { name: "Cumulative", tree: [ - { name: "P&L", tree: groupedRealizedPnlCumulative(list, title) }, + { name: "P&L", tree: groupedRealizedPnlCumulative(list, all, title) }, { name: "Net", title: title("Cumulative Net Realized P&L"), - bottom: mapCohorts(list, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => baseline({ metric: tree.realized.netRealizedPnl.cumulative, name, @@ -1968,20 +1953,20 @@ function groupedRealizedSubfolder(list, title) { /** * Grouped realized with extras - * @template {{ realized: RealizedWithExtras }} T - * @param {readonly CohortItem[]} list + * @param {readonly (CohortAgeRange | CohortLongTerm | CohortAll | CohortFull)[]} list + * @param {CohortAll} all * @param {(metric: string) => string} title * @returns {PartialOptionsGroup} */ -function groupedRealizedSubfolderWithExtras(list, title) { +function groupedRealizedSubfolderWithExtras(list, all, title) { return { name: "Realized", tree: [ - { name: "P&L", tree: groupedRealizedPnlSumWithExtras(list, title) }, + { name: "P&L", tree: groupedRealizedPnlSumWithExtras(list, all, title) }, { name: "Net", title: title("Net Realized P&L"), - bottom: mapCohorts(list, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => baseline({ metric: tree.realized.netRealizedPnl.sum, name, @@ -1993,7 +1978,7 @@ function groupedRealizedSubfolderWithExtras(list, title) { { name: "30d Change", title: title("Realized P&L 30d Change"), - bottom: mapCohorts(list, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => baseline({ metric: tree.realized.netRealizedPnlCumulative30dDelta, name, @@ -2005,11 +1990,11 @@ function groupedRealizedSubfolderWithExtras(list, title) { { name: "Cumulative", tree: [ - { name: "P&L", tree: groupedRealizedPnlCumulative(list, title) }, + { name: "P&L", tree: groupedRealizedPnlCumulative(list, all, title) }, { name: "Net", title: title("Cumulative Net Realized P&L"), - bottom: mapCohorts(list, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => baseline({ metric: tree.realized.netRealizedPnl.cumulative, name, @@ -2030,54 +2015,55 @@ function groupedRealizedSubfolderWithExtras(list, title) { /** * Grouped profitability section (basic) - * @template {readonly (UtxoCohortObject | CohortWithoutRelative)[]} T - * @param {{ list: T, title: (metric: string) => string }} args + * @param {{ list: readonly (UtxoCohortObject | CohortWithoutRelative)[], all: CohortAll, title: (metric: string) => string }} args * @returns {PartialOptionsGroup} */ -export function createGroupedProfitabilitySection({ list, title }) { +export function createGroupedProfitabilitySection({ list, all, title }) { return { name: "Profitability", tree: [ - { name: "Unrealized", tree: groupedPnlCharts(list, title) }, - groupedRealizedSubfolder(list, title), - { name: "Volume", tree: groupedSentInPnl(list, title) }, + { name: "Unrealized", tree: groupedPnlCharts(list, all, title) }, + groupedRealizedSubfolder(list, all, title), + { name: "Volume", tree: groupedSentInPnl(list, all, title) }, { name: "Invested Capital", - tree: groupedInvestedCapitalAbsolute(list, title), + tree: groupedInvestedCapitalAbsolute(list, all, title), }, - groupedSentiment(list, title), + groupedSentiment(list, all, title), ], }; } /** * Grouped section with invested capital % (basic cohorts) - * @param {{ list: readonly CohortBasicWithoutMarketCap[], title: (metric: string) => string }} args + * @param {{ list: readonly CohortBasicWithoutMarketCap[], all: CohortAll, title: (metric: string) => string }} args * @returns {PartialOptionsGroup} */ export function createGroupedProfitabilitySectionBasicWithInvestedCapitalPct({ list, + all, title, }) { return { name: "Profitability", tree: [ - { name: "Unrealized", tree: groupedPnlCharts(list, title) }, - groupedRealizedSubfolder(list, title), - { name: "Volume", tree: groupedSentInPnl(list, title) }, - { name: "Invested Capital", tree: groupedInvestedCapital(list, title) }, - groupedSentiment(list, title), + { name: "Unrealized", tree: groupedPnlCharts(list, all, title) }, + groupedRealizedSubfolder(list, all, title), + { name: "Volume", tree: groupedSentInPnl(list, all, title) }, + { name: "Invested Capital", tree: groupedInvestedCapital(list, all, title) }, + groupedSentiment(list, all, title), ], }; } /** * Grouped section for ageRange cohorts - * @param {{ list: readonly CohortAgeRange[], title: (metric: string) => string }} args + * @param {{ list: readonly CohortAgeRange[], all: CohortAll, title: (metric: string) => string }} args * @returns {PartialOptionsGroup} */ export function createGroupedProfitabilitySectionWithInvestedCapitalPct({ list, + all, title, }) { return { @@ -2086,11 +2072,11 @@ export function createGroupedProfitabilitySectionWithInvestedCapitalPct({ { name: "Unrealized", tree: [ - ...groupedPnlChartsWithOwnMarketCap(list, title), + ...groupedPnlChartsWithOwnMarketCap(list, all, title), { name: "Peak Regret", title: title("Unrealized Peak Regret"), - bottom: mapCohorts(list, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => line({ metric: tree.unrealized.peakRegret, name, @@ -2101,31 +2087,31 @@ export function createGroupedProfitabilitySectionWithInvestedCapitalPct({ }, ], }, - groupedRealizedSubfolderWithExtras(list, title), - { name: "Volume", tree: groupedSentInPnl(list, title) }, - { name: "Invested Capital", tree: groupedInvestedCapital(list, title) }, - groupedSentiment(list, title), + groupedRealizedSubfolderWithExtras(list, all, title), + { name: "Volume", tree: groupedSentInPnl(list, all, title) }, + { name: "Invested Capital", tree: groupedInvestedCapital(list, all, title) }, + groupedSentiment(list, all, title), ], }; } /** * Grouped section with NUPL - * @param {{ list: readonly (CohortFull | CohortBasicWithMarketCap)[], title: (metric: string) => string }} args + * @param {{ list: readonly (CohortFull | CohortBasicWithMarketCap)[], all: CohortAll, title: (metric: string) => string }} args * @returns {PartialOptionsGroup} */ -export function createGroupedProfitabilitySectionWithNupl({ list, title }) { +export function createGroupedProfitabilitySectionWithNupl({ list, all, title }) { return { name: "Profitability", tree: [ { name: "Unrealized", tree: [ - ...groupedPnlChartsWithMarketCap(list, title), + ...groupedPnlChartsWithMarketCap(list, all, title), { name: "NUPL", title: title("NUPL"), - bottom: mapCohorts(list, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => baseline({ metric: tree.relative.nupl, name, @@ -2136,31 +2122,31 @@ export function createGroupedProfitabilitySectionWithNupl({ list, title }) { }, ], }, - groupedRealizedSubfolder(list, title), - { name: "Volume", tree: groupedSentInPnl(list, title) }, - { name: "Invested Capital", tree: groupedInvestedCapital(list, title) }, - groupedSentiment(list, title), + groupedRealizedSubfolder(list, all, title), + { name: "Volume", tree: groupedSentInPnl(list, all, title) }, + { name: "Invested Capital", tree: groupedInvestedCapital(list, all, title) }, + groupedSentiment(list, all, title), ], }; } /** * Grouped section for LongTerm cohorts - * @param {{ list: readonly CohortLongTerm[], title: (metric: string) => string }} args + * @param {{ list: readonly CohortLongTerm[], all: CohortAll, title: (metric: string) => string }} args * @returns {PartialOptionsGroup} */ -export function createGroupedProfitabilitySectionLongTerm({ list, title }) { +export function createGroupedProfitabilitySectionLongTerm({ list, all, title }) { return { name: "Profitability", tree: [ { name: "Unrealized", tree: [ - ...groupedPnlChartsLongTerm(list, title), + ...groupedPnlChartsLongTerm(list, all, title), { name: "NUPL", title: title("NUPL"), - bottom: mapCohorts(list, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => baseline({ metric: tree.relative.nupl, name, @@ -2173,7 +2159,7 @@ export function createGroupedProfitabilitySectionLongTerm({ list, title }) { name: "Peak Regret", title: title("Unrealized Peak Regret"), bottom: [ - ...mapCohorts(list, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ color, name, tree }) => line({ metric: tree.unrealized.peakRegret, name, @@ -2181,7 +2167,7 @@ export function createGroupedProfitabilitySectionLongTerm({ list, title }) { unit: Unit.usd, }), ), - ...mapCohorts(list, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ color, name, tree }) => baseline({ metric: tree.relative.unrealizedPeakRegretRelToMarketCap, name, @@ -2193,21 +2179,22 @@ export function createGroupedProfitabilitySectionLongTerm({ list, title }) { }, ], }, - groupedRealizedSubfolderWithExtras(list, title), - { name: "Volume", tree: groupedSentInPnl(list, title) }, - { name: "Invested Capital", tree: groupedInvestedCapital(list, title) }, - groupedSentiment(list, title), + groupedRealizedSubfolderWithExtras(list, all, title), + { name: "Volume", tree: groupedSentInPnl(list, all, title) }, + { name: "Invested Capital", tree: groupedInvestedCapital(list, all, title) }, + groupedSentiment(list, all, title), ], }; } /** * Grouped section with Peak Regret + NUPL (minAge cohorts) - * @param {{ list: readonly CohortMinAge[], title: (metric: string) => string }} args + * @param {{ list: readonly CohortMinAge[], all: CohortAll, title: (metric: string) => string }} args * @returns {PartialOptionsGroup} */ export function createGroupedProfitabilitySectionWithPeakRegret({ list, + all, title, }) { return { @@ -2216,11 +2203,11 @@ export function createGroupedProfitabilitySectionWithPeakRegret({ { name: "Unrealized", tree: [ - ...groupedPnlChartsWithMarketCap(list, title), + ...groupedPnlChartsWithMarketCap(list, all, title), { name: "NUPL", title: title("NUPL"), - bottom: mapCohorts(list, ({ color, name, tree }) => + bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) => baseline({ metric: tree.relative.nupl, name, @@ -2233,7 +2220,7 @@ export function createGroupedProfitabilitySectionWithPeakRegret({ name: "Peak Regret", title: title("Unrealized Peak Regret"), bottom: [ - ...mapCohorts(list, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ color, name, tree }) => line({ metric: tree.unrealized.peakRegret, name, @@ -2241,7 +2228,7 @@ export function createGroupedProfitabilitySectionWithPeakRegret({ unit: Unit.usd, }), ), - ...mapCohorts(list, ({ color, name, tree }) => + ...mapCohortsWithAll(list, all, ({ color, name, tree }) => baseline({ metric: tree.relative.unrealizedPeakRegretRelToMarketCap, name, @@ -2253,10 +2240,10 @@ export function createGroupedProfitabilitySectionWithPeakRegret({ }, ], }, - groupedRealizedSubfolder(list, title), - { name: "Volume", tree: groupedSentInPnl(list, title) }, - { name: "Invested Capital", tree: groupedInvestedCapital(list, title) }, - groupedSentiment(list, title), + groupedRealizedSubfolder(list, all, title), + { name: "Volume", tree: groupedSentInPnl(list, all, title) }, + { name: "Invested Capital", tree: groupedInvestedCapital(list, all, title) }, + groupedSentiment(list, all, title), ], }; } diff --git a/website/scripts/options/distribution/valuation.js b/website/scripts/options/distribution/valuation.js index 2817fe322..0dce39a0f 100644 --- a/website/scripts/options/distribution/valuation.js +++ b/website/scripts/options/distribution/valuation.js @@ -12,7 +12,7 @@ import { Unit } from "../../utils/units.js"; import { line, baseline } from "../series.js"; -import { createRatioChart } from "../shared.js"; +import { createRatioChart, mapCohortsWithAll } from "../shared.js"; /** * @param {UtxoCohortObject | CohortWithoutRelative} cohort @@ -123,18 +123,17 @@ export function createValuationSection({ cohort, title }) { } /** - * @template {readonly (UtxoCohortObject | CohortWithoutRelative)[]} T - * @param {{ list: T, title: (metric: string) => string }} args + * @param {{ list: readonly (UtxoCohortObject | CohortWithoutRelative)[], all: CohortAll, title: (metric: string) => string }} args * @returns {PartialOptionsGroup} */ -export function createGroupedValuationSection({ list, title }) { +export function createGroupedValuationSection({ list, all, title }) { return { name: "Valuation", tree: [ { name: "Realized Cap", title: title("Realized Cap"), - bottom: list.map(({ name, color, tree }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.realized.realizedCap, name, @@ -146,7 +145,7 @@ export function createGroupedValuationSection({ list, title }) { { name: "30d Change", title: title("Realized Cap 30d Change"), - bottom: list.map(({ name, color, tree }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => baseline({ metric: tree.realized.realizedCap30dDelta, name, @@ -158,7 +157,7 @@ export function createGroupedValuationSection({ list, title }) { { name: "MVRV", title: title("MVRV"), - bottom: list.map(({ name, color, tree }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => baseline({ metric: tree.realized.realizedPriceExtra.ratio, name, @@ -173,11 +172,10 @@ export function createGroupedValuationSection({ list, title }) { } /** - * @template {{ name: string, color: Color, tree: { realized: { realizedCap: AnyMetricPattern, realizedCap30dDelta: AnyMetricPattern, realizedCapRelToOwnMarketCap: AnyMetricPattern, realizedPriceExtra: { ratio: AnyMetricPattern } } } }} T - * @param {{ list: readonly T[], title: (metric: string) => string }} args + * @param {{ list: readonly (CohortAll | CohortFull | CohortWithPercentiles)[], all: CohortAll, title: (metric: string) => string }} args * @returns {PartialOptionsGroup} */ -export function createGroupedValuationSectionWithOwnMarketCap({ list, title }) { +export function createGroupedValuationSectionWithOwnMarketCap({ list, all, title }) { return { name: "Valuation", tree: [ @@ -185,7 +183,7 @@ export function createGroupedValuationSectionWithOwnMarketCap({ list, title }) { name: "Realized Cap", title: title("Realized Cap"), bottom: [ - ...list.map(({ name, color, tree }) => + ...mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ metric: tree.realized.realizedCap, name, @@ -193,7 +191,7 @@ export function createGroupedValuationSectionWithOwnMarketCap({ list, title }) { unit: Unit.usd, }), ), - ...list.map(({ name, color, tree }) => + ...mapCohortsWithAll(list, all, ({ name, color, tree }) => baseline({ metric: tree.realized.realizedCapRelToOwnMarketCap, name, @@ -206,7 +204,7 @@ export function createGroupedValuationSectionWithOwnMarketCap({ list, title }) { { name: "30d Change", title: title("Realized Cap 30d Change"), - bottom: list.map(({ name, color, tree }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => baseline({ metric: tree.realized.realizedCap30dDelta, name, @@ -218,7 +216,7 @@ export function createGroupedValuationSectionWithOwnMarketCap({ list, title }) { { name: "MVRV", title: title("MVRV"), - bottom: list.map(({ name, color, tree }) => + bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => baseline({ metric: tree.realized.realizedPriceExtra.ratio, name, diff --git a/website/scripts/options/partial.js b/website/scripts/options/partial.js index b485a2a74..fc6961ce1 100644 --- a/website/scripts/options/partial.js +++ b/website/scripts/options/partial.js @@ -5,7 +5,6 @@ import { createCohortFolderAll, createCohortFolderFull, createCohortFolderWithAdjusted, - createCohortFolderWithNupl, createCohortFolderLongTerm, createCohortFolderAgeRange, createCohortFolderMinAge, @@ -14,6 +13,14 @@ import { createCohortFolderWithoutRelative, createCohortFolderAddress, createAddressCohortFolder, + createGroupedCohortFolderWithAdjusted, + createGroupedCohortFolderWithNupl, + createGroupedCohortFolderAgeRange, + createGroupedCohortFolderMinAge, + createGroupedCohortFolderBasicWithMarketCap, + createGroupedCohortFolderBasicWithoutMarketCap, + createGroupedCohortFolderAddress, + createGroupedAddressCohortFolder, } from "./distribution/index.js"; import { createMarketSection } from "./market.js"; import { createNetworkSection } from "./network.js"; @@ -50,17 +57,6 @@ export function createPartialOptions() { } = buildCohortData(); return [ - // Debug explorer (disabled) - // ...(localhost - // ? [ - // { - // kind: /** @type {const} */ ("explorer"), - // name: "Explorer", - // title: "Debug explorer", - // }, - // ] - // : []), - // Charts section { name: "Charts", @@ -78,20 +74,21 @@ export function createPartialOptions() { { name: "Distribution", tree: [ - // Overview - All UTXOs (adjustedSopr + percentiles but no RelToMarketCap) + // Overview - All UTXOs createCohortFolderAll({ ...cohortAll, name: "Overview" }), - // STH vs LTH - Direct comparison (before individual cohorts) - createCohortFolderWithNupl({ + // STH vs LTH - Direct comparison + createGroupedCohortFolderWithNupl({ name: "STH vs LTH", title: "STH vs LTH", list: [termShort, termLong], + all: cohortAll, }), - // STH - Short term holder cohort (Full capability) + // STH - Short term holder cohort createCohortFolderFull(termShort), - // LTH - Long term holder cohort (own market cap + nupl + peak regret + P/L ratio) + // LTH - Long term holder cohort createCohortFolderLongTerm(termLong), // Ages cohorts @@ -102,10 +99,11 @@ export function createPartialOptions() { { name: "Younger Than", tree: [ - createCohortFolderWithAdjusted({ + createGroupedCohortFolderWithAdjusted({ name: "Compare", title: "Max Age", list: upToDate, + all: cohortAll, }), ...upToDate.map(createCohortFolderWithAdjusted), ], @@ -114,10 +112,11 @@ export function createPartialOptions() { { name: "Older Than", tree: [ - createCohortFolderMinAge({ + createGroupedCohortFolderMinAge({ name: "Compare", title: "Min Age", list: fromDate, + all: cohortAll, }), ...fromDate.map(createCohortFolderMinAge), ], @@ -126,10 +125,11 @@ export function createPartialOptions() { { name: "Range", tree: [ - createCohortFolderAgeRange({ + createGroupedCohortFolderAgeRange({ name: "Compare", title: "Age Ranges", list: dateRange, + all: cohortAll, }), ...dateRange.map(createCohortFolderAgeRange), ], @@ -145,42 +145,39 @@ export function createPartialOptions() { { name: "Less Than", tree: [ - createCohortFolderBasicWithMarketCap({ + createGroupedCohortFolderBasicWithMarketCap({ name: "Compare", title: "Max Size", list: utxosUnderAmount, + all: cohortAll, }), - ...utxosUnderAmount.map( - createCohortFolderBasicWithMarketCap, - ), + ...utxosUnderAmount.map(createCohortFolderBasicWithMarketCap), ], }, // More Than (≥ X sats) { name: "More Than", tree: [ - createCohortFolderBasicWithMarketCap({ + createGroupedCohortFolderBasicWithMarketCap({ name: "Compare", title: "Min Size", list: utxosAboveAmount, + all: cohortAll, }), - ...utxosAboveAmount.map( - createCohortFolderBasicWithMarketCap, - ), + ...utxosAboveAmount.map(createCohortFolderBasicWithMarketCap), ], }, // Range { name: "Range", tree: [ - createCohortFolderBasicWithoutMarketCap({ + createGroupedCohortFolderBasicWithoutMarketCap({ name: "Compare", title: "Size Ranges", list: utxosAmountRanges, + all: cohortAll, }), - ...utxosAmountRanges.map( - createCohortFolderBasicWithoutMarketCap, - ), + ...utxosAmountRanges.map(createCohortFolderBasicWithoutMarketCap), ], }, ], @@ -194,10 +191,11 @@ export function createPartialOptions() { { name: "Less Than", tree: [ - createAddressCohortFolder({ + createGroupedAddressCohortFolder({ name: "Compare", title: "Max Balance", list: addressesUnderAmount, + all: cohortAll, }), ...addressesUnderAmount.map(createAddressCohortFolder), ], @@ -206,10 +204,11 @@ export function createPartialOptions() { { name: "More Than", tree: [ - createAddressCohortFolder({ + createGroupedAddressCohortFolder({ name: "Compare", title: "Min Balance", list: addressesAboveAmount, + all: cohortAll, }), ...addressesAboveAmount.map(createAddressCohortFolder), ], @@ -218,10 +217,11 @@ export function createPartialOptions() { { name: "Range", tree: [ - createAddressCohortFolder({ + createGroupedAddressCohortFolder({ name: "Compare", title: "Balance Ranges", list: addressesAmountRanges, + all: cohortAll, }), ...addressesAmountRanges.map(createAddressCohortFolder), ], @@ -233,37 +233,40 @@ export function createPartialOptions() { { name: "Script Types", tree: [ - createCohortFolderAddress({ + createGroupedCohortFolderAddress({ name: "Compare", title: "Script Types", list: typeAddressable, + all: cohortAll, }), ...typeAddressable.map(createCohortFolderAddress), ...typeOther.map(createCohortFolderWithoutRelative), ], }, - // Epochs - CohortBasicWithoutMarketCap (no RelToMarketCap) + // Epochs { name: "Epochs", tree: [ - createCohortFolderBasicWithoutMarketCap({ + createGroupedCohortFolderBasicWithoutMarketCap({ name: "Compare", title: "Epochs", list: epoch, + all: cohortAll, }), ...epoch.map(createCohortFolderBasicWithoutMarketCap), ], }, - // Years - CohortBasicWithoutMarketCap (no RelToMarketCap) + // Years { name: "Years", tree: [ - createCohortFolderBasicWithoutMarketCap({ + createGroupedCohortFolderBasicWithoutMarketCap({ name: "Compare", title: "Years", list: year, + all: cohortAll, }), ...year.map(createCohortFolderBasicWithoutMarketCap), ], @@ -282,25 +285,6 @@ export function createPartialOptions() { ], }, - // Table section (disabled) - // { - // kind: /** @type {const} */ ("table"), - // title: "Table", - // name: "Table", - // }, - - // Simulations section (disabled) - // { - // name: "Simulations", - // tree: [ - // { - // kind: /** @type {const} */ ("simulation"), - // name: "Save In Bitcoin", - // title: "Save In Bitcoin", - // }, - // ], - // }, - // API documentation { name: "API", diff --git a/website/scripts/options/shared.js b/website/scripts/options/shared.js index 999b3baf1..a791161c9 100644 --- a/website/scripts/options/shared.js +++ b/website/scripts/options/shared.js @@ -5,6 +5,64 @@ import { line, baseline, price } from "./series.js"; import { priceLine, priceLines } from "./constants.js"; import { colors } from "../utils/colors.js"; +// ============================================================================ +// Grouped Cohort Helpers +// ============================================================================ + +/** + * Map cohorts to series (without "all" cohort) + * Use for charts where "all" doesn't have required properties + * @template T + * @template R + * @param {readonly T[]} list + * @param {(item: T) => R} fn + * @returns {R[]} + */ +export function mapCohorts(list, fn) { + return list.map(fn); +} + +/** + * FlatMap cohorts to series (without "all" cohort) + * Use for charts where "all" doesn't have required properties + * @template T + * @template R + * @param {readonly T[]} list + * @param {(item: T) => R[]} fn + * @returns {R[]} + */ +export function flatMapCohorts(list, fn) { + return list.flatMap(fn); +} + +/** + * Map cohorts to series, with "all" cohort added as defaultActive: false + * @template T + * @template A + * @template R + * @param {readonly T[]} list + * @param {A} all + * @param {(item: T | A) => R} fn + * @returns {R[]} + */ +export function mapCohortsWithAll(list, all, fn) { + return [...list.map(fn), { ...fn({ ...all, name: "All" }), defaultActive: false }]; +} + +/** + * FlatMap cohorts to series, with "all" cohort added as defaultActive: false + * @template T + * @template A + * @template R + * @param {readonly T[]} list + * @param {A} all + * @param {(item: T | A) => R[]} fn + * @returns {R[]} + */ +export function flatMapCohortsWithAll(list, all, fn) { + return [...list.flatMap(fn), ...fn({ ...all, name: "All" }).map((s) => ({ ...s, defaultActive: false }))]; +} + /** * Create a title formatter for chart titles * @param {string} [cohortTitle] diff --git a/website/scripts/options/types.js b/website/scripts/options/types.js index cb38baf17..fdde56bc2 100644 --- a/website/scripts/options/types.js +++ b/website/scripts/options/types.js @@ -271,46 +271,55 @@ * @property {string} name * @property {string} title * @property {readonly CohortFull[]} list + * @property {CohortAll} all * * @typedef {Object} CohortGroupWithAdjusted * @property {string} name * @property {string} title * @property {readonly CohortWithAdjusted[]} list + * @property {CohortAll} all * * @typedef {Object} CohortGroupWithPercentiles * @property {string} name * @property {string} title * @property {readonly CohortWithPercentiles[]} list + * @property {CohortAll} all * * @typedef {Object} CohortGroupLongTerm * @property {string} name * @property {string} title * @property {readonly CohortLongTerm[]} list + * @property {CohortAll} all * * @typedef {Object} CohortGroupAgeRange * @property {string} name * @property {string} title * @property {readonly CohortAgeRange[]} list + * @property {CohortAll} all * * @typedef {Object} CohortGroupBasicWithMarketCap * @property {string} name * @property {string} title * @property {readonly CohortBasicWithMarketCap[]} list + * @property {CohortAll} all * * @typedef {Object} CohortGroupMinAge * @property {string} name * @property {string} title * @property {readonly CohortMinAge[]} list + * @property {CohortAll} all * * @typedef {Object} CohortGroupBasicWithoutMarketCap * @property {string} name * @property {string} title * @property {readonly CohortBasicWithoutMarketCap[]} list + * @property {CohortAll} all * * @typedef {Object} CohortGroupWithoutRelative * @property {string} name * @property {string} title * @property {readonly CohortWithoutRelative[]} list + * @property {CohortAll} all * * Union of basic cohort group types * @typedef {CohortGroupBasicWithMarketCap | CohortGroupBasicWithoutMarketCap} CohortGroupBasic @@ -319,6 +328,7 @@ * @property {string} name * @property {string} title * @property {readonly UtxoCohortObject[]} list + * @property {CohortAll} all * * @typedef {Object} AddressCohortObject * @property {string} name @@ -334,6 +344,7 @@ * @property {string} name * @property {string} title * @property {readonly AddressCohortObject[]} list + * @property {CohortAll} all * * @typedef {UtxoCohortGroupObject | AddressCohortGroupObject} CohortGroupObject * @@ -341,6 +352,7 @@ * @property {string} name * @property {string} title * @property {readonly CohortAddress[]} list + * @property {CohortAll} all */ // Re-export for type consumers diff --git a/website/scripts/types.js b/website/scripts/types.js index 4fd1e6da6..d69962297 100644 --- a/website/scripts/types.js +++ b/website/scripts/types.js @@ -188,7 +188,7 @@ * * Cohorts with nupl + percentiles (CohortFull and CohortLongTerm both have nupl and percentiles) * @typedef {CohortFull | CohortLongTerm} CohortWithNuplPercentiles - * @typedef {{ name: string, title: string, list: readonly CohortWithNuplPercentiles[] }} CohortGroupWithNuplPercentiles + * @typedef {{ name: string, title: string, list: readonly CohortWithNuplPercentiles[], all: CohortAll }} CohortGroupWithNuplPercentiles * * Cohorts with RealizedWithExtras (realizedCapRelToOwnMarketCap + realizedProfitToLossRatio) * @typedef {CohortAll | CohortFull | CohortWithPercentiles} CohortWithRealizedExtras