global: snapshot part 14

This commit is contained in:
nym21
2026-03-21 14:58:33 +01:00
parent 1ed4f258b4
commit 4b3aaee03b
7 changed files with 313 additions and 247 deletions

View File

@@ -50,28 +50,61 @@ function volumeFolderWithProfitability(activity, color, title) {
}),
{
name: "Profitability",
tree: ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`Sent Volume Profitability (${w.title})`),
bottom: [
...satsBtcUsd({
pattern: tv.inProfit.sum[w.key],
name: "In Profit",
tree: [
...ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`Sent Volume Profitability (${w.title})`),
bottom: [
...satsBtcUsd({
pattern: tv.inProfit.sum[w.key],
name: "In Profit",
color: colors.profit,
}),
...satsBtcUsd({
pattern: tv.inLoss.sum[w.key],
name: "In Loss",
color: colors.loss,
}),
],
})),
{
name: "Cumulative",
title: title("Cumulative Sent Volume Profitability"),
bottom: [
...satsBtcUsd({
pattern: tv.inProfit.cumulative,
name: "In Profit",
color: colors.profit,
}),
...satsBtcUsd({
pattern: tv.inLoss.cumulative,
name: "In Loss",
color: colors.loss,
}),
],
},
{
name: "In Profit",
tree: satsBtcUsdFullTree({
pattern: tv.inProfit,
title: title("Sent In Profit"),
color: colors.profit,
}),
...satsBtcUsd({
pattern: tv.inLoss.sum[w.key],
name: "In Loss",
},
{
name: "In Loss",
tree: satsBtcUsdFullTree({
pattern: tv.inLoss,
title: title("Sent In Loss"),
color: colors.loss,
}),
],
})),
},
],
},
],
};
}
// ============================================================================
// Shared SOPR Helpers
// ============================================================================
@@ -112,6 +145,36 @@ function singleRollingSoprTree(ratio, title, prefix = "") {
];
}
/** @returns {PartialOptionsTree} */
function valueDestroyedTree(/** @type {CountPattern<any>} */ valueDestroyed, /** @type {(name: string) => string} */ title) {
return chartsFromCount({ pattern: valueDestroyed, title: title("Value Destroyed"), unit: Unit.usd });
}
/**
* @param {CountPattern<any>} valueDestroyed
* @param {(name: string) => string} title
* @returns {PartialOptionsGroup}
*/
function valueDestroyedFolder(valueDestroyed, title) {
return { name: "Value Destroyed", tree: valueDestroyedTree(valueDestroyed, title) };
}
/**
* @param {CountPattern<any>} valueDestroyed
* @param {CountPattern<any>} adjusted
* @param {(name: string) => string} title
* @returns {PartialOptionsGroup}
*/
function valueDestroyedFolderWithAdjusted(valueDestroyed, adjusted, title) {
return {
name: "Value Destroyed",
tree: [
...valueDestroyedTree(valueDestroyed, title),
{ name: "Adjusted", tree: chartsFromCount({ pattern: adjusted, title: title("Adjusted Value Destroyed"), unit: Unit.usd }) },
],
};
}
// ============================================================================
// Shared Sell Side Risk Helpers
// ============================================================================
@@ -155,16 +218,36 @@ function singleSellSideRiskTree(sellSideRisk, title) {
* @param {CohortAll | CohortFull | CohortLongTerm} cohort
* @param {(name: string) => string} title
* @param {PartialOptionsGroup} soprFolder
* @param {PartialOptionsGroup} valueDestroyedItem
* @returns {PartialOptionsTree}
*/
function singleFullActivityTree(cohort, title, soprFolder) {
function singleFullActivityTree(cohort, title, soprFolder, valueDestroyedItem) {
const { tree, color } = cohort;
return [
volumeFolderWithProfitability(tree.activity, color, title),
soprFolder,
{ name: "Coindays Destroyed", tree: chartsFromCount({ pattern: tree.activity.coindaysDestroyed, title: title("Coindays Destroyed"), unit: Unit.coindays, color }) },
{ name: "Dormancy", tree: averagesArray({ windows: tree.activity.dormancy, title: title("Dormancy"), unit: Unit.days }) },
{ name: "Sell Side Risk", tree: singleSellSideRiskTree(tree.realized.sellSideRiskRatio, title) },
valueDestroyedItem,
{
name: "Coindays Destroyed",
tree: chartsFromCount({
pattern: tree.activity.coindaysDestroyed,
title: title("Coindays Destroyed"),
unit: Unit.coindays,
color,
}),
},
{
name: "Dormancy",
tree: averagesArray({
windows: tree.activity.dormancy,
title: title("Dormancy"),
unit: Unit.days,
}),
},
{
name: "Sell Side Risk",
tree: singleSellSideRiskTree(tree.realized.sellSideRiskRatio, title),
},
];
}
@@ -177,9 +260,12 @@ export function createActivitySectionWithAdjusted({ cohort, title }) {
name: "SOPR",
tree: [
...singleRollingSoprTree(sopr.ratio, title),
{ name: "Adjusted", tree: singleRollingSoprTree(sopr.adjusted.ratio, title, "Adjusted ") },
{
name: "Adjusted",
tree: singleRollingSoprTree(sopr.adjusted.ratio, title, "Adjusted "),
},
],
}),
}, valueDestroyedFolderWithAdjusted(sopr.valueDestroyed, sopr.adjusted.valueDestroyed, title)),
};
}
@@ -190,7 +276,7 @@ export function createActivitySection({ cohort, title }) {
tree: singleFullActivityTree(cohort, title, {
name: "SOPR",
tree: singleRollingSoprTree(cohort.tree.realized.sopr.ratio, title),
}),
}, valueDestroyedFolder(cohort.tree.realized.sopr.valueDestroyed, title)),
};
}
@@ -207,15 +293,6 @@ export function createActivitySectionWithActivity({ cohort, title }) {
name: "Activity",
tree: [
volumeFolderWithProfitability(tree.activity, color, title),
{
name: "Coindays Destroyed",
tree: chartsFromCount({
pattern: tree.activity.coindaysDestroyed,
title: title("Coindays Destroyed"),
unit: Unit.coindays,
color,
}),
},
{
name: "SOPR",
title: title("SOPR (24h)"),
@@ -228,6 +305,16 @@ export function createActivitySectionWithActivity({ cohort, title }) {
}),
],
},
valueDestroyedFolder(sopr.valueDestroyed, title),
{
name: "Coindays Destroyed",
tree: chartsFromCount({
pattern: tree.activity.coindaysDestroyed,
title: title("Coindays Destroyed"),
unit: Unit.coindays,
color,
}),
},
],
};
}
@@ -270,7 +357,43 @@ export function createGroupedActivitySectionMinimal({ list, all, title }) {
}
/**
* Grouped volume folder with In Profit/Loss subfolders
* Grouped profitability folder (compare + in profit + in loss)
* @template {{ name: string, color: Color }} T
* @template {{ name: string, color: Color }} A
* @param {readonly T[]} list
* @param {A} all
* @param {(name: string) => string} title
* @param {(c: T | A) => { sum: Record<string, AnyValuePattern>, cumulative: AnyValuePattern }} getInProfit
* @param {(c: T | A) => { sum: Record<string, AnyValuePattern>, cumulative: AnyValuePattern }} getInLoss
* @returns {PartialOptionsTree}
*/
function groupedProfitabilityArray(list, all, title, getInProfit, getInLoss) {
return [
{
name: "In Profit",
tree: groupedWindowsCumulativeSatsBtcUsd({
list,
all,
title,
metricTitle: "Sent In Profit",
getMetric: (c) => getInProfit(c),
}),
},
{
name: "In Loss",
tree: groupedWindowsCumulativeSatsBtcUsd({
list,
all,
title,
metricTitle: "Sent In Loss",
getMetric: (c) => getInLoss(c),
}),
},
];
}
/**
* Grouped volume folder with profitability subfolders
* @template {{ name: string, color: Color }} T
* @template {{ name: string, color: Color }} A
* @param {readonly T[]} list
@@ -283,9 +406,20 @@ function groupedVolumeFolder(list, all, title, getTransferVolume) {
return {
name: "Volume",
tree: [
...groupedWindowsCumulativeSatsBtcUsd({ list, all, title, metricTitle: "Sent Volume", getMetric: (c) => getTransferVolume(c) }),
{ name: "In Profit", tree: groupedWindowsCumulativeSatsBtcUsd({ list, all, title, metricTitle: "Sent In Profit", getMetric: (c) => getTransferVolume(c).inProfit }) },
{ name: "In Loss", tree: groupedWindowsCumulativeSatsBtcUsd({ list, all, title, metricTitle: "Sent In Loss", getMetric: (c) => getTransferVolume(c).inLoss }) },
...groupedWindowsCumulativeSatsBtcUsd({
list,
all,
title,
metricTitle: "Sent Volume",
getMetric: (c) => getTransferVolume(c),
}),
...groupedProfitabilityArray(
list,
all,
title,
(c) => getTransferVolume(c).inProfit,
(c) => getTransferVolume(c).inLoss,
),
],
};
}
@@ -320,6 +454,57 @@ function groupedSoprCharts(list, all, getRatio, title, prefix = "") {
}));
}
/**
* @template {{ name: string, color: Color }} T
* @template {{ name: string, color: Color }} A
* @param {readonly T[]} list
* @param {A} all
* @param {(name: string) => string} title
* @param {(c: T | A) => CountPattern<any>} getValueDestroyed
* @returns {PartialOptionsTree}
*/
function groupedValueDestroyedTree(list, all, title, getValueDestroyed) {
return groupedWindowsCumulative({
list, all, title, metricTitle: "Value Destroyed",
getWindowSeries: (c, key) => getValueDestroyed(c).sum[key],
getCumulativeSeries: (c) => getValueDestroyed(c).cumulative,
seriesFn: line, unit: Unit.usd,
});
}
/**
* @template {{ name: string, color: Color }} T
* @template {{ name: string, color: Color }} A
* @param {readonly T[]} list
* @param {A} all
* @param {(name: string) => string} title
* @param {(c: T | A) => CountPattern<any>} getValueDestroyed
* @returns {PartialOptionsGroup}
*/
function groupedValueDestroyedFolder(list, all, title, getValueDestroyed) {
return { name: "Value Destroyed", tree: groupedValueDestroyedTree(list, all, title, getValueDestroyed) };
}
/**
* @template {{ name: string, color: Color }} T
* @template {{ name: string, color: Color }} A
* @param {readonly T[]} list
* @param {A} all
* @param {(name: string) => string} title
* @param {(c: T | A) => CountPattern<any>} getValueDestroyed
* @param {(c: T | A) => CountPattern<any>} getAdjustedValueDestroyed
* @returns {PartialOptionsGroup}
*/
function groupedValueDestroyedFolderWithAdjusted(list, all, title, getValueDestroyed, getAdjustedValueDestroyed) {
return {
name: "Value Destroyed",
tree: [
...groupedValueDestroyedTree(list, all, title, getValueDestroyed),
{ name: "Adjusted", tree: groupedValueDestroyedTree(list, all, title, getAdjustedValueDestroyed) },
],
};
}
// ============================================================================
// Grouped Value/Flow Helpers
// ============================================================================
@@ -345,12 +530,19 @@ function groupedSoprCharts(list, all, getRatio, title, prefix = "") {
* @param {CohortAll} all
* @param {(name: string) => string} title
* @param {PartialOptionsGroup} soprFolder
* @param {PartialOptionsGroup} valueDestroyedItem
* @returns {PartialOptionsTree}
*/
function groupedFullActivityTree(list, all, title, soprFolder) {
function groupedFullActivityTree(list, all, title, soprFolder, valueDestroyedItem) {
return [
groupedVolumeFolder(list, all, title, (c) => c.tree.activity.transferVolume),
groupedVolumeFolder(
list,
all,
title,
(c) => c.tree.activity.transferVolume,
),
soprFolder,
valueDestroyedItem,
...groupedActivitySharedItems(list, all, title),
];
}
@@ -362,10 +554,24 @@ export function createGroupedActivitySectionWithAdjusted({ list, all, title }) {
tree: groupedFullActivityTree(list, all, title, {
name: "SOPR",
tree: [
...groupedSoprCharts(list, all, (c) => c.tree.realized.sopr.ratio, title),
{ name: "Adjusted", tree: groupedSoprCharts(list, all, (c) => c.tree.realized.sopr.adjusted.ratio, title, "Adjusted ") },
...groupedSoprCharts(
list,
all,
(c) => c.tree.realized.sopr.ratio,
title,
),
{
name: "Adjusted",
tree: groupedSoprCharts(
list,
all,
(c) => c.tree.realized.sopr.adjusted.ratio,
title,
"Adjusted ",
),
},
],
}),
}, groupedValueDestroyedFolderWithAdjusted(list, all, title, (c) => c.tree.realized.sopr.valueDestroyed, (c) => c.tree.realized.sopr.adjusted.valueDestroyed)),
};
}
@@ -375,8 +581,13 @@ export function createGroupedActivitySection({ list, all, title }) {
name: "Activity",
tree: groupedFullActivityTree(list, all, title, {
name: "SOPR",
tree: groupedSoprCharts(list, all, (c) => c.tree.realized.sopr.ratio, title),
}),
tree: groupedSoprCharts(
list,
all,
(c) => c.tree.realized.sopr.ratio,
title,
),
}, groupedValueDestroyedFolder(list, all, title, (c) => c.tree.realized.sopr.valueDestroyed)),
};
}
@@ -392,10 +603,15 @@ function groupedActivitySharedItems(list, all, title) {
{
name: "Coindays Destroyed",
tree: groupedWindowsCumulative({
list, all, title, metricTitle: "Coindays Destroyed",
list,
all,
title,
metricTitle: "Coindays Destroyed",
getWindowSeries: (c, key) => c.tree.activity.coindaysDestroyed.sum[key],
getCumulativeSeries: (c) => c.tree.activity.coindaysDestroyed.cumulative,
seriesFn: line, unit: Unit.coindays,
getCumulativeSeries: (c) =>
c.tree.activity.coindaysDestroyed.cumulative,
seriesFn: line,
unit: Unit.coindays,
}),
},
{
@@ -404,7 +620,12 @@ function groupedActivitySharedItems(list, all, title) {
name: w.name,
title: title(`Dormancy (${w.title})`),
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
line({ series: tree.activity.dormancy[w.key], name, color, unit: Unit.days }),
line({
series: tree.activity.dormancy[w.key],
name,
color,
unit: Unit.days,
}),
),
})),
},
@@ -414,14 +635,18 @@ function groupedActivitySharedItems(list, all, title) {
name: w.name,
title: title(`Sell Side Risk (${w.title})`),
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
line({ series: tree.realized.sellSideRiskRatio[w.key].ratio, name, color, unit: Unit.ratio }),
line({
series: tree.realized.sellSideRiskRatio[w.key].ratio,
name,
color,
unit: Unit.ratio,
}),
),
})),
},
];
}
/**
* Grouped activity for cohorts with activity but basic realized (AgeRange/MaxAge)
* @param {{ list: readonly (CohortAgeRange | CohortWithAdjusted)[], all: CohortAll, title: (name: string) => string }} args
@@ -433,17 +658,26 @@ export function createGroupedActivitySectionWithActivity({ list, all, title }) {
tree: [
{
name: "Volume",
tree: ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`Sent Volume (${w.title})`),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
satsBtcUsd({
pattern: tree.activity.transferVolume.sum[w.key],
name,
color,
}),
tree: [
...ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`Sent Volume (${w.title})`),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
satsBtcUsd({
pattern: tree.activity.transferVolume.sum[w.key],
name,
color,
}),
),
})),
...groupedProfitabilityArray(
list,
all,
title,
(c) => c.tree.activity.transferVolume.inProfit,
(c) => c.tree.activity.transferVolume.inLoss,
),
})),
],
},
{
name: "SOPR",
@@ -458,6 +692,7 @@ export function createGroupedActivitySectionWithActivity({ list, all, title }) {
}),
),
},
groupedValueDestroyedFolder(list, all, title, (c) => c.tree.realized.sopr.valueDestroyed),
{
name: "Coindays Destroyed",
tree: [

View File

@@ -21,7 +21,7 @@ import {
} from "../series.js";
import {
satsBtcUsd,
mapCohorts,
flatMapCohorts,
mapCohortsWithAll,
flatMapCohortsWithAll,
} from "../shared.js";
@@ -175,7 +175,7 @@ function profitabilityChart(supply, title) {
}
/**
* @param {{ toCirculating: { percent: AnySeriesPattern }, inProfit: { toCirculating: { percent: AnySeriesPattern } }, inLoss: { toCirculating: { percent: AnySeriesPattern } } }} supply
* @param {{ toCirculating: PercentRatioPattern, inProfit: { toCirculating: PercentRatioPattern }, inLoss: { toCirculating: PercentRatioPattern } }} supply
* @param {(name: string) => string} title
* @returns {PartialChartOption}
*/
@@ -184,9 +184,9 @@ function circulatingChart(supply, title) {
name: "% of Circulating",
title: title("Supply (% of Circulating)"),
bottom: [
line({ series: supply.toCirculating.percent, name: "Total", color: colors.default, unit: Unit.percentage }),
line({ series: supply.inProfit.toCirculating.percent, name: "In Profit", color: colors.profit, unit: Unit.percentage }),
line({ series: supply.inLoss.toCirculating.percent, name: "In Loss", color: colors.loss, unit: Unit.percentage }),
...percentRatio({ pattern: supply.toCirculating, name: "Total", color: colors.default }),
...percentRatio({ pattern: supply.inProfit.toCirculating, name: "In Profit", color: colors.profit }),
...percentRatio({ pattern: supply.inLoss.toCirculating, name: "In Loss", color: colors.loss }),
],
};
}
@@ -539,7 +539,7 @@ export function createGroupedHoldingsSectionWithOwnSupply({ list, all, title })
tree: [
groupedSupplyTotal(list, all, title),
...groupedSupplyProfitLoss(list, all, title),
{ name: "% of Circulating", title: title("Supply (% of Circulating)"), bottom: mapCohorts(list, ({ name, color, tree }) => line({ series: tree.supply.toCirculating.percent, name, color, unit: Unit.percentage })) },
{ name: "% of Circulating", title: title("Supply (% of Circulating)"), bottom: flatMapCohorts(list, ({ name, color, tree }) => percentRatio({ pattern: tree.supply.toCirculating, name, color })) },
...groupedDeltaItems(list, all, (c) => c.tree.supply.delta, Unit.sats, title, "Supply"),
],
},
@@ -560,7 +560,7 @@ export function createGroupedHoldingsSectionWithRelative({ list, all, title }) {
tree: [
groupedSupplyTotal(list, all, title),
...groupedSupplyProfitLoss(list, all, title),
{ name: "% of Circulating", title: title("Supply (% of Circulating)"), bottom: mapCohorts(list, ({ name, color, tree }) => line({ series: tree.supply.toCirculating.percent, name, color, unit: Unit.percentage })) },
{ name: "% of Circulating", title: title("Supply (% of Circulating)"), bottom: flatMapCohorts(list, ({ name, color, tree }) => percentRatio({ pattern: tree.supply.toCirculating, name, color })) },
{ name: "% of Own Supply", title: title("Supply (% of Own)"), bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ series: tree.supply.inProfit.toOwn.percent, name, color, unit: Unit.percentage })) },
...groupedDeltaItems(list, all, (c) => c.tree.supply.delta, Unit.sats, title, "Supply"),
],

