mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-25 07:09:59 -07:00
global: snapshot part 14
This commit is contained in:
@@ -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: [
|
||||
|
||||
@@ -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"),
|
||||
],
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }) },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
createCohortFolderLongTerm,
|
||||
createCohortFolderAgeRangeWithMatured,
|
||||
createCohortFolderBasicWithMarketCap,
|
||||
createCohortFolderBasicWithoutMarketCap,
|
||||
createCohortFolderWithoutRelative,
|
||||
createCohortFolderAddress,
|
||||
createAddressCohortFolder,
|
||||
@@ -16,7 +15,6 @@ import {
|
||||
createGroupedCohortFolderWithNupl,
|
||||
createGroupedCohortFolderAgeRangeWithMatured,
|
||||
createGroupedCohortFolderBasicWithMarketCap,
|
||||
createGroupedCohortFolderBasicWithoutMarketCap,
|
||||
createGroupedCohortFolderAddress,
|
||||
createGroupedAddressCohortFolder,
|
||||
createUtxoProfitabilitySection,
|
||||
@@ -160,14 +158,14 @@ export function createPartialOptions() {
|
||||
{
|
||||
name: "Range",
|
||||
tree: [
|
||||
createGroupedCohortFolderBasicWithoutMarketCap({
|
||||
createGroupedCohortFolderBasicWithMarketCap({
|
||||
name: "Compare",
|
||||
title: "Amount Ranges",
|
||||
list: utxosAmountRange,
|
||||
all: cohortAll,
|
||||
}),
|
||||
...utxosAmountRange.map(
|
||||
createCohortFolderBasicWithoutMarketCap,
|
||||
createCohortFolderBasicWithMarketCap,
|
||||
),
|
||||
],
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user