global: snapshot part 4

This commit is contained in:
nym21
2026-03-20 14:27:10 +01:00
parent 1d671ea41f
commit 8f93ff9f68
47 changed files with 683 additions and 637 deletions

View File

@@ -9,7 +9,15 @@
*/
import { Unit } from "../../utils/units.js";
import { line, baseline, dotsBaseline, percentRatio, chartsFromCount, averagesTree, ROLLING_WINDOWS } from "../series.js";
import {
line,
baseline,
dotsBaseline,
percentRatio,
chartsFromCount,
averagesArray,
ROLLING_WINDOWS,
} from "../series.js";
import {
satsBtcUsdFullTree,
mapCohortsWithAll,
@@ -33,7 +41,6 @@ function volumeAndCoinsTree(activity, color, title) {
name: "Volume",
tree: satsBtcUsdFullTree({
pattern: activity.transferVolume,
name: "Volume",
title: title("Sent Volume"),
color,
}),
@@ -62,7 +69,6 @@ function sentProfitLossTree(sent, title) {
name: "Sent In Profit",
tree: satsBtcUsdFullTree({
pattern: sent.inProfit,
name: "In Profit",
title: title("Sent Volume In Profit"),
color: colors.profit,
}),
@@ -71,7 +77,6 @@ function sentProfitLossTree(sent, title) {
name: "Sent In Loss",
tree: satsBtcUsdFullTree({
pattern: sent.inLoss,
name: "In Loss",
title: title("Sent Volume In Loss"),
color: colors.loss,
}),
@@ -90,7 +95,14 @@ function fullVolumeTree(activity, color, title) {
return [
...volumeAndCoinsTree(activity, color, title),
...sentProfitLossTree(activity.transferVolume, title),
averagesTree({ windows: activity.dormancy, title: title("Dormancy"), unit: Unit.days, name: "Dormancy" }),
{
name: "Dormancy",
tree: averagesArray({
windows: activity.dormancy,
title: title("Dormancy"),
unit: Unit.days,
}),
},
];
}
@@ -110,13 +122,26 @@ function singleRollingSoprTree(ratio, title, prefix = "") {
name: "Compare",
title: title(`${prefix}SOPR`),
bottom: ROLLING_WINDOWS.map((w) =>
baseline({ series: ratio[w.key], name: w.name, color: w.color, unit: Unit.ratio, base: 1 }),
baseline({
series: ratio[w.key],
name: w.name,
color: w.color,
unit: Unit.ratio,
base: 1,
}),
),
},
...ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`${prefix}SOPR (${w.title})`),
bottom: [baseline({ series: ratio[w.key], name: "SOPR", unit: Unit.ratio, base: 1 })],
bottom: [
baseline({
series: ratio[w.key],
name: "SOPR",
unit: Unit.ratio,
base: 1,
}),
],
})),
];
}
@@ -136,7 +161,11 @@ function singleSellSideRiskTree(sellSideRisk, title) {
name: "Compare",
title: title("Sell Side Risk"),
bottom: ROLLING_WINDOWS.flatMap((w) =>
percentRatio({ pattern: sellSideRisk[w.key], name: w.name, color: w.color }),
percentRatio({
pattern: sellSideRisk[w.key],
name: w.name,
color: w.color,
}),
),
},
...ROLLING_WINDOWS.map((w) => ({
@@ -171,11 +200,18 @@ export function createActivitySectionWithAdjusted({ cohort, title }) {
...singleRollingSoprTree(sopr.ratio, title),
{
name: "Adjusted",
tree: singleRollingSoprTree(sopr.adjusted.ratio, title, "Adjusted "),
tree: singleRollingSoprTree(
sopr.adjusted.ratio,
title,
"Adjusted ",
),
},
],
},
{ name: "Sell Side Risk", tree: singleSellSideRiskTree(r.sellSideRiskRatio, title) },
{
name: "Sell Side Risk",
tree: singleSellSideRiskTree(r.sellSideRiskRatio, title),
},
],
};
}
@@ -198,7 +234,10 @@ export function createActivitySection({ cohort, title }) {
name: "SOPR",
tree: singleRollingSoprTree(sopr.ratio, title),
},
{ name: "Sell Side Risk", tree: singleSellSideRiskTree(r.sellSideRiskRatio, title) },
{
name: "Sell Side Risk",
tree: singleSellSideRiskTree(r.sellSideRiskRatio, title),
},
],
};
}
@@ -220,13 +259,19 @@ export function createActivitySectionWithActivity({ cohort, title }) {
{
name: "SOPR",
title: title("SOPR (24h)"),
bottom: [dotsBaseline({ series: sopr.ratio._24h, name: "SOPR", unit: Unit.ratio, base: 1 })],
bottom: [
dotsBaseline({
series: sopr.ratio._24h,
name: "SOPR",
unit: Unit.ratio,
base: 1,
}),
],
},
],
};
}
/**
* Minimal activity section: volume only
* @param {{ cohort: CohortBasicWithMarketCap | CohortBasicWithoutMarketCap | CohortWithoutRelative | CohortAddr | AddrCohortObject, title: (name: string) => string }} args
@@ -237,7 +282,6 @@ export function createActivitySectionMinimal({ cohort, title }) {
name: "Activity",
tree: satsBtcUsdFullTree({
pattern: cohort.tree.activity.transferVolume,
name: "Volume",
title: title("Volume"),
}),
};
@@ -255,7 +299,12 @@ export function createGroupedActivitySectionMinimal({ list, all, title }) {
name: w.name,
title: title(`Volume (${w.title})`),
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
line({ series: tree.activity.transferVolume.sum[w.key].sats, name, color, unit: Unit.sats }),
line({
series: tree.activity.transferVolume.sum[w.key].sats,
name,
color,
unit: Unit.sats,
}),
),
})),
};
@@ -280,7 +329,13 @@ function groupedSoprCharts(list, all, getRatio, title, prefix = "") {
name: w.name,
title: title(`${prefix}SOPR (${w.title})`),
bottom: mapCohortsWithAll(list, all, (c) =>
baseline({ series: getRatio(c)[w.key], name: c.name, color: c.color, unit: Unit.ratio, base: 1 }),
baseline({
series: getRatio(c)[w.key],
name: c.name,
color: c.color,
unit: Unit.ratio,
base: 1,
}),
),
}));
}
@@ -300,7 +355,6 @@ function groupedSoprCharts(list, all, getRatio, title, prefix = "") {
* @returns {PartialOptionsTree}
*/
// ============================================================================
// Grouped Activity Sections
// ============================================================================
@@ -317,16 +371,32 @@ export function createGroupedActivitySectionWithAdjusted({ list, all, title }) {
name: "Volume",
title: title("Sent Volume"),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => [
line({ series: tree.activity.transferVolume.sum._24h.sats, name, color, unit: Unit.sats }),
line({
series: tree.activity.transferVolume.sum._24h.sats,
name,
color,
unit: Unit.sats,
}),
]),
},
{
name: "SOPR",
tree: [
...groupedSoprCharts(list, all, (c) => c.tree.realized.sopr.ratio, title),
...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 "),
tree: groupedSoprCharts(
list,
all,
(c) => c.tree.realized.sopr.adjusted.ratio,
title,
"Adjusted ",
),
},
],
},
@@ -336,7 +406,12 @@ export function createGroupedActivitySectionWithAdjusted({ 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,
}),
),
})),
},
@@ -344,7 +419,12 @@ export function createGroupedActivitySectionWithAdjusted({ list, all, title }) {
name: "Coindays Destroyed",
title: title("Coindays Destroyed"),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => [
line({ series: tree.activity.coindaysDestroyed.sum._24h, name, color, unit: Unit.coindays }),
line({
series: tree.activity.coindaysDestroyed.sum._24h,
name,
color,
unit: Unit.coindays,
}),
]),
},
],
@@ -364,13 +444,23 @@ export function createGroupedActivitySection({ list, all, title }) {
name: "Volume",
title: title("Sent Volume"),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => [
line({ series: tree.activity.transferVolume.sum._24h.sats, name, color, unit: Unit.sats }),
line({
series: tree.activity.transferVolume.sum._24h.sats,
name,
color,
unit: Unit.sats,
}),
]),
},
{
name: "SOPR",
tree: [
...groupedSoprCharts(list, all, (c) => c.tree.realized.sopr.ratio, title),
...groupedSoprCharts(
list,
all,
(c) => c.tree.realized.sopr.ratio,
title,
),
],
},
{
@@ -379,7 +469,12 @@ export function createGroupedActivitySection({ 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,
}),
),
})),
},
@@ -387,7 +482,12 @@ export function createGroupedActivitySection({ list, all, title }) {
name: "Coindays Destroyed",
title: title("Coindays Destroyed"),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => [
line({ series: tree.activity.coindaysDestroyed.sum._24h, name, color, unit: Unit.coindays }),
line({
series: tree.activity.coindaysDestroyed.sum._24h,
name,
color,
unit: Unit.coindays,
}),
]),
},
],
@@ -407,24 +507,39 @@ export function createGroupedActivitySectionWithActivity({ list, all, title }) {
name: "Volume",
title: title("Sent Volume"),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => [
line({ series: tree.activity.transferVolume.sum._24h.sats, name, color, unit: Unit.sats }),
line({
series: tree.activity.transferVolume.sum._24h.sats,
name,
color,
unit: Unit.sats,
}),
]),
},
{
name: "SOPR",
title: title("SOPR (24h)"),
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
baseline({ series: tree.realized.sopr.ratio._24h, name, color, unit: Unit.ratio, base: 1 }),
baseline({
series: tree.realized.sopr.ratio._24h,
name,
color,
unit: Unit.ratio,
base: 1,
}),
),
},
{
name: "Coindays Destroyed",
title: title("Coindays Destroyed"),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => [
line({ series: tree.activity.coindaysDestroyed.sum._24h, name, color, unit: Unit.coindays }),
line({
series: tree.activity.coindaysDestroyed.sum._24h,
name,
color,
unit: Unit.coindays,
}),
]),
},
],
};
}