View File

@@ -58,18 +58,15 @@ import {
createGroupedCostBasisSectionWithPercentiles,
} from "./cost-basis.js";
import {
createProfitabilitySection,
createProfitabilitySectionAll,
createProfitabilitySectionFull,
createProfitabilitySectionWithNupl,
createProfitabilitySectionWithInvestedCapitalPct,
createProfitabilitySectionBasicWithInvestedCapitalPct,
createProfitabilitySectionAddress,
createProfitabilitySectionWithProfitLoss,
createProfitabilitySectionWithInvestedCapitalPct,
createProfitabilitySectionLongTerm,
createGroupedProfitabilitySection,
createGroupedProfitabilitySectionWithNupl,
createGroupedProfitabilitySectionWithInvestedCapitalPct,
createGroupedProfitabilitySectionBasicWithInvestedCapitalPct,
} from "./profitability.js";
import {
createActivitySection,
@@ -161,7 +158,7 @@ export function createCohortFolderWithNupl(cohort) {
createValuationSectionFull({ cohort, title }),
createPricesSectionFull({ cohort, title }),
createCostBasisSectionWithPercentiles({ cohort, title }),
createProfitabilitySectionWithNupl({ cohort, title }),
createProfitabilitySection({ cohort, title }),
createActivitySection({ cohort, title }),
],
};
@@ -237,30 +234,12 @@ export function createCohortFolderBasicWithMarketCap(cohort) {
...createHoldingsSection({ cohort, title }),
createValuationSection({ cohort, title }),
createPricesSectionBasic({ cohort, title }),
createProfitabilitySectionWithNupl({ cohort, title }),
createProfitabilitySection({ cohort, title }),
createActivitySectionMinimal({ cohort, title }),
],
};
}
/**
* Basic folder WITHOUT RelToMarketCap
* @param {CohortBasicWithoutMarketCap} cohort
* @returns {PartialOptionsGroup}
*/
export function createCohortFolderBasicWithoutMarketCap(cohort) {
const title = formatCohortTitle(cohort.name);
return {
name: cohort.name || "all",
tree: [
...createHoldingsSection({ cohort, title }),
createValuationSection({ cohort, title }),
createPricesSectionBasic({ cohort, title }),
createProfitabilitySectionBasicWithInvestedCapitalPct({ cohort, title }),
createActivitySectionMinimal({ cohort, title }),
],
};
}
/**
* Address folder: like basic but with address count
@@ -275,7 +254,7 @@ export function createCohortFolderAddress(cohort) {
...createHoldingsSectionAddress({ cohort, title }),
createValuationSection({ cohort, title }),
createPricesSectionBasic({ cohort, title }),
createProfitabilitySectionAddress({ cohort, title }),
createProfitabilitySectionWithProfitLoss({ cohort, title }),
createActivitySectionMinimal({ cohort, title }),
],
};
@@ -313,7 +292,7 @@ export function createAddressCohortFolder(cohort) {
...createHoldingsSectionAddressAmount({ cohort, title }),
createValuationSection({ cohort, title }),
createPricesSectionBasic({ cohort, title }),
createProfitabilitySectionWithNupl({ cohort, title }),
createProfitabilitySection({ cohort, title }),
createActivitySectionMinimal({ cohort, title }),
],
};
@@ -458,32 +437,6 @@ export function createGroupedCohortFolderBasicWithMarketCap({
};
}
/**
* @param {CohortGroupBasicWithoutMarketCap} args
* @returns {PartialOptionsGroup}
*/
export function createGroupedCohortFolderBasicWithoutMarketCap({
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 }),
createGroupedProfitabilitySectionBasicWithInvestedCapitalPct({
list,
all,
title,
}),
createGroupedActivitySectionMinimal({ list, all, title }),
],
};
}
/**
* @param {CohortGroupAddr} args
@@ -502,7 +455,7 @@ export function createGroupedCohortFolderAddress({
...createGroupedHoldingsSectionAddress({ list, all, title }),
createGroupedValuationSection({ list, all, title }),
createGroupedPricesSection({ list, all, title }),
createGroupedProfitabilitySectionBasicWithInvestedCapitalPct({
createGroupedProfitabilitySection({
list,
all,
title,

View File

@@ -683,21 +683,11 @@ function realizedSubfolderBasic(r, title) {
* @returns {PartialOptionsGroup}
*/
export function createProfitabilitySection({ cohort, title }) {
const { tree } = cohort;
return {
name: "Profitability",
tree: [
{
name: "Unrealized",
tree: [
{
name: "NUPL",
title: title("NUPL"),
bottom: nuplSeries(tree.unrealized.nupl),
},
],
},
realizedSubfolderBasic(tree.realized, title),
{ name: "Unrealized", tree: [{ name: "NUPL", title: title("NUPL"), bottom: nuplSeries(cohort.tree.unrealized.nupl) }] },
realizedSubfolderBasic(cohort.tree.realized, title),
],
};
}
@@ -792,30 +782,6 @@ export function createProfitabilitySectionFull({ cohort, title }) {
};
}
/**
* Section with NUPL (basic cohorts with market cap — NuplPattern unrealized)
* @param {{ cohort: CohortBasicWithMarketCap, title: (name: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createProfitabilitySectionWithNupl({ cohort, title }) {
const { tree } = cohort;
return {
name: "Profitability",
tree: [
{
name: "Unrealized",
tree: [
{
name: "NUPL",
title: title("NUPL"),
bottom: nuplSeries(tree.unrealized.nupl),
},
],
},
realizedSubfolderBasic(tree.realized, title),
],
};
}
/**
* Section for LongTerm cohort
@@ -859,78 +825,7 @@ export function createProfitabilitySectionWithInvestedCapitalPct({
};
}
/**
* Section with invested capital % but no unrealized relative (basic cohorts)
* @param {{ cohort: CohortBasicWithoutMarketCap, title: (name: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createProfitabilitySectionBasicWithInvestedCapitalPct({
cohort,
title,
}) {
const { tree } = cohort;
return {
name: "Profitability",
tree: [
{
name: "Unrealized",
tree: [
{
name: "NUPL",
title: title("NUPL"),
bottom: nuplSeries(tree.unrealized.nupl),
},
],
},
realizedSubfolderBasic(tree.realized, title),
],
};
}
/**
* Section for CohortAddr (has unrealized profit/loss + NUPL, basic realized)
* @param {{ cohort: CohortAddr, title: (name: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createProfitabilitySectionAddress({ cohort, title }) {
const u = cohort.tree.unrealized;
return {
name: "Profitability",
tree: [
{
name: "Unrealized",
tree: [
{ name: "NUPL", title: title("NUPL"), bottom: nuplSeries(u.nupl) },
{
name: "Profit",
title: title("Unrealized Profit"),
bottom: [
line({
series: u.profit.usd,
name: "Profit",
color: colors.profit,
unit: Unit.usd,
}),
],
},
{
name: "Loss",
title: title("Unrealized Loss"),
bottom: [
line({
series: u.loss.usd,
name: "Loss",
color: colors.loss,
unit: Unit.usd,
}),
],
},
],
},
realizedSubfolderBasic(cohort.tree.realized, title),
],
};
}
// ============================================================================
// Grouped Cohort Helpers
@@ -1324,24 +1219,6 @@ export function createGroupedProfitabilitySectionWithProfitLoss({
};
}
/**
* Grouped section with invested capital % (basic cohorts — uses NUPL only)
* @param {{ list: readonly CohortBasicWithoutMarketCap[], all: CohortAll, title: (name: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createGroupedProfitabilitySectionBasicWithInvestedCapitalPct({
list,
all,
title,
}) {
return {
name: "Profitability",
tree: [
{ name: "Unrealized", tree: groupedNuplCharts(list, all, title) },
groupedRealizedSubfolder(list, all, title),
],
};
}
/**
* Grouped section for ageRange/maxAge cohorts

View File

@@ -88,8 +88,8 @@ export function createValuationSectionFull({ cohort, title }) {
],
},
{ name: "MVRV", title: title("MVRV"), bottom: ratioBottomSeries(tree.realized.price) },
{ name: "% of Own Market Cap", title: title("Realized Cap (% of Own Market Cap)"), bottom: percentRatioBaseline({ pattern: tree.realized.cap.toOwnMcap, name: "Rel. to Own Market Cap", color }) },
...singleDeltaItems(tree, title),
{ name: "% of Own Market Cap", title: title("Realized Cap (% of Own Market Cap)"), bottom: percentRatioBaseline({ pattern: tree.realized.cap.toOwnMcap, name: "Rel. to Own Market Cap", color }) },
],
};
}