global: snap

This commit is contained in:
nym21
2026-04-17 21:23:11 +02:00
parent 008143ff00
commit 2a93f51e81
47 changed files with 2075 additions and 389 deletions
@@ -47,6 +47,7 @@ export function buildCohortData() {
base: addrs.funded.all,
delta: addrs.delta.all,
},
avgAmount: addrs.avgAmount.all,
};
const shortNames = TERM_NAMES.short;
@@ -174,6 +175,9 @@ export function buildCohortData() {
base: addrs.funded[key],
delta: addrs.delta[key],
},
avgAmount: addrs.avgAmount[key],
exposed: addrs.exposed,
reused: addrs.reused,
};
});
@@ -28,7 +28,7 @@ import {
groupedWindowsCumulativeWithAll,
} from "../shared.js";
import { colors } from "../../utils/colors.js";
import { priceLines } from "../constants.js";
import { priceLine } from "../constants.js";
/**
* Simple supply series (total + half only, no profit/loss)
@@ -165,41 +165,44 @@ function groupedDeltaItems(list, all, getDelta, unit, title, name) {
// ============================================================================
/**
* Profitability chart (in profit + in loss supply)
* Amount chart: total + halved + in profit + in loss in sats/btc/usd.
* @param {{ total: AnyValuePattern, half: AnyValuePattern, inProfit: AnyValuePattern, inLoss: AnyValuePattern }} supply
* @param {(name: string) => string} title
* @returns {PartialChartOption}
*/
function profitabilityChart(supply, title) {
function profitabilityAmountChart(supply, title) {
return {
name: "Profitability",
name: "Amount",
title: title("Supply Profitability"),
bottom: [
...satsBtcUsd({
pattern: supply.total,
name: "Total",
color: colors.default,
}),
...satsBtcUsd({
pattern: supply.inProfit,
name: "In Profit",
color: colors.profit,
}),
...satsBtcUsd({
pattern: supply.inLoss,
name: "In Loss",
color: colors.loss,
}),
...satsBtcUsd({
pattern: supply.half,
name: "Halved",
color: colors.gray,
style: 4,
}),
...satsBtcUsd({ pattern: supply.total, name: "Total", color: colors.default }),
...satsBtcUsd({ pattern: supply.inProfit, name: "In Profit", color: colors.profit }),
...satsBtcUsd({ pattern: supply.inLoss, name: "In Loss", color: colors.loss }),
...satsBtcUsd({ pattern: supply.half, name: "Halved", color: colors.gray, style: 4 }),
],
};
}
/**
* Share chart: in profit / in loss as % of own supply.
* @param {{ inProfit: { toOwn: { percent: AnySeriesPattern, ratio: AnySeriesPattern } }, inLoss: { toOwn: { percent: AnySeriesPattern, ratio: AnySeriesPattern } } }} supply
* @param {(name: string) => string} title
* @returns {PartialChartOption}
*/
function profitabilityShareChart(supply, title) {
return {
name: "Share",
title: title("Supply Profitability"),
bottom: [
...percentRatio({ pattern: supply.inProfit.toOwn, name: "In Profit", color: colors.profit }),
...percentRatio({ pattern: supply.inLoss.toOwn, name: "In Loss", color: colors.loss }),
priceLine({ number: 100, color: colors.default, style: 0, unit: Unit.percentage }),
priceLine({ number: 50, unit: Unit.percentage }),
],
};
}
/**
* @param {{ toCirculating: PercentRatioPattern, inProfit: { toCirculating: PercentRatioPattern }, inLoss: { toCirculating: PercentRatioPattern } }} supply
* @param {(name: string) => string} title
@@ -207,8 +210,8 @@ function profitabilityChart(supply, title) {
*/
function circulatingChart(supply, title) {
return {
name: "% of Circulating",
title: title("Supply (% of Circulating)"),
name: "Dominance",
title: title("Supply Dominance"),
bottom: [
...percentRatio({ pattern: supply.toCirculating, name: "Total", color: colors.default }),
...percentRatio({ pattern: supply.inProfit.toCirculating, name: "In Profit", color: colors.profit }),
@@ -217,23 +220,6 @@ function circulatingChart(supply, title) {
};
}
/**
* @param {{ inProfit: { toOwn: { percent: AnySeriesPattern, ratio: AnySeriesPattern } }, inLoss: { toOwn: { percent: AnySeriesPattern, ratio: AnySeriesPattern } } }} supply
* @param {(name: string) => string} title
* @returns {PartialChartOption}
*/
function ownSupplyChart(supply, title) {
return {
name: "% of Own Supply",
title: title("Supply (% of Own)"),
bottom: [
...percentRatio({ pattern: supply.inProfit.toOwn, name: "In Profit", color: colors.profit }),
...percentRatio({ pattern: supply.inLoss.toOwn, name: "In Loss", color: colors.loss }),
...priceLines({ numbers: [100, 50, 0], unit: Unit.percentage }),
],
};
}
/**
* @param {OutputsPattern} outputs
* @param {Color} color
@@ -330,8 +316,13 @@ export function createHoldingsSectionAll({ cohort, title }) {
title: title("Supply"),
bottom: simpleSupplySeries(supply),
},
profitabilityChart(supply, title),
ownSupplyChart(supply, title),
{
name: "Profitability",
tree: [
profitabilityAmountChart(supply, title),
profitabilityShareChart(supply, title),
],
},
...singleDeltaItems(supply.delta, Unit.sats, title, "Supply"),
],
},
@@ -355,9 +346,14 @@ export function createHoldingsSectionWithRelative({ cohort, title }) {
title: title("Supply"),
bottom: simpleSupplySeries(supply),
},
profitabilityChart(supply, title),
circulatingChart(supply, title),
ownSupplyChart(supply, title),
{
name: "Profitability",
tree: [
profitabilityAmountChart(supply, title),
profitabilityShareChart(supply, title),
circulatingChart(supply, title),
],
},
...singleDeltaItems(supply.delta, Unit.sats, title, "Supply"),
],
},
@@ -380,8 +376,13 @@ export function createHoldingsSectionWithOwnSupply({ cohort, title }) {
title: title("Supply"),
bottom: simpleSupplySeries(supply),
},
profitabilityChart(supply, title),
circulatingChart(supply, title),
{
name: "Profitability",
tree: [
profitabilityAmountChart(supply, title),
circulatingChart(supply, title),
],
},
...singleDeltaItems(supply.delta, Unit.sats, title, "Supply"),
],
},
@@ -404,7 +405,10 @@ export function createHoldingsSectionWithProfitLoss({ cohort, title }) {
title: title("Supply"),
bottom: simpleSupplySeries(supply),
},
profitabilityChart(supply, title),
{
name: "Profitability",
tree: [profitabilityAmountChart(supply, title)],
},
...singleDeltaItems(supply.delta, Unit.sats, title, "Supply"),
],
},
@@ -427,7 +431,10 @@ export function createHoldingsSectionAddress({ cohort, title }) {
title: title("Supply"),
bottom: simpleSupplySeries(supply),
},
profitabilityChart(supply, title),
{
name: "Profitability",
tree: [profitabilityAmountChart(supply, title)],
},
...singleDeltaItems(supply.delta, Unit.sats, title, "Supply"),
],
},
@@ -502,7 +509,10 @@ export function createGroupedHoldingsSectionAddress({ list, all, title }) {
name: "Supply",
tree: [
groupedSupplyTotal(list, all, title),
...groupedSupplyProfitLoss(list, all, title),
{
name: "Profitability",
tree: groupedSupplyProfitLoss(list, all, title),
},
...groupedDeltaItems(list, all, (c) => c.tree.supply.delta, Unit.sats, title, "Supply"),
],
},
@@ -520,6 +530,25 @@ export function createGroupedHoldingsSectionAddress({ list, all, title }) {
...groupedDeltaItems(list, all, (c) => c.addressCount.delta, Unit.count, title, "Address Count"),
],
},
{
name: "Average Holdings",
tree: [
{
name: "Per UTXO",
title: title("Average Holdings per UTXO"),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, avgAmount }) =>
satsBtcUsd({ pattern: avgAmount.utxo, name, color }),
),
},
{
name: "Per Address",
title: title("Average Holdings per Funded Address"),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, avgAmount }) =>
satsBtcUsd({ pattern: avgAmount.addr, name, color }),
),
},
],
},
];
}
@@ -14,6 +14,9 @@ import {
formatCohortTitle,
satsBtcUsd,
satsBtcUsdFullTree,
avgHoldingsSubtree,
exposedSubtree,
reusedSubtree,
} from "../shared.js";
import {
ROLLING_WINDOWS,
@@ -103,6 +106,7 @@ export function createCohortFolderAll(cohort) {
createCostBasisSectionWithPercentiles({ cohort, title }),
createProfitabilitySectionAll({ cohort, title }),
createActivitySectionWithAdjusted({ cohort, title }),
avgHoldingsSubtree(cohort.avgAmount, title),
],
};
}
@@ -259,6 +263,9 @@ export function createCohortFolderAddress(cohort) {
createPricesSectionBasic({ cohort, title }),
createProfitabilitySectionWithProfitLoss({ cohort, title }),
createActivitySectionMinimal({ cohort, title }),
avgHoldingsSubtree(cohort.avgAmount, title),
reusedSubtree(cohort.reused, cohort.key, title),
exposedSubtree(cohort.exposed, cohort.key, title),
],
};
}
@@ -6,6 +6,7 @@ import { Unit } from "../../utils/units.js";
import { colors } from "../../utils/colors.js";
import { ROLLING_WINDOWS, line, baseline, mapWindows, sumsTreeBaseline, rollingPercentRatioTree, percentRatio, percentRatioBaseline } from "../series.js";
import { ratioBottomSeries, mapCohortsWithAll, flatMapCohortsWithAll } from "../shared.js";
import { priceLine } from "../constants.js";
// ============================================================================
// Shared building blocks
@@ -80,11 +81,26 @@ export function createValuationSectionFull({ cohort, title }) {
{ name: "Total", title: title("Realized Cap"), bottom: [line({ series: tree.realized.cap.usd, name: "Realized Cap", color, unit: Unit.usd })] },
{
name: "Profitability",
title: title("Invested Capital"),
bottom: [
line({ series: tree.realized.cap.usd, name: "Total", color: colors.default, unit: Unit.usd }),
line({ series: tree.unrealized.investedCapital.inProfit.usd, name: "In Profit", color: colors.profit, unit: Unit.usd }),
line({ series: tree.unrealized.investedCapital.inLoss.usd, name: "In Loss", color: colors.loss, unit: Unit.usd }),
tree: [
{
name: "Amount",
title: title("Invested Capital"),
bottom: [
line({ series: tree.realized.cap.usd, name: "Total", color: colors.default, unit: Unit.usd }),
line({ series: tree.unrealized.investedCapital.inProfit.usd, name: "In Profit", color: colors.profit, unit: Unit.usd }),
line({ series: tree.unrealized.investedCapital.inLoss.usd, name: "In Loss", color: colors.loss, unit: Unit.usd }),
],
},
{
name: "Share",
title: title("Invested Capital Profitability"),
bottom: [
...percentRatio({ pattern: tree.investedCapital.inProfit.toOwn, name: "In Profit", color: colors.profit }),
...percentRatio({ pattern: tree.investedCapital.inLoss.toOwn, name: "In Loss", color: colors.loss }),
priceLine({ number: 100, color: colors.default, style: 0, unit: Unit.percentage }),
priceLine({ number: 50, unit: Unit.percentage }),
],
},
],
},
{ name: "MVRV", title: title("MVRV"), bottom: ratioBottomSeries(tree.realized.price) },