mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-07-11 19:18:14 -07:00
global: snapshot
This commit is contained in:
@@ -2,7 +2,7 @@ import { colors } from "../utils/colors.js";
|
||||
import { brk } from "../client.js";
|
||||
import { Unit } from "../utils/units.js";
|
||||
import { dots, line, baseline, price, rollingWindowsTree, percentRatioDots } from "./series.js";
|
||||
import { satsBtcUsd } from "./shared.js";
|
||||
import { satsBtcUsd, priceRatioPercentilesTree } from "./shared.js";
|
||||
|
||||
/**
|
||||
* Create Cointime section
|
||||
@@ -173,57 +173,18 @@ export function createCointimeSection() {
|
||||
),
|
||||
],
|
||||
},
|
||||
...prices.map(({ pattern, name, color }) => {
|
||||
const p = pattern.percentiles;
|
||||
const pctUsd = /** @type {const} */ ([
|
||||
{ name: "pct95", prop: p.pct95.price, color: colors.ratioPct._95 },
|
||||
{ name: "pct5", prop: p.pct5.price, color: colors.ratioPct._5 },
|
||||
{ name: "pct98", prop: p.pct98.price, color: colors.ratioPct._98 },
|
||||
{ name: "pct2", prop: p.pct2.price, color: colors.ratioPct._2 },
|
||||
{ name: "pct99", prop: p.pct99.price, color: colors.ratioPct._99 },
|
||||
{ name: "pct1", prop: p.pct1.price, color: colors.ratioPct._1 },
|
||||
]);
|
||||
const pctRatio = /** @type {const} */ ([
|
||||
{ name: "pct95", prop: p.pct95.ratio, color: colors.ratioPct._95 },
|
||||
{ name: "pct5", prop: p.pct5.ratio, color: colors.ratioPct._5 },
|
||||
{ name: "pct98", prop: p.pct98.ratio, color: colors.ratioPct._98 },
|
||||
{ name: "pct2", prop: p.pct2.ratio, color: colors.ratioPct._2 },
|
||||
{ name: "pct99", prop: p.pct99.ratio, color: colors.ratioPct._99 },
|
||||
{ name: "pct1", prop: p.pct1.ratio, color: colors.ratioPct._1 },
|
||||
]);
|
||||
return {
|
||||
name,
|
||||
tree: [
|
||||
{
|
||||
name: "Price",
|
||||
title: `${name} Price`,
|
||||
top: [
|
||||
price({ metric: pattern, name, color }),
|
||||
price({
|
||||
metric: all.realized.price,
|
||||
name: "Realized",
|
||||
color: colors.realized,
|
||||
defaultActive: false,
|
||||
}),
|
||||
...pctUsd.map(({ name: pName, prop, color: pColor }) =>
|
||||
price({ metric: prop, name: pName, color: pColor, defaultActive: false, options: { lineStyle: 1 } }),
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Ratio",
|
||||
title: `${name} Price Ratio`,
|
||||
top: [price({ metric: pattern, name, color })],
|
||||
bottom: [
|
||||
baseline({ metric: pattern.ratio, name: "Ratio", unit: Unit.ratio, base: 1 }),
|
||||
...pctRatio.map(({ name: pName, prop, color: pColor }) =>
|
||||
line({ metric: prop, name: pName, color: pColor, defaultActive: false, unit: Unit.ratio, options: { lineStyle: 1 } }),
|
||||
),
|
||||
],
|
||||
},
|
||||
...prices.map(({ pattern, name, color }) => ({
|
||||
name,
|
||||
tree: priceRatioPercentilesTree({
|
||||
pattern,
|
||||
title: `${name} Price`,
|
||||
legend: name,
|
||||
color,
|
||||
priceReferences: [
|
||||
price({ metric: all.realized.price, name: "Realized", color: colors.realized, defaultActive: false }),
|
||||
],
|
||||
};
|
||||
}),
|
||||
}),
|
||||
})),
|
||||
],
|
||||
},
|
||||
|
||||
|
||||
@@ -33,6 +33,9 @@ export function buildCohortData() {
|
||||
AMOUNT_RANGE_NAMES,
|
||||
SPENDABLE_TYPE_NAMES,
|
||||
CLASS_NAMES,
|
||||
PROFITABILITY_RANGE_NAMES,
|
||||
PROFIT_NAMES,
|
||||
LOSS_NAMES,
|
||||
} = brk;
|
||||
|
||||
const cohortAll = {
|
||||
@@ -191,6 +194,28 @@ export function buildCohortData() {
|
||||
tree: utxoCohorts.class[key],
|
||||
}));
|
||||
|
||||
const { range, profit, loss } = utxoCohorts.profitability;
|
||||
|
||||
const profitabilityRange = entries(PROFITABILITY_RANGE_NAMES).map(
|
||||
([key, names], i, arr) => ({
|
||||
name: names.short,
|
||||
color: colors.at(i, arr.length),
|
||||
pattern: range[key],
|
||||
}),
|
||||
);
|
||||
|
||||
const profitabilityProfit = entries(PROFIT_NAMES).map(([key, names], i, arr) => ({
|
||||
name: names.short,
|
||||
color: colors.at(i, arr.length),
|
||||
pattern: profit[key],
|
||||
}));
|
||||
|
||||
const profitabilityLoss = entries(LOSS_NAMES).map(([key, names], i, arr) => ({
|
||||
name: names.short,
|
||||
color: colors.at(i, arr.length),
|
||||
pattern: loss[key],
|
||||
}));
|
||||
|
||||
return {
|
||||
cohortAll,
|
||||
termShort,
|
||||
@@ -208,5 +233,8 @@ export function buildCohortData() {
|
||||
typeAddressable,
|
||||
typeOther,
|
||||
class: class_,
|
||||
profitabilityRange,
|
||||
profitabilityProfit,
|
||||
profitabilityLoss,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
* - activity.js: SOPR, Volume, Lifespan
|
||||
*/
|
||||
|
||||
import { formatCohortTitle, satsBtcUsd } from "../shared.js";
|
||||
import { formatCohortTitle, satsBtcUsd, satsBtcUsdFullTree, simplePriceRatioTree, groupedSimplePriceRatioTree } from "../shared.js";
|
||||
import { ROLLING_WINDOWS, line, baseline, percentRatio, rollingWindowsTree, rollingPercentRatioTree } from "../series.js";
|
||||
import { Unit } from "../../utils/units.js";
|
||||
|
||||
// Section builders
|
||||
import {
|
||||
@@ -205,8 +207,11 @@ export function createCohortFolderAgeRangeWithMatured(cohort) {
|
||||
const title = formatCohortTitle(cohort.name);
|
||||
folder.tree.push({
|
||||
name: "Matured",
|
||||
title: title("Matured Supply"),
|
||||
bottom: satsBtcUsd({ pattern: cohort.matured, name: cohort.name }),
|
||||
tree: satsBtcUsdFullTree({
|
||||
pattern: cohort.matured,
|
||||
name: cohort.name,
|
||||
title: title("Matured Supply"),
|
||||
}),
|
||||
});
|
||||
return folder;
|
||||
}
|
||||
@@ -452,7 +457,7 @@ export function createGroupedCohortFolderAgeRangeWithMatured({
|
||||
name: "Matured",
|
||||
title: title("Matured Supply"),
|
||||
bottom: list.flatMap((cohort) =>
|
||||
satsBtcUsd({ pattern: cohort.matured, name: cohort.name, color: cohort.color }),
|
||||
satsBtcUsd({ pattern: cohort.matured.base, name: cohort.name, color: cohort.color }),
|
||||
),
|
||||
});
|
||||
return folder;
|
||||
@@ -580,3 +585,212 @@ export function createGroupedAddressCohortFolder({
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// UTXO Profitability Folder Builders
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @param {{ name: string, color: Color, pattern: RealizedSupplyPattern }} bucket
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
function singleBucketFolder({ name, color, pattern }) {
|
||||
return {
|
||||
name,
|
||||
tree: [
|
||||
{
|
||||
name: "Supply",
|
||||
tree: [
|
||||
{
|
||||
name: "All",
|
||||
title: `${name}: Supply`,
|
||||
bottom: satsBtcUsd({ pattern: pattern.supply.all, name, color }),
|
||||
},
|
||||
{
|
||||
name: "STH",
|
||||
title: `${name}: STH Supply`,
|
||||
bottom: satsBtcUsd({ pattern: pattern.supply.sth, name, color }),
|
||||
},
|
||||
{
|
||||
name: "Change",
|
||||
tree: [
|
||||
{ ...rollingWindowsTree({ 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" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Realized Cap",
|
||||
tree: [
|
||||
{
|
||||
name: "All",
|
||||
title: `${name}: Realized Cap`,
|
||||
bottom: [line({ metric: pattern.realizedCap.all, name, color, unit: Unit.usd })],
|
||||
},
|
||||
{
|
||||
name: "STH",
|
||||
title: `${name}: STH Realized Cap`,
|
||||
bottom: [line({ metric: pattern.realizedCap.sth, name, color, unit: Unit.usd })],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Realized Price",
|
||||
tree: simplePriceRatioTree({
|
||||
pattern: pattern.realizedPrice,
|
||||
title: `${name}: Realized Price`,
|
||||
legend: name,
|
||||
color,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "NUPL",
|
||||
title: `${name}: NUPL`,
|
||||
bottom: [line({ metric: pattern.nupl.ratio, name, color, unit: Unit.ratio })],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{ name: string, color: Color, pattern: RealizedSupplyPattern }[]} list
|
||||
* @param {string} titlePrefix
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function groupedBucketCharts(list, titlePrefix) {
|
||||
return [
|
||||
{
|
||||
name: "Supply",
|
||||
tree: [
|
||||
{
|
||||
name: "All",
|
||||
title: `${titlePrefix}: Supply`,
|
||||
bottom: list.flatMap(({ name, color, pattern }) =>
|
||||
satsBtcUsd({ pattern: pattern.supply.all, name, color }),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "STH",
|
||||
title: `${titlePrefix}: STH Supply`,
|
||||
bottom: list.flatMap(({ name, color, pattern }) =>
|
||||
satsBtcUsd({ pattern: pattern.supply.sth, name, color }),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Change",
|
||||
tree: [
|
||||
{
|
||||
name: "Absolute",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: `${titlePrefix}: Supply Change`,
|
||||
bottom: ROLLING_WINDOWS.flatMap((w) =>
|
||||
list.map(({ name, color, pattern }) =>
|
||||
baseline({ metric: pattern.supply.all.delta.absolute[w.key], name: `${name} ${w.name}`, color, unit: Unit.sats }),
|
||||
),
|
||||
),
|
||||
},
|
||||
...ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: `${titlePrefix}: Supply Change ${w.name}`,
|
||||
bottom: list.map(({ name, color, pattern }) =>
|
||||
baseline({ metric: pattern.supply.all.delta.absolute[w.key], name, color, unit: Unit.sats }),
|
||||
),
|
||||
})),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Rate",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
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 }),
|
||||
),
|
||||
),
|
||||
},
|
||||
...ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: `${titlePrefix}: Supply Rate ${w.name}`,
|
||||
bottom: list.flatMap(({ name, color, pattern }) =>
|
||||
percentRatio({ pattern: pattern.supply.all.delta.rate[w.key], name, color }),
|
||||
),
|
||||
})),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Realized Cap",
|
||||
tree: [
|
||||
{
|
||||
name: "All",
|
||||
title: `${titlePrefix}: Realized Cap`,
|
||||
bottom: list.map(({ name, color, pattern }) =>
|
||||
line({ metric: pattern.realizedCap.all, name, color, unit: Unit.usd }),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "STH",
|
||||
title: `${titlePrefix}: STH Realized Cap`,
|
||||
bottom: list.map(({ name, color, pattern }) =>
|
||||
line({ metric: pattern.realizedCap.sth, name, color, unit: Unit.usd }),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Realized Price",
|
||||
tree: groupedSimplePriceRatioTree({
|
||||
list: list.map(({ name, color, pattern }) => ({ name, color, pattern: pattern.realizedPrice })),
|
||||
title: `${titlePrefix}: Realized Price`,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "NUPL",
|
||||
title: `${titlePrefix}: NUPL`,
|
||||
bottom: list.map(({ name, color, pattern }) =>
|
||||
line({ metric: pattern.nupl.ratio, name, color, unit: Unit.ratio }),
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{ range: { name: string, color: Color, pattern: RealizedSupplyPattern }[], profit: { name: string, color: Color, pattern: RealizedSupplyPattern }[], loss: { name: string, color: Color, pattern: RealizedSupplyPattern }[] }} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createUtxoProfitabilitySection({ range, profit, loss }) {
|
||||
return {
|
||||
name: "UTXO Profitability",
|
||||
tree: [
|
||||
{
|
||||
name: "Range",
|
||||
tree: [
|
||||
{ name: "Compare", tree: groupedBucketCharts(range, "Profitability Range") },
|
||||
...range.map(singleBucketFolder),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "In Profit",
|
||||
tree: [
|
||||
{ name: "Compare", tree: groupedBucketCharts(profit, "In Profit") },
|
||||
...profit.map(singleBucketFolder),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "In Loss",
|
||||
tree: [
|
||||
{ name: "Compare", tree: groupedBucketCharts(loss, "In Loss") },
|
||||
...loss.map(singleBucketFolder),
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
*/
|
||||
|
||||
import { colors } from "../../utils/colors.js";
|
||||
import { createPriceRatioCharts, mapCohortsWithAll } from "../shared.js";
|
||||
import { createPriceRatioCharts, mapCohortsWithAll, priceRatioPercentilesTree } from "../shared.js";
|
||||
import { baseline, price } from "../series.js";
|
||||
import { Unit } from "../../utils/units.js";
|
||||
|
||||
@@ -53,26 +53,12 @@ export function createPricesSectionFull({ cohort, title }) {
|
||||
},
|
||||
{
|
||||
name: "Investor",
|
||||
tree: [
|
||||
{
|
||||
name: "Price",
|
||||
title: title("Investor Price"),
|
||||
top: [price({ metric: tree.realized.investor.price, name: "Investor", color })],
|
||||
},
|
||||
{
|
||||
name: "Ratio",
|
||||
title: title("Investor Price Ratio"),
|
||||
top: [price({ metric: tree.realized.investor.price, name: "Investor", color })],
|
||||
bottom: [
|
||||
baseline({
|
||||
metric: tree.realized.investor.price.ratio,
|
||||
name: "Ratio",
|
||||
unit: Unit.ratio,
|
||||
base: 1,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
tree: priceRatioPercentilesTree({
|
||||
pattern: tree.realized.investor.price,
|
||||
title: title("Investor Price"),
|
||||
legend: "Investor",
|
||||
color,
|
||||
}),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
/** UTXO Profitability section — range bands, cumulative profit/loss thresholds */
|
||||
|
||||
import { colors } from "../../utils/colors.js";
|
||||
import { entries } from "../../utils/array.js";
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { line, price } from "../series.js";
|
||||
import { brk } from "../../client.js";
|
||||
import { satsBtcUsd } from "../shared.js";
|
||||
|
||||
/**
|
||||
* @param {{ name: string, color: Color, pattern: RealizedSupplyPattern }[]} list
|
||||
* @param {string} titlePrefix
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function bucketCharts(list, titlePrefix) {
|
||||
return [
|
||||
{
|
||||
name: "Supply",
|
||||
title: `${titlePrefix}: Supply`,
|
||||
bottom: list.flatMap(({ name, color, pattern }) =>
|
||||
satsBtcUsd({ pattern: pattern.supply, name, color }),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Realized Cap",
|
||||
title: `${titlePrefix}: Realized Cap`,
|
||||
bottom: list.map(({ name, color, pattern }) =>
|
||||
line({ metric: pattern.realizedCap, name, color, unit: Unit.usd }),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Realized Price",
|
||||
title: `${titlePrefix}: Realized Price`,
|
||||
top: list.map(({ name, color, pattern }) =>
|
||||
price({ metric: pattern.realizedPrice, name, color }),
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createUtxoProfitabilitySection() {
|
||||
const { range, profit, loss } = brk.metrics.cohorts.utxo.profitability;
|
||||
const {
|
||||
PROFITABILITY_RANGE_NAMES,
|
||||
PROFIT_NAMES,
|
||||
LOSS_NAMES,
|
||||
} = brk;
|
||||
|
||||
const rangeList = entries(PROFITABILITY_RANGE_NAMES).map(
|
||||
([key, names], i, arr) => ({
|
||||
name: names.short,
|
||||
color: colors.at(i, arr.length),
|
||||
pattern: range[key],
|
||||
}),
|
||||
);
|
||||
|
||||
const profitList = entries(PROFIT_NAMES).map(([key, names], i, arr) => ({
|
||||
name: names.short,
|
||||
color: colors.at(i, arr.length),
|
||||
pattern: profit[key],
|
||||
}));
|
||||
|
||||
const lossList = entries(LOSS_NAMES).map(([key, names], i, arr) => ({
|
||||
name: names.short,
|
||||
color: colors.at(i, arr.length),
|
||||
pattern: loss[key],
|
||||
}));
|
||||
|
||||
return {
|
||||
name: "UTXO Profitability",
|
||||
tree: [
|
||||
{
|
||||
name: "Range",
|
||||
tree: bucketCharts(rangeList, "Profitability Range"),
|
||||
},
|
||||
{
|
||||
name: "In Profit",
|
||||
tree: bucketCharts(profitList, "In Profit"),
|
||||
},
|
||||
{
|
||||
name: "In Loss",
|
||||
tree: bucketCharts(lossList, "In Loss"),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
percentRatioBaseline,
|
||||
ROLLING_WINDOWS,
|
||||
} from "./series.js";
|
||||
import { simplePriceRatioTree } from "./shared.js";
|
||||
import { periodIdToName } from "./utils.js";
|
||||
|
||||
/**
|
||||
@@ -68,26 +69,12 @@ function createMaSubSection(label, averages) {
|
||||
/** @param {MaPeriod} a */
|
||||
const toFolder = (a) => ({
|
||||
name: periodIdToName(a.id, true),
|
||||
tree: [
|
||||
{
|
||||
name: "Price",
|
||||
title: `${periodIdToName(a.id, true)} ${label}`,
|
||||
top: [price({ metric: a.ratio, name: "average", color: a.color })],
|
||||
},
|
||||
{
|
||||
name: "Ratio",
|
||||
title: `${periodIdToName(a.id, true)} ${label} Ratio`,
|
||||
top: [price({ metric: a.ratio, name: "average", color: a.color })],
|
||||
bottom: [
|
||||
baseline({
|
||||
metric: a.ratio.ratio,
|
||||
name: "Ratio",
|
||||
color: a.color,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
tree: simplePriceRatioTree({
|
||||
pattern: a.ratio,
|
||||
title: `${periodIdToName(a.id, true)} ${label}`,
|
||||
legend: "average",
|
||||
color: a.color,
|
||||
}),
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
@@ -19,8 +19,8 @@ import {
|
||||
createGroupedCohortFolderBasicWithoutMarketCap,
|
||||
createGroupedCohortFolderAddress,
|
||||
createGroupedAddressCohortFolder,
|
||||
createUtxoProfitabilitySection,
|
||||
} from "./distribution/index.js";
|
||||
import { createUtxoProfitabilitySection } from "./distribution/utxo-profitability.js";
|
||||
import { createMarketSection } from "./market.js";
|
||||
import { createNetworkSection } from "./network.js";
|
||||
import { createMiningSection } from "./mining.js";
|
||||
@@ -53,6 +53,9 @@ export function createPartialOptions() {
|
||||
typeAddressable,
|
||||
typeOther,
|
||||
class: class_,
|
||||
profitabilityRange,
|
||||
profitabilityProfit,
|
||||
profitabilityLoss,
|
||||
} = buildCohortData();
|
||||
|
||||
return [
|
||||
@@ -92,7 +95,7 @@ export function createPartialOptions() {
|
||||
|
||||
// Ages cohorts
|
||||
{
|
||||
name: "UTXO Ages",
|
||||
name: "UTXO Age",
|
||||
tree: [
|
||||
// Younger Than (< X old)
|
||||
{
|
||||
@@ -138,7 +141,7 @@ export function createPartialOptions() {
|
||||
|
||||
// Sizes cohorts (UTXO size)
|
||||
{
|
||||
name: "UTXO Sizes",
|
||||
name: "UTXO Size",
|
||||
tree: [
|
||||
// Less Than (< X sats)
|
||||
{
|
||||
@@ -184,7 +187,7 @@ export function createPartialOptions() {
|
||||
|
||||
// Balances cohorts (Address balance)
|
||||
{
|
||||
name: "Address Balances",
|
||||
name: "Address Balance",
|
||||
tree: [
|
||||
// Less Than (< X sats)
|
||||
{
|
||||
@@ -230,11 +233,11 @@ export function createPartialOptions() {
|
||||
|
||||
// Script Types - addressable types have addrCount, others don't
|
||||
{
|
||||
name: "Script Types",
|
||||
name: "Script Type",
|
||||
tree: [
|
||||
createGroupedCohortFolderAddress({
|
||||
name: "Compare",
|
||||
title: "Script Types",
|
||||
title: "Script Type",
|
||||
list: typeAddressable,
|
||||
all: cohortAll,
|
||||
}),
|
||||
@@ -245,11 +248,11 @@ export function createPartialOptions() {
|
||||
|
||||
// Epochs
|
||||
{
|
||||
name: "Epochs",
|
||||
name: "Epoch",
|
||||
tree: [
|
||||
createGroupedCohortFolderWithAdjusted({
|
||||
name: "Compare",
|
||||
title: "Epochs",
|
||||
title: "Epoch",
|
||||
list: epoch,
|
||||
all: cohortAll,
|
||||
}),
|
||||
@@ -257,13 +260,13 @@ export function createPartialOptions() {
|
||||
],
|
||||
},
|
||||
|
||||
// Years
|
||||
// Classes
|
||||
{
|
||||
name: "Years",
|
||||
name: "Class",
|
||||
tree: [
|
||||
createGroupedCohortFolderWithAdjusted({
|
||||
name: "Compare",
|
||||
title: "Years",
|
||||
title: "Class",
|
||||
list: class_,
|
||||
all: cohortAll,
|
||||
}),
|
||||
@@ -272,7 +275,11 @@ export function createPartialOptions() {
|
||||
},
|
||||
|
||||
// UTXO Profitability bands
|
||||
createUtxoProfitabilitySection(),
|
||||
createUtxoProfitabilitySection({
|
||||
range: profitabilityRange,
|
||||
profit: profitabilityProfit,
|
||||
loss: profitabilityLoss,
|
||||
}),
|
||||
],
|
||||
},
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/** Shared helpers for options */
|
||||
|
||||
import { Unit } from "../utils/units.js";
|
||||
import { line, baseline, price } from "./series.js";
|
||||
import { line, baseline, price, ROLLING_WINDOWS } from "./series.js";
|
||||
import { priceLine, priceLines } from "./constants.js";
|
||||
import { colors } from "../utils/colors.js";
|
||||
|
||||
@@ -234,6 +234,159 @@ export function satsBtcUsdRolling({ pattern, name, color, defaultActive }) {
|
||||
return satsBtcUsd({ pattern, name, color, defaultActive });
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a full Sum / Rolling / Cumulative tree from a FullValuePattern
|
||||
* @param {Object} args
|
||||
* @param {FullValuePattern} args.pattern
|
||||
* @param {string} args.name
|
||||
* @param {string} args.title
|
||||
* @param {Color} [args.color]
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
export function satsBtcUsdFullTree({ pattern, name, title, color }) {
|
||||
return [
|
||||
{
|
||||
name: "Sum",
|
||||
title,
|
||||
bottom: satsBtcUsd({ pattern: pattern.base, name, color }),
|
||||
},
|
||||
{
|
||||
name: "Rolling",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: `${title} Rolling Sum`,
|
||||
bottom: ROLLING_WINDOWS.flatMap((w) =>
|
||||
satsBtcUsd({ pattern: pattern.sum[w.key], name: w.name, color: w.color }),
|
||||
),
|
||||
},
|
||||
...ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: `${title} ${w.name} Rolling Sum`,
|
||||
bottom: satsBtcUsd({ pattern: pattern.sum[w.key], name: w.name, color: w.color }),
|
||||
})),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: `${title} (Total)`,
|
||||
bottom: satsBtcUsd({ pattern: pattern.cumulative, name: "all-time", color }),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Price + Ratio charts from a simple price pattern (BpsCentsRatioSatsUsdPattern)
|
||||
* @param {Object} args
|
||||
* @param {AnyPricePattern & { ratio: AnyMetricPattern }} args.pattern
|
||||
* @param {string} args.title
|
||||
* @param {string} args.legend
|
||||
* @param {Color} [args.color]
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
export function simplePriceRatioTree({ pattern, title, legend, color }) {
|
||||
return [
|
||||
{
|
||||
name: "Price",
|
||||
title,
|
||||
top: [price({ metric: pattern, name: legend, color })],
|
||||
},
|
||||
{
|
||||
name: "Ratio",
|
||||
title: `${title} Ratio`,
|
||||
top: [price({ metric: pattern, name: legend, color })],
|
||||
bottom: [
|
||||
baseline({ metric: pattern.ratio, name: "Ratio", unit: Unit.ratio, base: 1 }),
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Price + Ratio charts with percentile bands (no SMAs/z-scores)
|
||||
* @param {Object} args
|
||||
* @param {PriceRatioPercentilesPattern} args.pattern
|
||||
* @param {string} args.title
|
||||
* @param {string} args.legend
|
||||
* @param {Color} [args.color]
|
||||
* @param {FetchedPriceSeriesBlueprint[]} [args.priceReferences]
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
export function priceRatioPercentilesTree({ pattern, title, legend, color, priceReferences }) {
|
||||
const p = pattern.percentiles;
|
||||
const pctUsd = [
|
||||
{ name: "pct95", prop: p.pct95.price, color: colors.ratioPct._95 },
|
||||
{ name: "pct5", prop: p.pct5.price, color: colors.ratioPct._5 },
|
||||
{ name: "pct98", prop: p.pct98.price, color: colors.ratioPct._98 },
|
||||
{ name: "pct2", prop: p.pct2.price, color: colors.ratioPct._2 },
|
||||
{ name: "pct99", prop: p.pct99.price, color: colors.ratioPct._99 },
|
||||
{ name: "pct1", prop: p.pct1.price, color: colors.ratioPct._1 },
|
||||
];
|
||||
const pctRatio = [
|
||||
{ name: "pct95", prop: p.pct95.ratio, color: colors.ratioPct._95 },
|
||||
{ name: "pct5", prop: p.pct5.ratio, color: colors.ratioPct._5 },
|
||||
{ name: "pct98", prop: p.pct98.ratio, color: colors.ratioPct._98 },
|
||||
{ name: "pct2", prop: p.pct2.ratio, color: colors.ratioPct._2 },
|
||||
{ name: "pct99", prop: p.pct99.ratio, color: colors.ratioPct._99 },
|
||||
{ name: "pct1", prop: p.pct1.ratio, color: colors.ratioPct._1 },
|
||||
];
|
||||
return [
|
||||
{
|
||||
name: "Price",
|
||||
title,
|
||||
top: [
|
||||
price({ metric: pattern, name: legend, color }),
|
||||
...(priceReferences ?? []),
|
||||
...pctUsd.map(({ name, prop, color }) =>
|
||||
price({ metric: prop, name, color, defaultActive: false, options: { lineStyle: 1 } }),
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Ratio",
|
||||
title: `${title} Ratio`,
|
||||
top: [
|
||||
price({ metric: pattern, name: legend, color }),
|
||||
...pctUsd.map(({ name, prop, color }) =>
|
||||
price({ metric: prop, name, color, defaultActive: false, options: { lineStyle: 1 } }),
|
||||
),
|
||||
],
|
||||
bottom: [
|
||||
baseline({ metric: pattern.ratio, name: "Ratio", unit: Unit.ratio, base: 1 }),
|
||||
...pctRatio.map(({ name, prop, color }) =>
|
||||
line({ metric: prop, name, color, defaultActive: false, unit: Unit.ratio, options: { lineStyle: 1 } }),
|
||||
),
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create grouped Price + Ratio charts overlaying multiple series
|
||||
* @param {Object} args
|
||||
* @param {{ name: string, color?: Color, pattern: AnyPricePattern & { ratio: AnyMetricPattern } }[]} args.list
|
||||
* @param {string} args.title
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
export function groupedSimplePriceRatioTree({ list, title }) {
|
||||
return [
|
||||
{
|
||||
name: "Price",
|
||||
title,
|
||||
top: list.map(({ name, color, pattern }) =>
|
||||
price({ metric: pattern, name, color }),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Ratio",
|
||||
title: `${title} Ratio`,
|
||||
bottom: list.map(({ name, color, pattern }) =>
|
||||
baseline({ metric: pattern.ratio, name, color, unit: Unit.ratio, base: 1 }),
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create coinbase/subsidy/fee rolling sum series from separate sources
|
||||
* @param {Object} args
|
||||
|
||||
@@ -234,7 +234,7 @@
|
||||
* @property {AgeRangePattern} tree
|
||||
*
|
||||
* Age range cohort with matured supply
|
||||
* @typedef {CohortAgeRange & { matured: AnyValuePattern }} CohortAgeRangeWithMatured
|
||||
* @typedef {CohortAgeRange & { matured: FullValuePattern }} CohortAgeRangeWithMatured
|
||||
*
|
||||
* Basic cohort WITH RelToMarketCap (geAmount.*, ltAmount.*)
|
||||
* @typedef {Object} CohortBasicWithMarketCap
|
||||
|
||||
@@ -26,6 +26,14 @@ function walkMetrics(node, map, path) {
|
||||
for (const [key, value] of Object.entries(node)) {
|
||||
const kn = key.toLowerCase();
|
||||
if (
|
||||
key === "lookback" ||
|
||||
key === "cumulativeMarketCap" ||
|
||||
key === "sd24h" ||
|
||||
key === "spot" ||
|
||||
key === "ohlc" ||
|
||||
key === "state" ||
|
||||
key === "emaSlow" ||
|
||||
key === "emaFast" ||
|
||||
key.endsWith("Raw") ||
|
||||
key.endsWith("Cents") ||
|
||||
key.endsWith("State") ||
|
||||
|
||||
Reference in New Issue
Block a user