mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-05-27 10:04:48 -07:00
website: snapshot
This commit is contained in:
@@ -7,11 +7,10 @@
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { priceLine } from "../constants.js";
|
||||
import { line, baseline } from "../series.js";
|
||||
import { formatCohortTitle } from "../shared.js";
|
||||
import {
|
||||
createSingleSupplySeries,
|
||||
createGroupedSupplyTotalSeries,
|
||||
createGroupedSupplyInProfitSeries,
|
||||
createGroupedSupplyInLossSeries,
|
||||
createGroupedSupplySection,
|
||||
createUtxoCountSeries,
|
||||
createAddressCountSeries,
|
||||
createRealizedPriceSeries,
|
||||
@@ -25,6 +24,8 @@ import {
|
||||
createGroupedSentSatsSeries,
|
||||
createGroupedSentBitcoinSeries,
|
||||
createGroupedSentDollarsSeries,
|
||||
groupedSupplyRelativeGenerators,
|
||||
createSingleSupplyRelativeOptions,
|
||||
} from "./shared.js";
|
||||
|
||||
/**
|
||||
@@ -39,7 +40,7 @@ export function createAddressCohortFolder(ctx, args) {
|
||||
const useGroupName = "list" in args;
|
||||
const isSingle = !("list" in args);
|
||||
|
||||
const title = args.title ? `${useGroupName ? "by" : "of"} ${args.title}` : "";
|
||||
const title = formatCohortTitle(args.title);
|
||||
|
||||
return {
|
||||
name: args.name || "all",
|
||||
@@ -48,44 +49,26 @@ export function createAddressCohortFolder(ctx, args) {
|
||||
isSingle
|
||||
? {
|
||||
name: "supply",
|
||||
title: `Supply ${title}`,
|
||||
title: title("Supply"),
|
||||
bottom: createSingleSupplySeries(
|
||||
ctx,
|
||||
/** @type {AddressCohortObject} */ (args),
|
||||
createSingleSupplyRelativeOptions(ctx, /** @type {AddressCohortObject} */ (args)),
|
||||
),
|
||||
}
|
||||
: {
|
||||
name: "supply",
|
||||
tree: [
|
||||
{
|
||||
name: "total",
|
||||
title: `Supply ${title}`,
|
||||
bottom: createGroupedSupplyTotalSeries(list),
|
||||
},
|
||||
{
|
||||
name: "in profit",
|
||||
title: `Supply In Profit ${title}`,
|
||||
bottom: createGroupedSupplyInProfitSeries(list),
|
||||
},
|
||||
{
|
||||
name: "in loss",
|
||||
title: `Supply In Loss ${title}`,
|
||||
bottom: createGroupedSupplyInLossSeries(list),
|
||||
},
|
||||
],
|
||||
},
|
||||
: createGroupedSupplySection(list, title, groupedSupplyRelativeGenerators),
|
||||
|
||||
// UTXO count
|
||||
{
|
||||
name: "utxo count",
|
||||
title: `UTXO Count ${title}`,
|
||||
title: title("UTXO Count"),
|
||||
bottom: createUtxoCountSeries(list, useGroupName),
|
||||
},
|
||||
|
||||
// Address count (ADDRESS COHORTS ONLY - fully type safe!)
|
||||
{
|
||||
name: "address count",
|
||||
title: `Address Count ${title}`,
|
||||
title: title("Address Count"),
|
||||
bottom: createAddressCountSeries(ctx, list, useGroupName),
|
||||
},
|
||||
|
||||
@@ -97,12 +80,12 @@ export function createAddressCohortFolder(ctx, args) {
|
||||
? [
|
||||
{
|
||||
name: "Price",
|
||||
title: `Realized Price ${title}`,
|
||||
title: title("Realized Price"),
|
||||
top: createRealizedPriceSeries(list),
|
||||
},
|
||||
{
|
||||
name: "Ratio",
|
||||
title: `Realized Price Ratio ${title}`,
|
||||
title: title("Realized Price Ratio"),
|
||||
bottom: createRealizedPriceRatioSeries(ctx, list),
|
||||
},
|
||||
]
|
||||
@@ -112,9 +95,21 @@ export function createAddressCohortFolder(ctx, args) {
|
||||
)),
|
||||
{
|
||||
name: "capitalization",
|
||||
title: `Realized Cap ${title}`,
|
||||
title: title("Realized Cap"),
|
||||
bottom: createRealizedCapWithExtras(ctx, list, args, useGroupName),
|
||||
},
|
||||
{
|
||||
name: "value",
|
||||
title: title("Realized Value"),
|
||||
bottom: list.map(({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.realized.realizedValue,
|
||||
name: useGroupName ? name : "Realized Value",
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
},
|
||||
...(!useGroupName
|
||||
? createRealizedPnlSection(
|
||||
ctx,
|
||||
@@ -140,7 +135,7 @@ export function createAddressCohortFolder(ctx, args) {
|
||||
/**
|
||||
* Create realized price options for single cohort
|
||||
* @param {AddressCohortObject} args
|
||||
* @param {string} title
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function createRealizedPriceOptions(args, title) {
|
||||
@@ -149,7 +144,7 @@ function createRealizedPriceOptions(args, title) {
|
||||
return [
|
||||
{
|
||||
name: "price",
|
||||
title: `Realized Price ${title}`,
|
||||
title: title("Realized Price"),
|
||||
top: [
|
||||
line({
|
||||
metric: tree.realized.realizedPrice,
|
||||
@@ -199,7 +194,7 @@ function createRealizedCapWithExtras(ctx, list, args, useGroupName) {
|
||||
* Create realized PnL section for single cohort
|
||||
* @param {PartialContext} ctx
|
||||
* @param {AddressCohortObject} args
|
||||
* @param {string} title
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function createRealizedPnlSection(ctx, args, title) {
|
||||
@@ -209,7 +204,7 @@ function createRealizedPnlSection(ctx, args, title) {
|
||||
return [
|
||||
{
|
||||
name: "pnl",
|
||||
title: `Realized P&L ${title}`,
|
||||
title: title("Realized P&L"),
|
||||
bottom: [
|
||||
line({
|
||||
metric: realized.realizedProfit.sum,
|
||||
@@ -287,7 +282,7 @@ function createRealizedPnlSection(ctx, args, title) {
|
||||
},
|
||||
{
|
||||
name: "Net pnl",
|
||||
title: `Net Realized P&L ${title}`,
|
||||
title: title("Net Realized P&L"),
|
||||
bottom: [
|
||||
baseline({
|
||||
metric: realized.netRealizedPnl.sum,
|
||||
@@ -345,7 +340,7 @@ function createRealizedPnlSection(ctx, args, title) {
|
||||
},
|
||||
{
|
||||
name: "sopr",
|
||||
title: `SOPR ${title}`,
|
||||
title: title("SOPR"),
|
||||
bottom: [
|
||||
baseline({
|
||||
metric: realized.sopr,
|
||||
@@ -378,7 +373,7 @@ function createRealizedPnlSection(ctx, args, title) {
|
||||
},
|
||||
{
|
||||
name: "Sell Side Risk",
|
||||
title: `Sell Side Risk Ratio ${title}`,
|
||||
title: title("Sell Side Risk Ratio"),
|
||||
bottom: [
|
||||
line({
|
||||
metric: realized.sellSideRiskRatio,
|
||||
@@ -404,7 +399,7 @@ function createRealizedPnlSection(ctx, args, title) {
|
||||
},
|
||||
{
|
||||
name: "value",
|
||||
title: `Value Created & Destroyed ${title}`,
|
||||
title: title("Value Created & Destroyed"),
|
||||
bottom: [
|
||||
line({
|
||||
metric: realized.valueCreated,
|
||||
@@ -428,7 +423,7 @@ function createRealizedPnlSection(ctx, args, title) {
|
||||
* @param {PartialContext} ctx
|
||||
* @param {readonly AddressCohortObject[]} list
|
||||
* @param {boolean} useGroupName
|
||||
* @param {string} title
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function createUnrealizedSection(ctx, list, useGroupName, title) {
|
||||
@@ -440,7 +435,7 @@ function createUnrealizedSection(ctx, list, useGroupName, title) {
|
||||
tree: [
|
||||
{
|
||||
name: "profit",
|
||||
title: `Unrealized Profit ${title}`,
|
||||
title: title("Unrealized Profit"),
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
line({
|
||||
metric: tree.unrealized.unrealizedProfit,
|
||||
@@ -452,7 +447,7 @@ function createUnrealizedSection(ctx, list, useGroupName, title) {
|
||||
},
|
||||
{
|
||||
name: "loss",
|
||||
title: `Unrealized Loss ${title}`,
|
||||
title: title("Unrealized Loss"),
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
line({
|
||||
metric: tree.unrealized.unrealizedLoss,
|
||||
@@ -464,7 +459,7 @@ function createUnrealizedSection(ctx, list, useGroupName, title) {
|
||||
},
|
||||
{
|
||||
name: "total pnl",
|
||||
title: `Total Unrealized P&L ${title}`,
|
||||
title: title("Total Unrealized P&L"),
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
baseline({
|
||||
metric: tree.unrealized.totalUnrealizedPnl,
|
||||
@@ -476,7 +471,7 @@ function createUnrealizedSection(ctx, list, useGroupName, title) {
|
||||
},
|
||||
{
|
||||
name: "negative loss",
|
||||
title: `Negative Unrealized Loss ${title}`,
|
||||
title: title("Negative Unrealized Loss"),
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
line({
|
||||
metric: tree.unrealized.negUnrealizedLoss,
|
||||
@@ -491,7 +486,7 @@ function createUnrealizedSection(ctx, list, useGroupName, title) {
|
||||
tree: [
|
||||
{
|
||||
name: "nupl",
|
||||
title: `NUPL (Rel to Market Cap) ${title}`,
|
||||
title: title("NUPL (Rel to Market Cap)"),
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
baseline({
|
||||
metric: tree.relative.nupl,
|
||||
@@ -504,7 +499,7 @@ function createUnrealizedSection(ctx, list, useGroupName, title) {
|
||||
},
|
||||
{
|
||||
name: "profit",
|
||||
title: `Unrealized Profit (% of Market Cap) ${title}`,
|
||||
title: title("Unrealized Profit (% of Market Cap)"),
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
line({
|
||||
metric: tree.relative.unrealizedProfitRelToMarketCap,
|
||||
@@ -516,7 +511,7 @@ function createUnrealizedSection(ctx, list, useGroupName, title) {
|
||||
},
|
||||
{
|
||||
name: "loss",
|
||||
title: `Unrealized Loss (% of Market Cap) ${title}`,
|
||||
title: title("Unrealized Loss (% of Market Cap)"),
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
line({
|
||||
metric: tree.relative.unrealizedLossRelToMarketCap,
|
||||
@@ -528,7 +523,7 @@ function createUnrealizedSection(ctx, list, useGroupName, title) {
|
||||
},
|
||||
{
|
||||
name: "net pnl",
|
||||
title: `Net Unrealized P&L (% of Market Cap) ${title}`,
|
||||
title: title("Net Unrealized P&L (% of Market Cap)"),
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
baseline({
|
||||
metric: tree.relative.netUnrealizedPnlRelToMarketCap,
|
||||
@@ -540,7 +535,7 @@ function createUnrealizedSection(ctx, list, useGroupName, title) {
|
||||
},
|
||||
{
|
||||
name: "negative loss",
|
||||
title: `Negative Unrealized Loss (% of Market Cap) ${title}`,
|
||||
title: title("Negative Unrealized Loss (% of Market Cap)"),
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
line({
|
||||
metric: tree.relative.negUnrealizedLossRelToMarketCap,
|
||||
@@ -554,7 +549,7 @@ function createUnrealizedSection(ctx, list, useGroupName, title) {
|
||||
},
|
||||
{
|
||||
name: "nupl",
|
||||
title: `Net Unrealized P&L ${title}`,
|
||||
title: title("Net Unrealized P&L"),
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
baseline({
|
||||
metric: tree.unrealized.netUnrealizedPnl,
|
||||
@@ -577,7 +572,7 @@ function createUnrealizedSection(ctx, list, useGroupName, title) {
|
||||
* Create cost basis section (no percentiles for address cohorts)
|
||||
* @param {readonly AddressCohortObject[]} list
|
||||
* @param {boolean} useGroupName
|
||||
* @param {string} title
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function createCostBasisSection(list, useGroupName, title) {
|
||||
@@ -587,7 +582,7 @@ function createCostBasisSection(list, useGroupName, title) {
|
||||
tree: [
|
||||
{
|
||||
name: "min",
|
||||
title: `Min Cost Basis ${title}`,
|
||||
title: title("Min Cost Basis"),
|
||||
top: list.map(({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.costBasis.min,
|
||||
@@ -599,7 +594,7 @@ function createCostBasisSection(list, useGroupName, title) {
|
||||
},
|
||||
{
|
||||
name: "max",
|
||||
title: `Max Cost Basis ${title}`,
|
||||
title: title("Max Cost Basis"),
|
||||
top: list.map(({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.costBasis.max,
|
||||
@@ -617,7 +612,7 @@ function createCostBasisSection(list, useGroupName, title) {
|
||||
/**
|
||||
* Create activity section
|
||||
* @param {AddressCohortObject | AddressCohortGroupObject} args
|
||||
* @param {string} title
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function createActivitySection(args, title) {
|
||||
@@ -633,12 +628,12 @@ function createActivitySection(args, title) {
|
||||
tree: [
|
||||
{
|
||||
name: "Coins Destroyed",
|
||||
title: `Coins Destroyed ${title}`,
|
||||
title: title("Coins Destroyed"),
|
||||
bottom: createSingleCoinsDestroyedSeries(cohort),
|
||||
},
|
||||
{
|
||||
name: "Sent",
|
||||
title: `Sent ${title}`,
|
||||
title: title("Sent"),
|
||||
bottom: createSingleSentSeries(cohort),
|
||||
},
|
||||
],
|
||||
@@ -653,22 +648,22 @@ function createActivitySection(args, title) {
|
||||
tree: [
|
||||
{
|
||||
name: "coinblocks destroyed",
|
||||
title: `Coinblocks Destroyed ${title}`,
|
||||
title: title("Coinblocks Destroyed"),
|
||||
bottom: createGroupedCoinblocksDestroyedSeries(list),
|
||||
},
|
||||
{
|
||||
name: "coindays destroyed",
|
||||
title: `Coindays Destroyed ${title}`,
|
||||
title: title("Coindays Destroyed"),
|
||||
bottom: createGroupedCoindaysDestroyedSeries(list),
|
||||
},
|
||||
{
|
||||
name: "satblocks destroyed",
|
||||
title: `Satblocks Destroyed ${title}`,
|
||||
title: title("Satblocks Destroyed"),
|
||||
bottom: createGroupedSatblocksDestroyedSeries(list),
|
||||
},
|
||||
{
|
||||
name: "satdays destroyed",
|
||||
title: `Satdays Destroyed ${title}`,
|
||||
title: title("Satdays Destroyed"),
|
||||
bottom: createGroupedSatdaysDestroyedSeries(list),
|
||||
},
|
||||
{
|
||||
@@ -676,17 +671,17 @@ function createActivitySection(args, title) {
|
||||
tree: [
|
||||
{
|
||||
name: "sats",
|
||||
title: `Sent (Sats) ${title}`,
|
||||
title: title("Sent (Sats)"),
|
||||
bottom: createGroupedSentSatsSeries(list),
|
||||
},
|
||||
{
|
||||
name: "bitcoin",
|
||||
title: `Sent (BTC) ${title}`,
|
||||
title: title("Sent (BTC)"),
|
||||
bottom: createGroupedSentBitcoinSeries(list),
|
||||
},
|
||||
{
|
||||
name: "dollars",
|
||||
title: `Sent ($) ${title}`,
|
||||
title: title("Sent ($)"),
|
||||
bottom: createGroupedSentDollarsSeries(list),
|
||||
},
|
||||
],
|
||||
|
||||
@@ -24,10 +24,20 @@ const entries = (obj) =>
|
||||
);
|
||||
|
||||
/** @type {readonly AddressableType[]} */
|
||||
const ADDRESSABLE_TYPES = ["p2pk65", "p2pk33", "p2pkh", "p2sh", "p2wpkh", "p2wsh", "p2tr", "p2a"];
|
||||
const ADDRESSABLE_TYPES = [
|
||||
"p2pk65",
|
||||
"p2pk33",
|
||||
"p2pkh",
|
||||
"p2sh",
|
||||
"p2wpkh",
|
||||
"p2wsh",
|
||||
"p2tr",
|
||||
"p2a",
|
||||
];
|
||||
|
||||
/** @type {(key: SpendableType) => key is AddressableType} */
|
||||
const isAddressable = (key) => ADDRESSABLE_TYPES.includes(/** @type {any} */ (key));
|
||||
const isAddressable = (key) =>
|
||||
ADDRESSABLE_TYPES.includes(/** @type {any} */ (key));
|
||||
|
||||
/**
|
||||
* Build all cohort data from brk tree
|
||||
@@ -51,8 +61,7 @@ export function buildCohortData(colors, brk) {
|
||||
YEAR_NAMES,
|
||||
} = brk;
|
||||
|
||||
// Base cohort representing "all" - CohortAll (adjustedSopr + percentiles but no RelToMarketCap)
|
||||
/** @type {CohortAll} */
|
||||
// Base cohort representing "all"
|
||||
const cohortAll = {
|
||||
name: "",
|
||||
title: "",
|
||||
@@ -61,9 +70,8 @@ export function buildCohortData(colors, brk) {
|
||||
addrCount: addrCount.all,
|
||||
};
|
||||
|
||||
// Term cohorts - split because short is CohortFull, long is CohortWithPercentiles
|
||||
// Term cohorts
|
||||
const shortNames = TERM_NAMES.short;
|
||||
/** @type {CohortFull} */
|
||||
const termShort = {
|
||||
name: shortNames.short,
|
||||
title: shortNames.long,
|
||||
@@ -79,43 +87,40 @@ export function buildCohortData(colors, brk) {
|
||||
tree: utxoCohorts.term.long,
|
||||
};
|
||||
|
||||
// Max age cohorts (up to X time) - CohortWithAdjusted (adjustedSopr only)
|
||||
/** @type {readonly CohortWithAdjusted[]} */
|
||||
// Max age cohorts (up to X time)
|
||||
const upToDate = entries(utxoCohorts.maxAge).map(([key, tree]) => {
|
||||
const names = MAX_AGE_NAMES[key];
|
||||
return {
|
||||
name: names.short,
|
||||
title: names.long,
|
||||
title: `UTXOs ${names.long}`,
|
||||
color: colors[maxAgeColors[key]],
|
||||
tree,
|
||||
};
|
||||
});
|
||||
|
||||
// Min age cohorts (from X time) - CohortBasicWithMarketCap (has RelToMarketCap)
|
||||
/** @type {readonly CohortBasicWithMarketCap[]} */
|
||||
// Min age cohorts (from X time)
|
||||
const fromDate = entries(utxoCohorts.minAge).map(([key, tree]) => {
|
||||
const names = MIN_AGE_NAMES[key];
|
||||
return {
|
||||
name: names.short,
|
||||
title: names.long,
|
||||
title: `UTXOs ${names.long}`,
|
||||
color: colors[minAgeColors[key]],
|
||||
tree,
|
||||
};
|
||||
});
|
||||
|
||||
// Age range cohorts - CohortAgeRange (no nupl)
|
||||
// Age range cohorts
|
||||
const dateRange = entries(utxoCohorts.ageRange).map(([key, tree]) => {
|
||||
const names = AGE_RANGE_NAMES[key];
|
||||
return {
|
||||
name: names.short,
|
||||
title: names.long,
|
||||
title: `UTXOs ${names.long}`,
|
||||
color: colors[ageRangeColors[key]],
|
||||
tree,
|
||||
};
|
||||
});
|
||||
|
||||
// Epoch cohorts - CohortBasicWithoutMarketCap (no RelToMarketCap)
|
||||
/** @type {readonly CohortBasicWithoutMarketCap[]} */
|
||||
// Epoch cohorts
|
||||
const epoch = entries(utxoCohorts.epoch).map(([key, tree]) => {
|
||||
const names = EPOCH_NAMES[key];
|
||||
return {
|
||||
@@ -126,66 +131,61 @@ export function buildCohortData(colors, brk) {
|
||||
};
|
||||
});
|
||||
|
||||
// UTXOs above amount - CohortBasicWithMarketCap (has RelToMarketCap)
|
||||
/** @type {readonly CohortBasicWithMarketCap[]} */
|
||||
// UTXOs above amount
|
||||
const utxosAboveAmount = entries(utxoCohorts.geAmount).map(([key, tree]) => {
|
||||
const names = GE_AMOUNT_NAMES[key];
|
||||
return {
|
||||
name: names.short,
|
||||
title: names.long,
|
||||
title: `UTXOs ${names.long}`,
|
||||
color: colors[geAmountColors[key]],
|
||||
tree,
|
||||
};
|
||||
});
|
||||
|
||||
// Addresses above amount
|
||||
/** @type {readonly AddressCohortObject[]} */
|
||||
const addressesAboveAmount = entries(addressCohorts.geAmount).map(
|
||||
([key, tree]) => {
|
||||
const names = GE_AMOUNT_NAMES[key];
|
||||
return {
|
||||
name: names.short,
|
||||
title: names.long,
|
||||
title: `Addresses ${names.long}`,
|
||||
color: colors[geAmountColors[key]],
|
||||
tree,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
// UTXOs under amount - CohortBasicWithMarketCap (has RelToMarketCap)
|
||||
/** @type {readonly CohortBasicWithMarketCap[]} */
|
||||
// UTXOs under amount
|
||||
const utxosUnderAmount = entries(utxoCohorts.ltAmount).map(([key, tree]) => {
|
||||
const names = LT_AMOUNT_NAMES[key];
|
||||
return {
|
||||
name: names.short,
|
||||
title: names.long,
|
||||
title: `UTXOs ${names.long}`,
|
||||
color: colors[ltAmountColors[key]],
|
||||
tree,
|
||||
};
|
||||
});
|
||||
|
||||
// Addresses under amount
|
||||
/** @type {readonly AddressCohortObject[]} */
|
||||
const addressesUnderAmount = entries(addressCohorts.ltAmount).map(
|
||||
([key, tree]) => {
|
||||
const names = LT_AMOUNT_NAMES[key];
|
||||
return {
|
||||
name: names.short,
|
||||
title: names.long,
|
||||
title: `Addresses ${names.long}`,
|
||||
color: colors[ltAmountColors[key]],
|
||||
tree,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
// UTXOs amount ranges - CohortBasicWithoutMarketCap (no RelToMarketCap)
|
||||
/** @type {readonly CohortBasicWithoutMarketCap[]} */
|
||||
// UTXOs amount ranges
|
||||
const utxosAmountRanges = entries(utxoCohorts.amountRange).map(
|
||||
([key, tree]) => {
|
||||
const names = AMOUNT_RANGE_NAMES[key];
|
||||
return {
|
||||
name: names.short,
|
||||
title: names.long,
|
||||
title: `UTXOs ${names.long}`,
|
||||
color: colors[amountRangeColors[key]],
|
||||
tree,
|
||||
};
|
||||
@@ -193,13 +193,12 @@ export function buildCohortData(colors, brk) {
|
||||
);
|
||||
|
||||
// Addresses amount ranges
|
||||
/** @type {readonly AddressCohortObject[]} */
|
||||
const addressesAmountRanges = entries(addressCohorts.amountRange).map(
|
||||
([key, tree]) => {
|
||||
const names = AMOUNT_RANGE_NAMES[key];
|
||||
return {
|
||||
name: names.short,
|
||||
title: names.long,
|
||||
title: `Addresses ${names.long}`,
|
||||
color: colors[amountRangeColors[key]],
|
||||
tree,
|
||||
};
|
||||
@@ -207,33 +206,30 @@ export function buildCohortData(colors, brk) {
|
||||
);
|
||||
|
||||
// Spendable type cohorts - split by addressability
|
||||
/** @type {readonly CohortAddress[]} */
|
||||
const typeAddressable = ADDRESSABLE_TYPES.map((key) => {
|
||||
const names = SPENDABLE_TYPE_NAMES[key];
|
||||
return {
|
||||
name: names.short,
|
||||
title: names.long,
|
||||
title: names.short,
|
||||
color: colors[spendableTypeColors[key]],
|
||||
tree: utxoCohorts.type[key],
|
||||
addrCount: addrCount[key],
|
||||
};
|
||||
});
|
||||
|
||||
/** @type {readonly CohortBasicWithoutMarketCap[]} */
|
||||
const typeOther = entries(utxoCohorts.type)
|
||||
.filter(([key]) => !isAddressable(key))
|
||||
.map(([key, tree]) => {
|
||||
const names = SPENDABLE_TYPE_NAMES[key];
|
||||
return {
|
||||
name: names.short,
|
||||
title: names.long,
|
||||
title: names.short,
|
||||
color: colors[spendableTypeColors[key]],
|
||||
tree,
|
||||
};
|
||||
});
|
||||
|
||||
// Year cohorts - CohortBasicWithoutMarketCap (no RelToMarketCap)
|
||||
/** @type {readonly CohortBasicWithoutMarketCap[]} */
|
||||
// Year cohorts
|
||||
const year = entries(utxoCohorts.year).map(([key, tree]) => {
|
||||
const names = YEAR_NAMES[key];
|
||||
return {
|
||||
|
||||
@@ -10,8 +10,7 @@ export {
|
||||
createCohortFolderAll,
|
||||
createCohortFolderFull,
|
||||
createCohortFolderWithAdjusted,
|
||||
createCohortFolderWithPercentiles,
|
||||
createCohortFolderLongTerm,
|
||||
createCohortFolderWithNupl,
|
||||
createCohortFolderAgeRange,
|
||||
createCohortFolderBasicWithMarketCap,
|
||||
createCohortFolderBasicWithoutMarketCap,
|
||||
|
||||
@@ -9,46 +9,25 @@ import { satsBtcUsd } from "../shared.js";
|
||||
* Create supply section for a single cohort
|
||||
* @param {PartialContext} ctx
|
||||
* @param {CohortObject} cohort
|
||||
* @param {Object} [options]
|
||||
* @param {AnyFetchedSeriesBlueprint[]} [options.supplyRelative] - Supply relative to circulating supply metrics
|
||||
* @param {AnyFetchedSeriesBlueprint[]} [options.pnlRelative] - Supply in profit/loss relative to circulating supply metrics
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function createSingleSupplySeries(ctx, cohort) {
|
||||
export function createSingleSupplySeries(ctx, cohort, { supplyRelative = [], pnlRelative = [] } = {}) {
|
||||
const { colors } = ctx;
|
||||
const { tree } = cohort;
|
||||
|
||||
return [
|
||||
...satsBtcUsd(tree.supply.total, "Supply", colors.default),
|
||||
...("supplyRelToCirculatingSupply" in tree.relative
|
||||
? [
|
||||
line({
|
||||
metric: tree.relative.supplyRelToCirculatingSupply,
|
||||
name: "Supply",
|
||||
color: colors.default,
|
||||
unit: Unit.pctSupply,
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
...supplyRelative,
|
||||
...satsBtcUsd(tree.unrealized.supplyInProfit, "In Profit", colors.green),
|
||||
...satsBtcUsd(tree.unrealized.supplyInLoss, "In Loss", colors.red),
|
||||
...satsBtcUsd(tree.supply.halved, "half", colors.gray).map((s) => ({
|
||||
...s,
|
||||
options: { lineStyle: 4 },
|
||||
})),
|
||||
...("supplyInProfitRelToCirculatingSupply" in tree.relative
|
||||
? [
|
||||
line({
|
||||
metric: tree.relative.supplyInProfitRelToCirculatingSupply,
|
||||
name: "In Profit",
|
||||
color: colors.green,
|
||||
unit: Unit.pctSupply,
|
||||
}),
|
||||
line({
|
||||
metric: tree.relative.supplyInLossRelToCirculatingSupply,
|
||||
name: "In Loss",
|
||||
color: colors.red,
|
||||
unit: Unit.pctSupply,
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
...pnlRelative,
|
||||
line({
|
||||
metric: tree.relative.supplyInProfitRelToOwnSupply,
|
||||
name: "In Profit",
|
||||
@@ -74,67 +53,198 @@ export function createSingleSupplySeries(ctx, cohort) {
|
||||
|
||||
/**
|
||||
* Create supply total series for grouped cohorts
|
||||
* @param {readonly CohortObject[]} list
|
||||
* @template {readonly CohortObject[]} T
|
||||
* @param {T} list
|
||||
* @param {Object} [options]
|
||||
* @param {(cohort: T[number]) => AnyFetchedSeriesBlueprint[]} [options.relativeMetrics] - Generator for relative supply metrics
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function createGroupedSupplyTotalSeries(list) {
|
||||
return list.flatMap(({ color, name, tree }) => [
|
||||
...satsBtcUsd(tree.supply.total, name, color),
|
||||
...("supplyRelToCirculatingSupply" in tree.relative
|
||||
? [
|
||||
line({
|
||||
metric: tree.relative.supplyRelToCirculatingSupply,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.pctSupply,
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
export function createGroupedSupplyTotalSeries(list, { relativeMetrics } = {}) {
|
||||
return list.flatMap((cohort) => [
|
||||
...satsBtcUsd(cohort.tree.supply.total, cohort.name, cohort.color),
|
||||
...(relativeMetrics ? relativeMetrics(cohort) : []),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create supply in profit series for grouped cohorts
|
||||
* @param {readonly CohortObject[]} list
|
||||
* @template {readonly CohortObject[]} T
|
||||
* @param {T} list
|
||||
* @param {Object} [options]
|
||||
* @param {(cohort: T[number]) => AnyFetchedSeriesBlueprint[]} [options.relativeMetrics] - Generator for relative supply metrics
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function createGroupedSupplyInProfitSeries(list) {
|
||||
return list.flatMap(({ color, name, tree }) => [
|
||||
...satsBtcUsd(tree.unrealized.supplyInProfit, name, color),
|
||||
...("supplyInProfitRelToCirculatingSupply" in tree.relative
|
||||
? [
|
||||
line({
|
||||
metric: tree.relative.supplyInProfitRelToCirculatingSupply,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.pctSupply,
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
export function createGroupedSupplyInProfitSeries(list, { relativeMetrics } = {}) {
|
||||
return list.flatMap((cohort) => [
|
||||
...satsBtcUsd(cohort.tree.unrealized.supplyInProfit, cohort.name, cohort.color),
|
||||
...(relativeMetrics ? relativeMetrics(cohort) : []),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create supply in loss series for grouped cohorts
|
||||
* @param {readonly CohortObject[]} list
|
||||
* @template {readonly CohortObject[]} T
|
||||
* @param {T} list
|
||||
* @param {Object} [options]
|
||||
* @param {(cohort: T[number]) => AnyFetchedSeriesBlueprint[]} [options.relativeMetrics] - Generator for relative supply metrics
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function createGroupedSupplyInLossSeries(list) {
|
||||
return list.flatMap(({ color, name, tree }) => [
|
||||
...satsBtcUsd(tree.unrealized.supplyInLoss, name, color),
|
||||
...("supplyInLossRelToCirculatingSupply" in tree.relative
|
||||
? [
|
||||
line({
|
||||
metric: tree.relative.supplyInLossRelToCirculatingSupply,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.pctSupply,
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
export function createGroupedSupplyInLossSeries(list, { relativeMetrics } = {}) {
|
||||
return list.flatMap((cohort) => [
|
||||
...satsBtcUsd(cohort.tree.unrealized.supplyInLoss, cohort.name, cohort.color),
|
||||
...(relativeMetrics ? relativeMetrics(cohort) : []),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create supply section for grouped cohorts
|
||||
* @template {readonly CohortObject[]} T
|
||||
* @param {T} list
|
||||
* @param {(metric: string) => string} title
|
||||
* @param {Object} [options]
|
||||
* @param {(cohort: T[number]) => AnyFetchedSeriesBlueprint[]} [options.supplyRelativeMetrics] - Generator for supply relative metrics
|
||||
* @param {(cohort: T[number]) => AnyFetchedSeriesBlueprint[]} [options.profitRelativeMetrics] - Generator for supply in profit relative metrics
|
||||
* @param {(cohort: T[number]) => AnyFetchedSeriesBlueprint[]} [options.lossRelativeMetrics] - Generator for supply in loss relative metrics
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createGroupedSupplySection(list, title, { supplyRelativeMetrics, profitRelativeMetrics, lossRelativeMetrics } = {}) {
|
||||
return {
|
||||
name: "supply",
|
||||
tree: [
|
||||
{
|
||||
name: "total",
|
||||
title: title("Supply"),
|
||||
bottom: createGroupedSupplyTotalSeries(list, { relativeMetrics: supplyRelativeMetrics }),
|
||||
},
|
||||
{
|
||||
name: "in profit",
|
||||
title: title("Supply In Profit"),
|
||||
bottom: createGroupedSupplyInProfitSeries(list, { relativeMetrics: profitRelativeMetrics }),
|
||||
},
|
||||
{
|
||||
name: "in loss",
|
||||
title: title("Supply In Loss"),
|
||||
bottom: createGroupedSupplyInLossSeries(list, { relativeMetrics: lossRelativeMetrics }),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Circulating Supply Relative Metrics Generators
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Create supply relative to circulating supply series for single cohort
|
||||
* @param {PartialContext} ctx
|
||||
* @param {CohortWithCirculatingSupplyRelative} cohort
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function createSupplyRelativeToCirculatingSeries(ctx, cohort) {
|
||||
return [
|
||||
line({
|
||||
metric: cohort.tree.relative.supplyRelToCirculatingSupply,
|
||||
name: "Supply",
|
||||
color: ctx.colors.default,
|
||||
unit: Unit.pctSupply,
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create supply in profit/loss relative to circulating supply series for single cohort
|
||||
* @param {PartialContext} ctx
|
||||
* @param {CohortWithCirculatingSupplyRelative} cohort
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function createSupplyPnlRelativeToCirculatingSeries(ctx, cohort) {
|
||||
return [
|
||||
line({
|
||||
metric: cohort.tree.relative.supplyInProfitRelToCirculatingSupply,
|
||||
name: "In Profit",
|
||||
color: ctx.colors.green,
|
||||
unit: Unit.pctSupply,
|
||||
}),
|
||||
line({
|
||||
metric: cohort.tree.relative.supplyInLossRelToCirculatingSupply,
|
||||
name: "In Loss",
|
||||
color: ctx.colors.red,
|
||||
unit: Unit.pctSupply,
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create supply relative to circulating supply metrics generator for grouped cohorts
|
||||
* @param {CohortWithCirculatingSupplyRelative} cohort
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function createGroupedSupplyRelativeMetrics(cohort) {
|
||||
return [
|
||||
line({
|
||||
metric: cohort.tree.relative.supplyRelToCirculatingSupply,
|
||||
name: cohort.name,
|
||||
color: cohort.color,
|
||||
unit: Unit.pctSupply,
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create supply in profit relative to circulating supply metrics generator for grouped cohorts
|
||||
* @param {CohortWithCirculatingSupplyRelative} cohort
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function createGroupedSupplyInProfitRelativeMetrics(cohort) {
|
||||
return [
|
||||
line({
|
||||
metric: cohort.tree.relative.supplyInProfitRelToCirculatingSupply,
|
||||
name: cohort.name,
|
||||
color: cohort.color,
|
||||
unit: Unit.pctSupply,
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create supply in loss relative to circulating supply metrics generator for grouped cohorts
|
||||
* @param {CohortWithCirculatingSupplyRelative} cohort
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function createGroupedSupplyInLossRelativeMetrics(cohort) {
|
||||
return [
|
||||
line({
|
||||
metric: cohort.tree.relative.supplyInLossRelToCirculatingSupply,
|
||||
name: cohort.name,
|
||||
color: cohort.color,
|
||||
unit: Unit.pctSupply,
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Grouped supply relative generators object for cohorts with circulating supply relative
|
||||
* @type {{ supplyRelativeMetrics: typeof createGroupedSupplyRelativeMetrics, profitRelativeMetrics: typeof createGroupedSupplyInProfitRelativeMetrics, lossRelativeMetrics: typeof createGroupedSupplyInLossRelativeMetrics }}
|
||||
*/
|
||||
export const groupedSupplyRelativeGenerators = {
|
||||
supplyRelativeMetrics: createGroupedSupplyRelativeMetrics,
|
||||
profitRelativeMetrics: createGroupedSupplyInProfitRelativeMetrics,
|
||||
lossRelativeMetrics: createGroupedSupplyInLossRelativeMetrics,
|
||||
};
|
||||
|
||||
/**
|
||||
* Create single cohort supply relative options for cohorts with circulating supply relative
|
||||
* @param {PartialContext} ctx
|
||||
* @param {CohortWithCirculatingSupplyRelative} cohort
|
||||
* @returns {{ supplyRelative: AnyFetchedSeriesBlueprint[], pnlRelative: AnyFetchedSeriesBlueprint[] }}
|
||||
*/
|
||||
export function createSingleSupplyRelativeOptions(ctx, cohort) {
|
||||
return {
|
||||
supplyRelative: createSupplyRelativeToCirculatingSeries(ctx, cohort),
|
||||
pnlRelative: createSupplyPnlRelativeToCirculatingSeries(ctx, cohort),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create UTXO count series
|
||||
* @param {readonly CohortObject[]} list
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user