website: snapshot

This commit is contained in:
nym21
2026-02-04 17:26:35 +01:00
parent 0d5d7da70f
commit 0437ce1bb4
33 changed files with 5752 additions and 995 deletions

View File

@@ -16,14 +16,89 @@
import { Unit } from "../../utils/units.js";
import { line, baseline } from "../series.js";
import { satsBtcUsd, satsBtcUsdBaseline } from "../shared.js";
import { colors } from "../../utils/colors.js";
import { priceLines } from "../constants.js";
/**
* @param {UtxoCohortObject | CohortWithoutRelative} cohort
* @returns {AnyFetchedSeriesBlueprint[]}
*/
function createSingleSupplySeries(cohort) {
const { color, tree } = cohort;
return [...satsBtcUsd({ pattern: tree.supply.total, name: "Supply", color })];
const { tree } = cohort;
return [
...satsBtcUsd({
pattern: tree.supply.total,
name: "Total",
color: colors.default,
}),
...satsBtcUsd({
pattern: tree.unrealized.supplyInProfit,
name: "In Profit",
color: colors.profit,
}),
...satsBtcUsd({
pattern: tree.unrealized.supplyInLoss,
name: "In Loss",
color: colors.loss,
}),
// Halved supply (sparse line)
...satsBtcUsd({
pattern: tree.supply.halved,
name: "Halved",
color: colors.gray,
style: 4,
}),
];
}
/**
* Supply series for CohortAll (has % of Own Supply but not % of Circulating)
* @param {CohortAll} cohort
* @returns {AnyFetchedSeriesBlueprint[]}
*/
function createSingleSupplySeriesAll(cohort) {
const { tree } = cohort;
return [
...satsBtcUsd({
pattern: tree.supply.total,
name: "Total",
color: colors.default,
}),
...satsBtcUsd({
pattern: tree.unrealized.supplyInProfit,
name: "In Profit",
color: colors.profit,
}),
...satsBtcUsd({
pattern: tree.unrealized.supplyInLoss,
name: "In Loss",
color: colors.loss,
}),
// Halved supply (sparse line)
...satsBtcUsd({
pattern: tree.supply.halved,
name: "Halved",
color: colors.gray,
style: 4,
}),
// % of Own Supply
line({
metric: tree.relative.supplyInProfitRelToOwnSupply,
name: "In Profit",
color: colors.profit,
unit: Unit.pctOwn,
}),
line({
metric: tree.relative.supplyInLossRelToOwnSupply,
name: "In Loss",
color: colors.loss,
unit: Unit.pctOwn,
}),
...priceLines({
numbers: [100, 50, 0],
unit: Unit.pctOwn,
}),
];
}
/**
@@ -31,7 +106,10 @@ function createSingleSupplySeries(cohort) {
* @returns {AnyFetchedSeriesBlueprint[]}
*/
function createSingle30dChangeSeries(cohort) {
return satsBtcUsdBaseline({ pattern: cohort.tree.supply._30dChange, name: "30d Change" });
return satsBtcUsdBaseline({
pattern: cohort.tree.supply._30dChange,
name: "30d Change",
});
}
/**
@@ -79,18 +157,121 @@ function createSingleAddrCount30dChangeSeries(cohort) {
}
/**
* Create supply series with % of Circulating (for cohorts with relative data)
* @param {CohortFull | CohortWithAdjusted | CohortBasicWithMarketCap | CohortMinAge} cohort
* @returns {AnyFetchedSeriesBlueprint[]}
*/
function createSingleRelativeSeries(cohort) {
const { color, tree } = cohort;
function createSingleSupplySeriesWithRelative(cohort) {
const { tree } = cohort;
return [
...satsBtcUsd({
pattern: tree.supply.total,
name: "Total",
color: colors.default,
}),
...satsBtcUsd({
pattern: tree.unrealized.supplyInProfit,
name: "In Profit",
color: colors.profit,
}),
...satsBtcUsd({
pattern: tree.unrealized.supplyInLoss,
name: "In Loss",
color: colors.loss,
}),
// Halved supply (sparse line)
...satsBtcUsd({
pattern: tree.supply.halved,
name: "Halved",
color: colors.gray,
style: 4,
}),
// % of Circulating Supply
line({
metric: tree.relative.supplyRelToCirculatingSupply,
name: "% of Circulating",
color,
name: "Total",
color: colors.default,
unit: Unit.pctSupply,
}),
line({
metric: tree.relative.supplyInProfitRelToCirculatingSupply,
name: "In Profit",
color: colors.profit,
unit: Unit.pctSupply,
}),
line({
metric: tree.relative.supplyInLossRelToCirculatingSupply,
name: "In Loss",
color: colors.loss,
unit: Unit.pctSupply,
}),
// % of Own Supply
line({
metric: tree.relative.supplyInProfitRelToOwnSupply,
name: "In Profit",
color: colors.profit,
unit: Unit.pctOwn,
}),
line({
metric: tree.relative.supplyInLossRelToOwnSupply,
name: "In Loss",
color: colors.loss,
unit: Unit.pctOwn,
}),
...priceLines({
numbers: [100, 50, 0],
unit: Unit.pctOwn,
}),
];
}
/**
* Supply series with % of Own Supply only (for cohorts without % of Circulating)
* @param {CohortAgeRange | CohortBasicWithoutMarketCap} cohort
* @returns {AnyFetchedSeriesBlueprint[]}
*/
function createSingleSupplySeriesWithOwnSupply(cohort) {
const { tree } = cohort;
return [
...satsBtcUsd({
pattern: tree.unrealized.supplyInProfit,
name: "In Profit",
color: colors.profit,
}),
...satsBtcUsd({
pattern: tree.unrealized.supplyInLoss,
name: "In Loss",
color: colors.loss,
}),
...satsBtcUsd({
pattern: tree.supply.total,
name: "Total",
color: colors.default,
}),
// Halved supply (sparse line)
...satsBtcUsd({
pattern: tree.supply.halved,
name: "Halved",
color: colors.gray,
style: 4,
}),
// % of Own Supply
line({
metric: tree.relative.supplyInProfitRelToOwnSupply,
name: "In Profit",
color: colors.profit,
unit: Unit.pctOwn,
}),
line({
metric: tree.relative.supplyInLossRelToOwnSupply,
name: "In Loss",
color: colors.loss,
unit: Unit.pctOwn,
}),
...priceLines({
numbers: [100, 50, 0],
unit: Unit.pctOwn,
}),
];
}
@@ -132,17 +313,18 @@ export function createHoldingsSection({ cohort, title }) {
}
/**
* @param {{ cohort: CohortFull | CohortWithAdjusted | CohortBasicWithMarketCap | CohortMinAge, title: (metric: string) => string }} args
* Holdings section with % of Own Supply only (for cohorts without % of Circulating)
* @param {{ cohort: CohortAgeRange | CohortBasicWithoutMarketCap, title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createHoldingsSectionWithRelative({ cohort, title }) {
export function createHoldingsSectionWithOwnSupply({ cohort, title }) {
return {
name: "Holdings",
tree: [
{
name: "Supply",
title: title("Supply"),
bottom: createSingleSupplySeries(cohort),
bottom: createSingleSupplySeriesWithOwnSupply(cohort),
},
{
name: "UTXO Count",
@@ -164,10 +346,42 @@ export function createHoldingsSectionWithRelative({ cohort, title }) {
},
],
},
],
};
}
/**
* @param {{ cohort: CohortFull | CohortWithAdjusted | CohortBasicWithMarketCap | CohortMinAge, title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createHoldingsSectionWithRelative({ cohort, title }) {
return {
name: "Holdings",
tree: [
{
name: "Relative",
title: title("Relative to Circulating Supply"),
bottom: createSingleRelativeSeries(cohort),
name: "Supply",
title: title("Supply"),
bottom: createSingleSupplySeriesWithRelative(cohort),
},
{
name: "UTXO Count",
title: title("UTXO Count"),
bottom: createSingleUtxoCountSeries(cohort),
},
{
name: "30d Changes",
tree: [
{
name: "Supply",
title: title("Supply 30d Change"),
bottom: createSingle30dChangeSeries(cohort),
},
{
name: "UTXO Count",
title: title("UTXO Count 30d Change"),
bottom: createSingleUtxoCount30dChangeSeries(cohort),
},
],
},
],
};
@@ -184,7 +398,7 @@ export function createHoldingsSectionAll({ cohort, title }) {
{
name: "Supply",
title: title("Supply"),
bottom: createSingleSupplySeries(cohort),
bottom: createSingleSupplySeriesAll(cohort),
},
{
name: "UTXO Count",
@@ -238,7 +452,62 @@ export function createHoldingsSectionAddress({ cohort, title }) {
{
name: "Supply",
title: title("Supply"),
bottom: createSingleSupplySeries(cohort),
bottom: createSingleSupplySeriesWithOwnSupply(cohort),
},
{
name: "UTXO Count",
title: title("UTXO Count"),
bottom: createSingleUtxoCountSeries(cohort),
},
{
name: "Address Count",
title: title("Address Count"),
bottom: [
line({
metric: cohort.addrCount.count,
name: "Address Count",
color: cohort.color,
unit: Unit.count,
}),
],
},
{
name: "30d Changes",
tree: [
{
name: "Supply",
title: title("Supply 30d Change"),
bottom: createSingle30dChangeSeries(cohort),
},
{
name: "UTXO Count",
title: title("UTXO Count 30d Change"),
bottom: createSingleUtxoCount30dChangeSeries(cohort),
},
{
name: "Address Count",
title: title("Address Count 30d Change"),
bottom: createSingleAddrCount30dChangeSeries(cohort),
},
],
},
],
};
}
/**
* Holdings section for address amount cohorts (has relative supply + address count)
* @param {{ cohort: AddressCohortObject, title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createHoldingsSectionAddressAmount({ cohort, title }) {
return {
name: "Holdings",
tree: [
{
name: "Supply",
title: title("Supply"),
bottom: createSingleSupplySeriesWithRelative(cohort),
},
{
name: "UTXO Count",
@@ -291,10 +560,67 @@ export function createGroupedHoldingsSectionAddress({ list, title }) {
tree: [
{
name: "Supply",
title: title("Supply"),
bottom: list.flatMap(({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.supply.total, name, color }),
),
tree: [
{
name: "Total",
title: title("Supply"),
bottom: list.flatMap(({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.supply.total, name, color }),
),
},
{
name: "In Profit",
title: title("Supply In Profit"),
bottom: [
...list.flatMap(({ name, color, tree }) =>
satsBtcUsd({
pattern: tree.unrealized.supplyInProfit,
name,
color,
}),
),
// % of Own Supply
...list.map(({ name, color, tree }) =>
line({
metric: tree.relative.supplyInProfitRelToOwnSupply,
name,
color,
unit: Unit.pctOwn,
}),
),
...priceLines({
numbers: [100, 50, 0],
unit: Unit.pctOwn,
}),
],
},
{
name: "In Loss",
title: title("Supply In Loss"),
bottom: [
...list.flatMap(({ name, color, tree }) =>
satsBtcUsd({
pattern: tree.unrealized.supplyInLoss,
name,
color,
}),
),
// % of Own Supply
...list.map(({ name, color, tree }) =>
line({
metric: tree.relative.supplyInLossRelToOwnSupply,
name,
color,
unit: Unit.pctOwn,
}),
),
...priceLines({
numbers: [100, 50, 0],
unit: Unit.pctOwn,
}),
],
},
],
},
{
name: "UTXO Count",
@@ -322,7 +648,176 @@ export function createGroupedHoldingsSectionAddress({ list, title }) {
name: "Supply",
title: title("Supply 30d Change"),
bottom: list.flatMap(({ name, color, tree }) =>
satsBtcUsdBaseline({ pattern: tree.supply._30dChange, name, color }),
satsBtcUsdBaseline({
pattern: tree.supply._30dChange,
name,
color,
}),
),
},
{
name: "UTXO Count",
title: title("UTXO Count 30d Change"),
bottom: list.map(({ name, color, tree }) =>
baseline({
metric: tree.outputs.utxoCount30dChange,
name,
unit: Unit.count,
color,
}),
),
},
{
name: "Address Count",
title: title("Address Count 30d Change"),
bottom: list.map(({ name, color, addrCount }) =>
baseline({
metric: addrCount._30dChange,
name,
unit: Unit.count,
color,
}),
),
},
],
},
],
};
}
/**
* Grouped holdings section for address amount cohorts (has relative supply + address count)
* @param {{ list: readonly AddressCohortObject[], title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createGroupedHoldingsSectionAddressAmount({ list, title }) {
return {
name: "Holdings",
tree: [
{
name: "Supply",
tree: [
{
name: "Total",
title: title("Supply"),
bottom: [
...list.flatMap(({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.supply.total, name, color }),
),
// % of Circulating
...list.map(({ name, color, tree }) =>
line({
metric: tree.relative.supplyRelToCirculatingSupply,
name,
color,
unit: Unit.pctSupply,
}),
),
],
},
{
name: "In Profit",
title: title("Supply In Profit"),
bottom: [
...list.flatMap(({ name, color, tree }) =>
satsBtcUsd({
pattern: tree.unrealized.supplyInProfit,
name,
color,
}),
),
// % of Circulating
...list.map(({ name, color, tree }) =>
line({
metric: tree.relative.supplyInProfitRelToCirculatingSupply,
name,
color,
unit: Unit.pctSupply,
}),
),
// % of Own Supply
...list.map(({ name, color, tree }) =>
line({
metric: tree.relative.supplyInProfitRelToOwnSupply,
name,
color,
unit: Unit.pctOwn,
}),
),
...priceLines({
numbers: [100, 50, 0],
unit: Unit.pctOwn,
}),
],
},
{
name: "In Loss",
title: title("Supply In Loss"),
bottom: [
...list.flatMap(({ name, color, tree }) =>
satsBtcUsd({
pattern: tree.unrealized.supplyInLoss,
name,
color,
}),
),
// % of Circulating
...list.map(({ name, color, tree }) =>
line({
metric: tree.relative.supplyInLossRelToCirculatingSupply,
name,
color,
unit: Unit.pctSupply,
}),
),
// % of Own Supply
...list.map(({ name, color, tree }) =>
line({
metric: tree.relative.supplyInLossRelToOwnSupply,
name,
color,
unit: Unit.pctOwn,
}),
),
...priceLines({
numbers: [100, 50, 0],
unit: Unit.pctOwn,
}),
],
},
],
},
{
name: "UTXO Count",
title: title("UTXO Count"),
bottom: list.map(({ name, color, tree }) =>
line({
metric: tree.outputs.utxoCount,
name,
color,
unit: Unit.count,
}),
),
},
{
name: "Address Count",
title: title("Address Count"),
bottom: list.map(({ name, color, addrCount }) =>
line({ metric: addrCount.count, name, color, unit: Unit.count }),
),
},
{
name: "30d Changes",
tree: [
{
name: "Supply",
title: title("Supply 30d Change"),
bottom: list.flatMap(({ name, color, tree }) =>
satsBtcUsdBaseline({
pattern: tree.supply._30dChange,
name,
color,
}),
),
},
{
@@ -366,10 +861,37 @@ export function createGroupedHoldingsSection({ list, title }) {
tree: [
{
name: "Supply",
title: title("Supply"),
bottom: list.flatMap(({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.supply.total, name, color }),
),
tree: [
{
name: "Total",
title: title("Supply"),
bottom: list.flatMap(({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.supply.total, name, color }),
),
},
{
name: "In Profit",
title: title("Supply In Profit"),
bottom: list.flatMap(({ name, color, tree }) =>
satsBtcUsd({
pattern: tree.unrealized.supplyInProfit,
name,
color,
}),
),
},
{
name: "In Loss",
title: title("Supply In Loss"),
bottom: list.flatMap(({ name, color, tree }) =>
satsBtcUsd({
pattern: tree.unrealized.supplyInLoss,
name,
color,
}),
),
},
],
},
{
name: "UTXO Count",
@@ -390,7 +912,128 @@ export function createGroupedHoldingsSection({ list, title }) {
name: "Supply",
title: title("Supply 30d Change"),
bottom: list.flatMap(({ name, color, tree }) =>
satsBtcUsdBaseline({ pattern: tree.supply._30dChange, name, color }),
satsBtcUsdBaseline({
pattern: tree.supply._30dChange,
name,
color,
}),
),
},
{
name: "UTXO Count",
title: title("UTXO Count 30d Change"),
bottom: list.map(({ name, color, tree }) =>
baseline({
metric: tree.outputs.utxoCount30dChange,
name,
unit: Unit.count,
color,
}),
),
},
],
},
],
};
}
/**
* Grouped holdings section with % of Own Supply only (for cohorts without % of Circulating)
* @param {{ list: readonly (CohortAgeRange | CohortBasicWithoutMarketCap)[], title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createGroupedHoldingsSectionWithOwnSupply({ list, title }) {
return {
name: "Holdings",
tree: [
{
name: "Supply",
tree: [
{
name: "In Profit",
title: title("Supply In Profit"),
bottom: [
...list.flatMap(({ name, color, tree }) =>
satsBtcUsd({
pattern: tree.unrealized.supplyInProfit,
name,
color,
}),
),
// % of Own Supply
...list.map(({ name, color, tree }) =>
line({
metric: tree.relative.supplyInProfitRelToOwnSupply,
name,
color,
unit: Unit.pctOwn,
}),
),
...priceLines({
numbers: [100, 50, 0],
unit: Unit.pctOwn,
}),
],
},
{
name: "In Loss",
title: title("Supply In Loss"),
bottom: [
...list.flatMap(({ name, color, tree }) =>
satsBtcUsd({
pattern: tree.unrealized.supplyInLoss,
name,
color,
}),
),
// % of Own Supply
...list.map(({ name, color, tree }) =>
line({
metric: tree.relative.supplyInLossRelToOwnSupply,
name,
color,
unit: Unit.pctOwn,
}),
),
...priceLines({
numbers: [100, 50, 0],
unit: Unit.pctOwn,
}),
],
},
{
name: "Total",
title: title("Supply"),
bottom: list.flatMap(({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.supply.total, name, color }),
),
},
],
},
{
name: "UTXO Count",
title: title("UTXO Count"),
bottom: list.map(({ name, color, tree }) =>
line({
metric: tree.outputs.utxoCount,
name,
color,
unit: Unit.count,
}),
),
},
{
name: "30d Changes",
tree: [
{
name: "Supply",
title: title("Supply 30d Change"),
bottom: list.flatMap(({ name, color, tree }) =>
satsBtcUsdBaseline({
pattern: tree.supply._30dChange,
name,
color,
}),
),
},
{
@@ -421,10 +1064,96 @@ export function createGroupedHoldingsSectionWithRelative({ list, title }) {
tree: [
{
name: "Supply",
title: title("Supply"),
bottom: list.flatMap(({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.supply.total, name, color }),
),
tree: [
{
name: "Total",
title: title("Supply"),
bottom: [
...list.flatMap(({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.supply.total, name, color }),
),
// % of Circulating
...list.map(({ name, color, tree }) =>
line({
metric: tree.relative.supplyRelToCirculatingSupply,
name,
color,
unit: Unit.pctSupply,
}),
),
],
},
{
name: "In Profit",
title: title("Supply In Profit"),
bottom: [
...list.flatMap(({ name, color, tree }) =>
satsBtcUsd({
pattern: tree.unrealized.supplyInProfit,
name,
color,
}),
),
// % of Circulating
...list.map(({ name, color, tree }) =>
line({
metric: tree.relative.supplyInProfitRelToCirculatingSupply,
name,
color,
unit: Unit.pctSupply,
}),
),
// % of Own Supply
...list.map(({ name, color, tree }) =>
line({
metric: tree.relative.supplyInProfitRelToOwnSupply,
name,
color,
unit: Unit.pctOwn,
}),
),
...priceLines({
numbers: [100, 50, 0],
unit: Unit.pctOwn,
}),
],
},
{
name: "In Loss",
title: title("Supply In Loss"),
bottom: [
...list.flatMap(({ name, color, tree }) =>
satsBtcUsd({
pattern: tree.unrealized.supplyInLoss,
name,
color,
}),
),
// % of Circulating
...list.map(({ name, color, tree }) =>
line({
metric: tree.relative.supplyInLossRelToCirculatingSupply,
name,
color,
unit: Unit.pctSupply,
}),
),
// % of Own Supply
...list.map(({ name, color, tree }) =>
line({
metric: tree.relative.supplyInLossRelToOwnSupply,
name,
color,
unit: Unit.pctOwn,
}),
),
...priceLines({
numbers: [100, 50, 0],
unit: Unit.pctOwn,
}),
],
},
],
},
{
name: "UTXO Count",
@@ -445,7 +1174,11 @@ export function createGroupedHoldingsSectionWithRelative({ list, title }) {
name: "Supply",
title: title("Supply 30d Change"),
bottom: list.flatMap(({ name, color, tree }) =>
satsBtcUsdBaseline({ pattern: tree.supply._30dChange, name, color }),
satsBtcUsdBaseline({
pattern: tree.supply._30dChange,
name,
color,
}),
),
},
{
@@ -462,18 +1195,6 @@ export function createGroupedHoldingsSectionWithRelative({ list, title }) {
},
],
},
{
name: "Relative",
title: title("Relative to Circulating Supply"),
bottom: list.map(({ name, color, tree }) =>
line({
metric: tree.relative.supplyRelToCirculatingSupply,
name,
color,
unit: Unit.pctSupply,
}),
),
},
],
};
}