View File

@@ -93,7 +93,7 @@ export function createCostBasisSectionWithPercentiles({ cohort, title }) {
tree: singleWeightFolder({
avgPrice: tree.realized.price, avgName: "Average",
inProfit: cb.inProfit.perCoin, inLoss: cb.inLoss.perCoin,
percentiles: cb.percentiles, color, weightLabel: "BTC-weighted", title,
percentiles: cb.perCoin, color, weightLabel: "BTC-weighted", title,
min: cb.min, max: cb.max,
}),
},
@@ -102,7 +102,7 @@ export function createCostBasisSectionWithPercentiles({ cohort, title }) {
tree: singleWeightFolder({
avgPrice: tree.realized.investor.price, avgName: "Average",
inProfit: cb.inProfit.perDollar, inLoss: cb.inLoss.perDollar,
percentiles: cb.investedCapital, color, weightLabel: "USD-weighted", title,
percentiles: cb.perDollar, color, weightLabel: "USD-weighted", title,
}),
},
{
@@ -215,7 +215,7 @@ export function createGroupedCostBasisSectionWithPercentiles({ list, all, title
getAvgPrice: (c) => c.tree.realized.price,
getInProfit: (c) => c.tree.costBasis.inProfit.perCoin,
getInLoss: (c) => c.tree.costBasis.inLoss.perCoin,
getPercentiles: (c) => c.tree.costBasis.percentiles,
getPercentiles: (c) => c.tree.costBasis.perCoin,
avgTitle: "Average", weightLabel: "BTC-weighted",
}),
},
@@ -226,7 +226,7 @@ export function createGroupedCostBasisSectionWithPercentiles({ list, all, title
getAvgPrice: (c) => c.tree.realized.investor.price,
getInProfit: (c) => c.tree.costBasis.inProfit.perDollar,
getInLoss: (c) => c.tree.costBasis.inLoss.perDollar,
getPercentiles: (c) => c.tree.costBasis.investedCapital,
getPercentiles: (c) => c.tree.costBasis.perDollar,
avgTitle: "Average", weightLabel: "USD-weighted",
}),
},

