website: snapshot

This commit is contained in:
nym21
2026-02-04 20:30:56 +01:00
parent dd96709d18
commit 9b409799c8
11 changed files with 780 additions and 709 deletions

View File

@@ -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<any>, coindaysDestroyed: CountPattern<any> } } }} 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,

View File

@@ -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,

View File

@@ -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),
],
},
],

View File

@@ -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 }),
],
};
}

View File

@@ -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,

View File

@@ -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<T>[]} list
* @param {(item: CohortItem<T>) => 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<T>[]} 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<T>[]} 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<T>[]} 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<T>[]} 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<T>[]} 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<T>[]} 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<T>[]} 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<T>[]} 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<T>[]} 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<T>[]} 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<T>[]} 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<T>[]} 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),
],
};
}

View File

@@ -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,