global: snapshot part 11

This commit is contained in:
nym21
2026-03-21 12:05:04 +01:00
parent 143aa90b18
commit 573336ed80
5 changed files with 974 additions and 611 deletions
+158 -146
View File
@@ -23,6 +23,8 @@ import {
satsBtcUsdFullTree,
mapCohortsWithAll,
flatMapCohortsWithAll,
groupedWindowsCumulative,
groupedWindowsCumulativeSatsBtcUsd,
} from "../shared.js";
import { colors } from "../../utils/colors.js";
@@ -31,30 +33,40 @@ import { colors } from "../../utils/colors.js";
// ============================================================================
/**
* Volume folder with optional profitability (in profit + in loss per window)
* @param {{ transferVolume: TransferVolumePattern }} activity
* @param {Color} color
* @param {(name: string) => string} title
* @param {boolean} [withProfitability]
* @returns {PartialOptionsGroup}
*/
function volumeFolder(activity, color, title, withProfitability) {
function volumeFolderWithProfitability(activity, color, title) {
const tv = activity.transferVolume;
return {
name: "Volume",
tree: [
...satsBtcUsdFullTree({ pattern: tv, title: title("Sent Volume"), color }),
...(withProfitability ? [{
...satsBtcUsdFullTree({
pattern: tv,
title: title("Sent Volume"),
color,
}),
{
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", color: colors.profit }),
...satsBtcUsd({ pattern: tv.inLoss.sum[w.key], name: "In Loss", color: colors.loss }),
...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,
}),
],
})),
}] : []),
},
],
};
}
@@ -68,7 +80,7 @@ function volumeFolder(activity, color, title, withProfitability) {
*/
function fullVolumeTree(activity, color, title) {
return [
volumeFolder(activity, color, title, true),
volumeFolderWithProfitability(activity, color, title),
{
name: "Coindays Destroyed",
tree: chartsFromCount({
@@ -154,7 +166,11 @@ function singleSellSideRiskTree(sellSideRisk, title) {
...ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`Sell Side Risk (${w.title})`),
bottom: percentRatio({ pattern: sellSideRisk[w.key], name: "Risk", color: w.color }),
bottom: percentRatio({
pattern: sellSideRisk[w.key],
name: "Risk",
color: w.color,
}),
})),
];
}
@@ -237,7 +253,7 @@ export function createActivitySectionWithActivity({ cohort, title }) {
return {
name: "Activity",
tree: [
volumeFolder(tree.activity, color, title, true),
volumeFolderWithProfitability(tree.activity, color, title),
{
name: "Coindays Destroyed",
tree: chartsFromCount({
@@ -290,7 +306,11 @@ export function createGroupedActivitySectionMinimal({ list, all, title }) {
name: w.name,
title: title(`Volume (${w.title})`),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.activity.transferVolume.sum[w.key], name, color }),
satsBtcUsd({
pattern: tree.activity.transferVolume.sum[w.key],
name,
color,
}),
),
})),
};
@@ -356,78 +376,49 @@ export function createGroupedActivitySectionWithAdjusted({ list, all, title }) {
{
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 }),
),
})),
{
name: "Cumulative",
title: title("Cumulative Sent Volume"),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.activity.transferVolume.cumulative, name, color }),
),
},
...groupedWindowsCumulativeSatsBtcUsd({
list,
all,
title,
metricTitle: "Sent Volume",
getMetric: (c) => c.tree.activity.transferVolume,
}),
{
name: "In Profit",
tree: [
...ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`Sent In Profit (${w.title})`),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.activity.transferVolume.inProfit.sum[w.key], name, color }),
),
})),
{
name: "Cumulative",
title: title("Cumulative Sent In Profit"),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.activity.transferVolume.inProfit.cumulative, name, color }),
),
},
],
tree: groupedWindowsCumulativeSatsBtcUsd({
list,
all,
title,
metricTitle: "Sent In Profit",
getMetric: (c) => c.tree.activity.transferVolume.inProfit,
}),
},
{
name: "In Loss",
tree: [
...ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`Sent In Loss (${w.title})`),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.activity.transferVolume.inLoss.sum[w.key], name, color }),
),
})),
{
name: "Cumulative",
title: title("Cumulative Sent In Loss"),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.activity.transferVolume.inLoss.cumulative, name, color }),
),
},
],
tree: groupedWindowsCumulativeSatsBtcUsd({
list,
all,
title,
metricTitle: "Sent In Loss",
getMetric: (c) => c.tree.activity.transferVolume.inLoss,
}),
},
],
},
{
name: "Coindays Destroyed",
tree: [
...ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`Coindays Destroyed (${w.title})`),
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
line({ series: tree.activity.coindaysDestroyed.sum[w.key], name, color, unit: Unit.coindays }),
),
})),
{
name: "Cumulative",
title: title("Cumulative Coindays Destroyed"),
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
line({ series: tree.activity.coindaysDestroyed.cumulative, name, color, unit: Unit.coindays }),
),
},
],
tree: groupedWindowsCumulative({
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,
}),
},
{
name: "Dormancy",
@@ -435,17 +426,33 @@ export function createGroupedActivitySectionWithAdjusted({ 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,
}),
),
})),
},
{
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 ",
),
},
],
},
@@ -455,7 +462,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,
}),
),
})),
},
@@ -475,78 +487,49 @@ export function createGroupedActivitySection({ list, all, title }) {
{
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 }),
),
})),
{
name: "Cumulative",
title: title("Cumulative Sent Volume"),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.activity.transferVolume.cumulative, name, color }),
),
},
...groupedWindowsCumulativeSatsBtcUsd({
list,
all,
title,
metricTitle: "Sent Volume",
getMetric: (c) => c.tree.activity.transferVolume,
}),
{
name: "In Profit",
tree: [
...ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`Sent In Profit (${w.title})`),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.activity.transferVolume.inProfit.sum[w.key], name, color }),
),
})),
{
name: "Cumulative",
title: title("Cumulative Sent In Profit"),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.activity.transferVolume.inProfit.cumulative, name, color }),
),
},
],
tree: groupedWindowsCumulativeSatsBtcUsd({
list,
all,
title,
metricTitle: "Sent In Profit",
getMetric: (c) => c.tree.activity.transferVolume.inProfit,
}),
},
{
name: "In Loss",
tree: [
...ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`Sent In Loss (${w.title})`),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.activity.transferVolume.inLoss.sum[w.key], name, color }),
),
})),
{
name: "Cumulative",
title: title("Cumulative Sent In Loss"),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.activity.transferVolume.inLoss.cumulative, name, color }),
),
},
],
tree: groupedWindowsCumulativeSatsBtcUsd({
list,
all,
title,
metricTitle: "Sent In Loss",
getMetric: (c) => c.tree.activity.transferVolume.inLoss,
}),
},
],
},
{
name: "Coindays Destroyed",
tree: [
...ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`Coindays Destroyed (${w.title})`),
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
line({ series: tree.activity.coindaysDestroyed.sum[w.key], name, color, unit: Unit.coindays }),
),
})),
{
name: "Cumulative",
title: title("Cumulative Coindays Destroyed"),
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
line({ series: tree.activity.coindaysDestroyed.cumulative, name, color, unit: Unit.coindays }),
),
},
],
tree: groupedWindowsCumulative({
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,
}),
},
{
name: "Dormancy",
@@ -554,13 +537,23 @@ export function createGroupedActivitySection({ 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,
}),
),
})),
},
{
name: "SOPR",
tree: groupedSoprCharts(list, all, (c) => c.tree.realized.sopr.ratio, title),
tree: groupedSoprCharts(
list,
all,
(c) => c.tree.realized.sopr.ratio,
title,
),
},
{
name: "Sell Side Risk",
@@ -568,7 +561,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,
}),
),
})),
},
@@ -591,7 +589,11 @@ export function createGroupedActivitySectionWithActivity({ list, all, title }) {
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 }),
satsBtcUsd({
pattern: tree.activity.transferVolume.sum[w.key],
name,
color,
}),
),
})),
},
@@ -615,14 +617,24 @@ export function createGroupedActivitySectionWithActivity({ list, all, title }) {
name: w.name,
title: title(`Coindays Destroyed (${w.title})`),
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
line({ series: tree.activity.coindaysDestroyed.sum[w.key], name, color, unit: Unit.coindays }),
line({
series: tree.activity.coindaysDestroyed.sum[w.key],
name,
color,
unit: Unit.coindays,
}),
),
})),
{
name: "Cumulative",
title: title("Cumulative Coindays Destroyed"),
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
line({ series: tree.activity.coindaysDestroyed.cumulative, name, color, unit: Unit.coindays }),
line({
series: tree.activity.coindaysDestroyed.cumulative,
name,
color,
unit: Unit.coindays,
}),
),
},
],
@@ -136,53 +136,36 @@ function groupedWeightFolder({ list, all, getAvgPrice, getInProfit, getInLoss, g
return [
{
name: "Average",
tree: [
{
name: "All",
title: title(`${avgTitle} Comparison`),
top: mapCohortsWithAll(list, all, (c) =>
price({ series: getAvgPrice(c), name: c.name, color: c.color }),
),
},
{
name: "In Profit",
title: title(`Cost Basis In Profit (${weightLabel})`),
top: mapCohortsWithAll(list, all, (c) =>
price({ series: getInProfit(c), name: c.name, color: c.color }),
),
},
{
name: "In Loss",
title: title(`Cost Basis In Loss (${weightLabel})`),
top: mapCohortsWithAll(list, all, (c) =>
price({ series: getInLoss(c), name: c.name, color: c.color }),
),
},
],
title: title(`${avgTitle} Comparison`),
top: mapCohortsWithAll(list, all, (c) =>
price({ series: getAvgPrice(c), name: c.name, color: c.color }),
),
},
{
name: "Distribution",
tree: [
{
name: "Average",
title: title(`${avgTitle} Comparison`),
top: mapCohortsWithAll(list, all, (c) =>
price({ series: getAvgPrice(c), name: c.name, color: c.color }),
),
},
...(/** @type {const} */ ([
["pct50", "Median"],
["pct75", "Q3"],
["pct25", "Q1"],
])).map(([pct, label]) => ({
name: label,
title: title(`Cost Basis ${label} (${weightLabel})`),
top: mapCohortsWithAll(list, all, (c) =>
price({ series: getPercentiles(c)[pct], name: c.name, color: c.color }),
),
})),
],
name: "In Profit",
title: title(`Cost Basis In Profit (${weightLabel})`),
top: mapCohortsWithAll(list, all, (c) =>
price({ series: getInProfit(c), name: c.name, color: c.color }),
),
},
{
name: "In Loss",
title: title(`Cost Basis In Loss (${weightLabel})`),
top: mapCohortsWithAll(list, all, (c) =>
price({ series: getInLoss(c), name: c.name, color: c.color }),
),
},
...(/** @type {const} */ ([
["pct50", "Median"],
["pct75", "Q3"],
["pct25", "Q1"],
])).map(([pct, label]) => ({
name: label,
title: title(`Cost Basis ${label} (${weightLabel})`),
top: mapCohortsWithAll(list, all, (c) =>
price({ series: getPercentiles(c)[pct], name: c.name, color: c.color }),
),
})),
];
}
@@ -646,7 +646,7 @@ function singleBucketFolder({ name, color, pattern }) {
title: `${name}: Supply Change`,
unit: Unit.sats,
}),
name: "Absolute",
name: "Change",
},
{
...rollingPercentRatioTree({
@@ -715,7 +715,7 @@ function groupedBucketCharts(list, titlePrefix) {
name: "Change",
tree: [
{
name: "Absolute",
name: "Change",
tree: [
{
name: "Compare",
File diff suppressed because it is too large Load Diff
+91 -1
View File
@@ -1,7 +1,7 @@
/** Shared helpers for options */
import { Unit } from "../utils/units.js";
import { line, baseline, price, sumsAndAveragesCumulativeWith } from "./series.js";
import { ROLLING_WINDOWS, line, baseline, price, sumsAndAveragesCumulativeWith } from "./series.js";
import { priceLine, priceLines } from "./constants.js";
import { colors } from "../utils/colors.js";
@@ -749,3 +749,93 @@ export function createPriceRatioCharts({
}),
];
}
// ============================================================================
// Grouped Rolling Windows + Cumulative
// ============================================================================
/**
* Generic: rolling window charts + cumulative for grouped cohorts
* @template {{ name: string, color: Color }} T
* @template {{ name: string, color: Color }} A
* @param {Object} args
* @param {readonly T[]} args.list
* @param {A} args.all
* @param {(name: string) => string} args.title
* @param {string} args.metricTitle
* @param {(c: T | A, windowKey: "_24h" | "_1w" | "_1m" | "_1y") => AnySeriesPattern} args.getWindowSeries
* @param {(c: T | A) => AnySeriesPattern} args.getCumulativeSeries
* @param {(args: { series: AnySeriesPattern, name: string, color: Color, unit: Unit }) => AnyFetchedSeriesBlueprint} args.seriesFn
* @param {Unit} args.unit
* @returns {PartialOptionsTree}
*/
export function groupedWindowsCumulative({ list, all, title, metricTitle, getWindowSeries, getCumulativeSeries, seriesFn, unit }) {
return [
...ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`${metricTitle} (${w.title})`),
bottom: mapCohortsWithAll(list, all, (c) =>
seriesFn({ series: getWindowSeries(c, w.key), name: c.name, color: c.color, unit }),
),
})),
{
name: "Cumulative",
title: title(`Cumulative ${metricTitle}`),
bottom: mapCohortsWithAll(list, all, (c) =>
seriesFn({ series: getCumulativeSeries(c), name: c.name, color: c.color, unit }),
),
},
];
}
/**
* USD variant: windows access .sum[key].usd, cumulative accesses .cumulative.usd
* @template {{ name: string, color: Color }} T
* @template {{ name: string, color: Color }} A
* @param {Object} args
* @param {readonly T[]} args.list
* @param {A} args.all
* @param {(name: string) => string} args.title
* @param {string} args.metricTitle
* @param {(c: T | A) => { sum: Record<string, { usd: AnySeriesPattern }>, cumulative: { usd: AnySeriesPattern } }} args.getMetric
* @param {(args: { series: AnySeriesPattern, name: string, color: Color, unit: Unit }) => AnyFetchedSeriesBlueprint} [args.seriesFn]
* @returns {PartialOptionsTree}
*/
export function groupedWindowsCumulativeUsd({ list, all, title, metricTitle, getMetric, seriesFn = line }) {
return groupedWindowsCumulative({
list, all, title, metricTitle, seriesFn, unit: Unit.usd,
getWindowSeries: (c, key) => getMetric(c).sum[key].usd,
getCumulativeSeries: (c) => getMetric(c).cumulative.usd,
});
}
/**
* Multi-unit variant: windows access .sum[key] as satsBtcUsd pattern, cumulative same
* @template {{ name: string, color: Color }} T
* @template {{ name: string, color: Color }} A
* @param {Object} args
* @param {readonly T[]} args.list
* @param {A} args.all
* @param {(name: string) => string} args.title
* @param {string} args.metricTitle
* @param {(c: T | A) => { sum: Record<string, AnyValuePattern>, cumulative: AnyValuePattern }} args.getMetric
* @returns {PartialOptionsTree}
*/
export function groupedWindowsCumulativeSatsBtcUsd({ list, all, title, metricTitle, getMetric }) {
return [
...ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`${metricTitle} (${w.title})`),
bottom: flatMapCohortsWithAll(list, all, (c) =>
satsBtcUsd({ pattern: getMetric(c).sum[w.key], name: c.name, color: c.color }),
),
})),
{
name: "Cumulative",
title: title(`Cumulative ${metricTitle}`),
bottom: flatMapCohortsWithAll(list, all, (c) =>
satsBtcUsd({ pattern: getMetric(c).cumulative, name: c.name, color: c.color }),
),
},
];
}