View File

@@ -10,8 +10,19 @@
* - activity.js: SOPR, Volume, Lifespan
*/
import { formatCohortTitle, satsBtcUsd, satsBtcUsdFullTree } from "../shared.js";
import { ROLLING_WINDOWS, line, baseline, percentRatio, sumsTree, rollingPercentRatioTree } from "../series.js";
import {
formatCohortTitle,
satsBtcUsd,
satsBtcUsdFullTree,
} from "../shared.js";
import {
ROLLING_WINDOWS,
line,
baseline,
percentRatio,
sumsTree,
rollingPercentRatioTree,
} from "../series.js";
import { Unit } from "../../utils/units.js";
import { colors } from "../../utils/colors.js";
@@ -210,7 +221,6 @@ export function createCohortFolderAgeRangeWithMatured(cohort) {
name: "Matured",
tree: satsBtcUsdFullTree({
pattern: cohort.matured,
name: cohort.name,
title: title("Matured Supply"),
}),
});
@@ -452,13 +462,22 @@ export function createGroupedCohortFolderAgeRangeWithMatured({
list,
all,
}) {
const folder = createGroupedCohortFolderAgeRange({ name, title: groupTitle, list, all });
const folder = createGroupedCohortFolderAgeRange({
name,
title: groupTitle,
list,
all,
});
const title = formatCohortTitle(groupTitle);
folder.tree.push({
name: "Matured",
title: title("Matured Supply"),
bottom: list.flatMap((cohort) =>
satsBtcUsd({ pattern: cohort.matured.base, name: cohort.name, color: cohort.color }),
satsBtcUsd({
pattern: cohort.matured.base,
name: cohort.name,
color: cohort.color,
}),
),
});
return folder;
@@ -607,14 +626,32 @@ function singleBucketFolder({ name, color, pattern }) {
title: `${name}: Supply`,
bottom: [
...satsBtcUsd({ pattern: pattern.supply.all, name: "Total" }),
...satsBtcUsd({ pattern: pattern.supply.sth, name: "STH", color: colors.term.short }),
...satsBtcUsd({
pattern: pattern.supply.sth,
name: "STH",
color: colors.term.short,
}),
],
},
{
name: "Change",
tree: [
{ ...sumsTree({ windows: pattern.supply.all.delta.absolute, title: `${name}: Supply Change`, unit: Unit.sats, series: baseline }), name: "Absolute" },
{ ...rollingPercentRatioTree({ windows: pattern.supply.all.delta.rate, title: `${name}: Supply Rate` }), name: "Rate" },
{
...sumsTree({
windows: pattern.supply.all.delta.absolute,
title: `${name}: Supply Change`,
unit: Unit.sats,
series: baseline,
}),
name: "Absolute",
},
{
...rollingPercentRatioTree({
windows: pattern.supply.all.delta.rate,
title: `${name}: Supply Rate`,
}),
name: "Rate",
},
],
},
],
@@ -623,14 +660,25 @@ function singleBucketFolder({ name, color, pattern }) {
name: "Realized Cap",
title: `${name}: Realized Cap`,
bottom: [
line({ series: pattern.realizedCap.all, name: "Total", unit: Unit.usd }),
line({ series: pattern.realizedCap.sth, name: "STH", color: colors.term.short, unit: Unit.usd }),
line({
series: pattern.realizedCap.all,
name: "Total",
unit: Unit.usd,
}),
line({
series: pattern.realizedCap.sth,
name: "STH",
color: colors.term.short,
unit: Unit.usd,
}),
],
},
{
name: "NUPL",
title: `${name}: NUPL`,
bottom: [line({ series: pattern.nupl.ratio, name, color, unit: Unit.ratio })],
bottom: [
line({ series: pattern.nupl.ratio, name, color, unit: Unit.ratio }),
],
},
],
};
@@ -671,7 +719,12 @@ function groupedBucketCharts(list, titlePrefix) {
title: `${titlePrefix}: Supply Change`,
bottom: ROLLING_WINDOWS.flatMap((w) =>
list.map(({ name, color, pattern }) =>
baseline({ series: pattern.supply.all.delta.absolute[w.key], name: `${name} ${w.name}`, color, unit: Unit.sats }),
baseline({
series: pattern.supply.all.delta.absolute[w.key],
name: `${name} ${w.name}`,
color,
unit: Unit.sats,
}),
),
),
},
@@ -679,7 +732,12 @@ function groupedBucketCharts(list, titlePrefix) {
name: w.name,
title: `${titlePrefix}: Supply Change (${w.title})`,
bottom: list.map(({ name, color, pattern }) =>
baseline({ series: pattern.supply.all.delta.absolute[w.key], name, color, unit: Unit.sats }),
baseline({
series: pattern.supply.all.delta.absolute[w.key],
name,
color,
unit: Unit.sats,
}),
),
})),
],
@@ -692,7 +750,11 @@ function groupedBucketCharts(list, titlePrefix) {
title: `${titlePrefix}: Supply Rate`,
bottom: ROLLING_WINDOWS.flatMap((w) =>
list.flatMap(({ name, color, pattern }) =>
percentRatio({ pattern: pattern.supply.all.delta.rate[w.key], name: `${name} ${w.name}`, color }),
percentRatio({
pattern: pattern.supply.all.delta.rate[w.key],
name: `${name} ${w.name}`,
color,
}),
),
),
},
@@ -700,7 +762,11 @@ function groupedBucketCharts(list, titlePrefix) {
name: w.name,
title: `${titlePrefix}: Supply Rate (${w.title})`,
bottom: list.flatMap(({ name, color, pattern }) =>
percentRatio({ pattern: pattern.supply.all.delta.rate[w.key], name, color }),
percentRatio({
pattern: pattern.supply.all.delta.rate[w.key],
name,
color,
}),
),
})),
],
@@ -716,14 +782,24 @@ function groupedBucketCharts(list, titlePrefix) {
name: "All",
title: `${titlePrefix}: Realized Cap`,
bottom: list.map(({ name, color, pattern }) =>
line({ series: pattern.realizedCap.all, name, color, unit: Unit.usd }),
line({
series: pattern.realizedCap.all,
name,
color,
unit: Unit.usd,
}),
),
},
{
name: "STH",
title: `${titlePrefix}: STH Realized Cap`,
bottom: list.map(({ name, color, pattern }) =>
line({ series: pattern.realizedCap.sth, name, color, unit: Unit.usd }),
line({
series: pattern.realizedCap.sth,
name,
color,
unit: Unit.usd,
}),
),
},
],
@@ -749,7 +825,10 @@ export function createUtxoProfitabilitySection({ range, profit, loss }) {
{
name: "Range",
tree: [
{ name: "Compare", tree: groupedBucketCharts(range, "Profitability Range") },
{
name: "Compare",
tree: groupedBucketCharts(range, "Profitability Range"),
},
...range.map(singleBucketFolder),
],
},

View File

@@ -11,7 +11,7 @@
*/
import { Unit } from "../../utils/units.js";
import { ROLLING_WINDOWS, line, dotted, baseline, dots, dotsBaseline, percentRatio, percentRatioBaseline } from "../series.js";
import { ROLLING_WINDOWS, line, dotted, baseline, percentRatio, percentRatioBaseline } from "../series.js";
import { colors } from "../../utils/colors.js";
import { priceLine } from "../constants.js";
import {
@@ -236,7 +236,7 @@ function nuplSeries(nupl) {
// ============================================================================
/**
* Flat metric folder: Compare + windows + Cumulative + Per Block + optional % of Realized Cap
* Flat metric folder: Compare + windows + Cumulative + optional % of Realized Cap
* @param {Object} args
* @param {{ sum: Record<string, { usd: AnySeriesPattern }>, cumulative: { usd: AnySeriesPattern }, base: { usd: AnySeriesPattern } }} args.pattern
* @param {string} args.metricTitle
@@ -264,11 +264,6 @@ function realizedMetricFolder({ pattern, metricTitle, color, title, toRcap }) {
title: title(`Realized ${metricTitle} (Total)`),
bottom: [line({ series: pattern.cumulative.usd, name: metricTitle, color, unit: Unit.usd })],
},
{
name: "Per Block",
title: title(`Realized ${metricTitle} per Block`),
bottom: [dots({ series: pattern.base.usd, name: metricTitle, color, unit: Unit.usd })],
},
...(toRcap ? [{
name: "% of Realized Cap",
title: title(`Realized ${metricTitle} (% of Realized Cap)`),
@@ -278,7 +273,7 @@ function realizedMetricFolder({ pattern, metricTitle, color, title, toRcap }) {
}
/**
* Net P&L folder: Compare + windows + Cumulative + Per Block + optional % of Rcap + Change/
* Net P&L folder: Compare + windows + Cumulative + optional % of Rcap + Change/
* @param {Object} args
* @param {NetPnlFullPattern | NetPnlBasicPattern} args.netPnl
* @param {(name: string) => string} args.title
@@ -307,11 +302,6 @@ function realizedNetFolder({ netPnl, title, toRcap, extraChange = [] }) {
title: title("Net Realized P&L (Total)"),
bottom: [baseline({ series: netPnl.cumulative.usd, name: "Net", unit: Unit.usd })],
},
{
name: "Per Block",
title: title("Net Realized P&L per Block"),
bottom: [dotsBaseline({ series: netPnl.base.usd, name: "Net", unit: Unit.usd })],
},
...(toRcap ? [{
name: "% of Realized Cap",
title: title("Net Realized P&L (% of Realized Cap)"),
@@ -464,11 +454,6 @@ function realizedSubfolderFull(r, title) {
title: title("Peak Regret (Total)"),
bottom: [line({ series: r.peakRegret.cumulative.usd, name: "Peak Regret", unit: Unit.usd })],
},
{
name: "Per Block",
title: title("Peak Regret per Block"),
bottom: [dots({ series: r.peakRegret.base.usd, name: "Peak Regret", unit: Unit.usd })],
},
{
name: "% of Realized Cap",
title: title("Peak Regret (% of Realized Cap)"),