mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-05-30 03:19:28 -07:00
website: snapshot
This commit is contained in:
@@ -189,6 +189,13 @@ export function createChainSection(ctx) {
|
||||
unit: Unit.count,
|
||||
options: { lineStyle: 4 },
|
||||
}),
|
||||
line({
|
||||
metric: blocks.count._24hBlockCount,
|
||||
name: "24h sum",
|
||||
color: colors.pink,
|
||||
unit: Unit.count,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.count._1wBlockCount,
|
||||
name: "1w sum",
|
||||
@@ -199,7 +206,7 @@ export function createChainSection(ctx) {
|
||||
line({
|
||||
metric: blocks.count._1mBlockCount,
|
||||
name: "1m sum",
|
||||
color: colors.pink,
|
||||
color: colors.orange,
|
||||
unit: Unit.count,
|
||||
defaultActive: false,
|
||||
}),
|
||||
@@ -225,10 +232,36 @@ export function createChainSection(ctx) {
|
||||
title: "Block Size",
|
||||
bottom: [
|
||||
...fromSizePattern(blocks.size, Unit.bytes),
|
||||
line({
|
||||
metric: blocks.totalSize,
|
||||
name: "total",
|
||||
color: colors.purple,
|
||||
unit: Unit.bytes,
|
||||
defaultActive: false,
|
||||
}),
|
||||
...fromFullnessPattern(blocks.vbytes, Unit.vb),
|
||||
...fromFullnessPattern(blocks.weight, Unit.wu),
|
||||
line({
|
||||
metric: blocks.weight.sum,
|
||||
name: "sum",
|
||||
color: colors.stat.sum,
|
||||
unit: Unit.wu,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.weight.cumulative,
|
||||
name: "cumulative",
|
||||
color: colors.stat.cumulative,
|
||||
unit: Unit.wu,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Fullness",
|
||||
title: "Block Fullness",
|
||||
bottom: fromFullnessPattern(blocks.fullness, Unit.percentage),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -380,11 +413,6 @@ export function createChainSection(ctx) {
|
||||
title: "Output Count",
|
||||
bottom: [...fromSizePattern(outputs.count.totalCount, Unit.count)],
|
||||
},
|
||||
{
|
||||
name: "OP_RETURN",
|
||||
title: "OP_RETURN Outputs",
|
||||
bottom: fromFullnessPattern(scripts.count.opreturn, Unit.count),
|
||||
},
|
||||
{
|
||||
name: "Speed",
|
||||
title: "Outputs Per Second",
|
||||
@@ -416,6 +444,60 @@ export function createChainSection(ctx) {
|
||||
],
|
||||
},
|
||||
|
||||
// Scripts
|
||||
{
|
||||
name: "Scripts",
|
||||
tree: [
|
||||
{
|
||||
name: "Count",
|
||||
tree: [
|
||||
{ name: "P2PKH", title: "P2PKH Output Count", bottom: fromDollarsPattern(scripts.count.p2pkh, Unit.count) },
|
||||
{ name: "P2SH", title: "P2SH Output Count", bottom: fromDollarsPattern(scripts.count.p2sh, Unit.count) },
|
||||
{ name: "P2WPKH", title: "P2WPKH Output Count", bottom: fromDollarsPattern(scripts.count.p2wpkh, Unit.count) },
|
||||
{ name: "P2WSH", title: "P2WSH Output Count", bottom: fromDollarsPattern(scripts.count.p2wsh, Unit.count) },
|
||||
{ name: "P2TR", title: "P2TR Output Count", bottom: fromDollarsPattern(scripts.count.p2tr, Unit.count) },
|
||||
{ name: "P2PK33", title: "P2PK33 Output Count", bottom: fromDollarsPattern(scripts.count.p2pk33, Unit.count) },
|
||||
{ name: "P2PK65", title: "P2PK65 Output Count", bottom: fromDollarsPattern(scripts.count.p2pk65, Unit.count) },
|
||||
{ name: "P2MS", title: "P2MS Output Count", bottom: fromDollarsPattern(scripts.count.p2ms, Unit.count) },
|
||||
{ name: "P2A", title: "P2A Output Count", bottom: fromDollarsPattern(scripts.count.p2a, Unit.count) },
|
||||
{ name: "OP_RETURN", title: "OP_RETURN Output Count", bottom: fromDollarsPattern(scripts.count.opreturn, Unit.count) },
|
||||
{ name: "SegWit", title: "SegWit Output Count", bottom: fromDollarsPattern(scripts.count.segwit, Unit.count) },
|
||||
{ name: "Empty", title: "Empty Output Count", bottom: fromDollarsPattern(scripts.count.emptyoutput, Unit.count) },
|
||||
{ name: "Unknown", title: "Unknown Output Count", bottom: fromDollarsPattern(scripts.count.unknownoutput, Unit.count) },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Adoption",
|
||||
tree: [
|
||||
{
|
||||
name: "SegWit",
|
||||
title: "SegWit Adoption",
|
||||
bottom: [
|
||||
line({ metric: scripts.count.segwitAdoption.base, name: "base", unit: Unit.percentage }),
|
||||
line({ metric: scripts.count.segwitAdoption.sum, name: "sum", color: colors.stat.sum, unit: Unit.percentage }),
|
||||
line({ metric: scripts.count.segwitAdoption.cumulative, name: "cumulative", color: colors.stat.cumulative, unit: Unit.percentage, defaultActive: false }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Taproot",
|
||||
title: "Taproot Adoption",
|
||||
bottom: [
|
||||
line({ metric: scripts.count.taprootAdoption.base, name: "base", unit: Unit.percentage }),
|
||||
line({ metric: scripts.count.taprootAdoption.sum, name: "sum", color: colors.stat.sum, unit: Unit.percentage }),
|
||||
line({ metric: scripts.count.taprootAdoption.cumulative, name: "cumulative", color: colors.stat.cumulative, unit: Unit.percentage, defaultActive: false }),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Value",
|
||||
tree: [
|
||||
{ name: "OP_RETURN", title: "OP_RETURN Value", bottom: fromCoinbasePattern(scripts.value.opreturn) },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// Supply
|
||||
{
|
||||
name: "Supply",
|
||||
@@ -456,7 +538,10 @@ export function createChainSection(ctx) {
|
||||
{
|
||||
name: "Coinbase",
|
||||
title: "Coinbase Rewards",
|
||||
bottom: fromCoinbasePattern(blocks.rewards.coinbase),
|
||||
bottom: [
|
||||
...fromCoinbasePattern(blocks.rewards.coinbase),
|
||||
...satsBtcUsd(blocks.rewards._24hCoinbaseSum, "24h sum", colors.pink, { defaultActive: false }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Subsidy",
|
||||
@@ -470,6 +555,13 @@ export function createChainSection(ctx) {
|
||||
unit: Unit.percentage,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.rewards.subsidyUsd1ySma,
|
||||
name: "1y SMA",
|
||||
color: colors.lime,
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -557,6 +649,63 @@ export function createChainSection(ctx) {
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Empty by Type",
|
||||
title: "Empty Address Count by Type",
|
||||
bottom: [
|
||||
line({
|
||||
metric: distribution.emptyAddrCount.p2pkh,
|
||||
name: "P2PKH",
|
||||
color: colors.orange,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
line({
|
||||
metric: distribution.emptyAddrCount.p2sh,
|
||||
name: "P2SH",
|
||||
color: colors.yellow,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
line({
|
||||
metric: distribution.emptyAddrCount.p2wpkh,
|
||||
name: "P2WPKH",
|
||||
color: colors.green,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
line({
|
||||
metric: distribution.emptyAddrCount.p2wsh,
|
||||
name: "P2WSH",
|
||||
color: colors.teal,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
line({
|
||||
metric: distribution.emptyAddrCount.p2tr,
|
||||
name: "P2TR",
|
||||
color: colors.purple,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
line({
|
||||
metric: distribution.emptyAddrCount.p2pk65,
|
||||
name: "P2PK65",
|
||||
color: colors.pink,
|
||||
unit: Unit.count,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: distribution.emptyAddrCount.p2pk33,
|
||||
name: "P2PK33",
|
||||
color: colors.red,
|
||||
unit: Unit.count,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: distribution.emptyAddrCount.p2a,
|
||||
name: "P2A",
|
||||
color: colors.blue,
|
||||
unit: Unit.count,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "By Type",
|
||||
title: "Address Count by Type",
|
||||
@@ -678,6 +827,12 @@ export function createChainSection(ctx) {
|
||||
name: "Difficulty",
|
||||
unit: Unit.difficulty,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.difficulty.epoch,
|
||||
name: "Epoch",
|
||||
color: colors.teal,
|
||||
unit: Unit.epoch,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.difficulty.blocksBeforeNextAdjustment,
|
||||
name: "before next",
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
satsBtcUsd,
|
||||
createRatioChart,
|
||||
createZScoresFolder,
|
||||
formatCohortTitle,
|
||||
} from "./shared.js";
|
||||
|
||||
/**
|
||||
@@ -27,7 +28,7 @@ function createCointimePriceWithRatioOptions(
|
||||
title,
|
||||
top: [line({ metric: price, name: legend, color, unit: Unit.usd })],
|
||||
},
|
||||
createRatioChart(ctx, { title, price, ratio, color }),
|
||||
createRatioChart(ctx, { title: formatCohortTitle(title), price, ratio, color }),
|
||||
createZScoresFolder(ctx, { title, legend, price, ratio, color }),
|
||||
];
|
||||
}
|
||||
@@ -40,7 +41,15 @@ function createCointimePriceWithRatioOptions(
|
||||
export function createCointimeSection(ctx) {
|
||||
const { colors, brk } = ctx;
|
||||
const { cointime, distribution, supply } = brk.metrics;
|
||||
const { pricing, cap, activity, supply: cointimeSupply, adjusted, reserveRisk, value } = cointime;
|
||||
const {
|
||||
pricing,
|
||||
cap,
|
||||
activity,
|
||||
supply: cointimeSupply,
|
||||
adjusted,
|
||||
reserveRisk,
|
||||
value,
|
||||
} = cointime;
|
||||
const { all } = distribution.utxoCohorts;
|
||||
|
||||
// Cointime prices data
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import {
|
||||
fromBlockCount,
|
||||
fromBitcoin,
|
||||
fromBlockSize,
|
||||
fromSizePattern,
|
||||
fromFullnessPattern,
|
||||
fromDollarsPattern,
|
||||
@@ -32,15 +29,6 @@ export function createContext({ brk }) {
|
||||
colors,
|
||||
brk,
|
||||
|
||||
/** @type {OmitFirstArg<typeof fromBlockCount>} */
|
||||
fromBlockCount: (pattern, title, color) =>
|
||||
fromBlockCount(colors, pattern, title, color),
|
||||
/** @type {OmitFirstArg<typeof fromBitcoin>} */
|
||||
fromBitcoin: (pattern, title, color) =>
|
||||
fromBitcoin(colors, pattern, title, color),
|
||||
/** @type {OmitFirstArg<typeof fromBlockSize>} */
|
||||
fromBlockSize: (pattern, title, color) =>
|
||||
fromBlockSize(colors, pattern, title, color),
|
||||
/** @type {OmitFirstArg<typeof fromSizePattern>} */
|
||||
fromSizePattern: (pattern, unit, title) =>
|
||||
fromSizePattern(colors, pattern, unit, title),
|
||||
|
||||
@@ -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
@@ -2,7 +2,7 @@
|
||||
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { line } from "../series.js";
|
||||
import { createRatioChart, createZScoresFolder } from "../shared.js";
|
||||
import { createRatioChart, createZScoresFolder, formatCohortTitle } from "../shared.js";
|
||||
import { periodIdToName } from "./utils.js";
|
||||
|
||||
/**
|
||||
@@ -89,7 +89,7 @@ export function createPriceWithRatioOptions(
|
||||
title,
|
||||
top: [line({ metric: priceMetric, name: legend, color, unit: Unit.usd })],
|
||||
},
|
||||
createRatioChart(ctx, { title, price: priceMetric, ratio, color }),
|
||||
createRatioChart(ctx, { title: formatCohortTitle(title), price: priceMetric, ratio, color }),
|
||||
createZScoresFolder(ctx, {
|
||||
title,
|
||||
legend,
|
||||
|
||||
@@ -47,83 +47,104 @@ export function createInvestingSection(ctx, { dca, lookback, returns }) {
|
||||
const { colors } = ctx;
|
||||
const dcaClasses = buildDcaClasses(colors, dca);
|
||||
|
||||
/**
|
||||
* @param {string} id
|
||||
* @param {ShortPeriodKey} key
|
||||
*/
|
||||
const createPeriodTree = (id, key) => {
|
||||
const name = periodIdToName(id, true);
|
||||
return {
|
||||
name,
|
||||
tree: [
|
||||
{
|
||||
name: "Cost basis",
|
||||
title: `${name} Cost Basis`,
|
||||
top: [
|
||||
line({ metric: dca.periodAveragePrice[key], name: "DCA", color: colors.green, unit: Unit.usd }),
|
||||
line({ metric: lookback[key], name: "Lump sum", color: colors.orange, unit: Unit.usd }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Returns",
|
||||
title: `${name} Returns`,
|
||||
bottom: [
|
||||
baseline({ metric: dca.periodReturns[key], name: "DCA", unit: Unit.percentage }),
|
||||
baseline({ metric: returns.priceReturns[key], name: "Lump sum", color: [colors.cyan, colors.orange], unit: Unit.percentage }),
|
||||
priceLine({ ctx, unit: Unit.percentage }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Stack",
|
||||
title: `${name} Stack`,
|
||||
bottom: [
|
||||
...satsBtcUsd(dca.periodStack[key], "DCA", colors.green),
|
||||
...satsBtcUsd(dca.periodLumpSumStack[key], "Lump sum", colors.orange),
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} id
|
||||
* @param {LongPeriodKey} key
|
||||
*/
|
||||
const createPeriodTreeWithCagr = (id, key) => {
|
||||
const name = periodIdToName(id, true);
|
||||
return {
|
||||
name,
|
||||
tree: [
|
||||
{
|
||||
name: "Cost basis",
|
||||
title: `${name} Cost Basis`,
|
||||
top: [
|
||||
line({ metric: dca.periodAveragePrice[key], name: "DCA", color: colors.green, unit: Unit.usd }),
|
||||
line({ metric: lookback[key], name: "Lump sum", color: colors.orange, unit: Unit.usd }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Returns",
|
||||
title: `${name} Returns`,
|
||||
bottom: [
|
||||
baseline({ metric: dca.periodReturns[key], name: "DCA", unit: Unit.percentage }),
|
||||
baseline({ metric: returns.priceReturns[key], name: "Lump sum", color: [colors.cyan, colors.orange], unit: Unit.percentage }),
|
||||
line({ metric: dca.periodCagr[key], name: "DCA CAGR", color: colors.purple, unit: Unit.percentage, defaultActive: false }),
|
||||
line({ metric: returns.cagr[key], name: "Lump sum CAGR", color: colors.indigo, unit: Unit.percentage, defaultActive: false }),
|
||||
priceLine({ ctx, unit: Unit.percentage }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Stack",
|
||||
title: `${name} Stack`,
|
||||
bottom: [
|
||||
...satsBtcUsd(dca.periodStack[key], "DCA", colors.green),
|
||||
...satsBtcUsd(dca.periodLumpSumStack[key], "Lump sum", colors.orange),
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
name: "Investing",
|
||||
tree: [
|
||||
// DCA vs Lump sum
|
||||
{
|
||||
name: "DCA vs Lump sum",
|
||||
tree: /** @type {const} */ ([
|
||||
["1w", "_1w"],
|
||||
["1m", "_1m"],
|
||||
["3m", "_3m"],
|
||||
["6m", "_6m"],
|
||||
["1y", "_1y"],
|
||||
["2y", "_2y"],
|
||||
["3y", "_3y"],
|
||||
["4y", "_4y"],
|
||||
["5y", "_5y"],
|
||||
["6y", "_6y"],
|
||||
["8y", "_8y"],
|
||||
["10y", "_10y"],
|
||||
]).map(([id, key]) => {
|
||||
const name = periodIdToName(id, true);
|
||||
const priceAgo = lookback[key];
|
||||
const priceReturns = returns.priceReturns[key];
|
||||
const dcaCostBasis = dca.periodAveragePrice[key];
|
||||
const dcaReturns = dca.periodReturns[key];
|
||||
const dcaStack = dca.periodStack[key];
|
||||
const lumpSumStack = dca.periodLumpSumStack[key];
|
||||
return {
|
||||
name,
|
||||
tree: [
|
||||
{
|
||||
name: "Cost basis",
|
||||
title: `${name} Cost Basis`,
|
||||
top: [
|
||||
line({
|
||||
metric: dcaCostBasis,
|
||||
name: "DCA",
|
||||
color: colors.green,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: priceAgo,
|
||||
name: "Lump sum",
|
||||
color: colors.orange,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Returns",
|
||||
title: `${name} Returns`,
|
||||
bottom: [
|
||||
baseline({
|
||||
metric: dcaReturns,
|
||||
name: "DCA",
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
baseline({
|
||||
metric: priceReturns,
|
||||
name: "Lump sum",
|
||||
color: [colors.cyan, colors.orange],
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
priceLine({ ctx, unit: Unit.percentage }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Stack",
|
||||
title: `${name} Stack`,
|
||||
bottom: [
|
||||
...satsBtcUsd(dcaStack, "DCA", colors.green),
|
||||
...satsBtcUsd(lumpSumStack, "Lump sum", colors.orange),
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}),
|
||||
tree: [
|
||||
createPeriodTree("1w", "_1w"),
|
||||
createPeriodTree("1m", "_1m"),
|
||||
createPeriodTree("3m", "_3m"),
|
||||
createPeriodTree("6m", "_6m"),
|
||||
createPeriodTree("1y", "_1y"),
|
||||
createPeriodTreeWithCagr("2y", "_2y"),
|
||||
createPeriodTreeWithCagr("3y", "_3y"),
|
||||
createPeriodTreeWithCagr("4y", "_4y"),
|
||||
createPeriodTreeWithCagr("5y", "_5y"),
|
||||
createPeriodTreeWithCagr("6y", "_6y"),
|
||||
createPeriodTreeWithCagr("8y", "_8y"),
|
||||
createPeriodTreeWithCagr("10y", "_10y"),
|
||||
],
|
||||
},
|
||||
|
||||
// DCA classes
|
||||
|
||||
@@ -6,8 +6,7 @@ import {
|
||||
createCohortFolderAll,
|
||||
createCohortFolderFull,
|
||||
createCohortFolderWithAdjusted,
|
||||
createCohortFolderWithPercentiles,
|
||||
createCohortFolderLongTerm,
|
||||
createCohortFolderWithNupl,
|
||||
createCohortFolderAgeRange,
|
||||
createCohortFolderBasicWithMarketCap,
|
||||
createCohortFolderBasicWithoutMarketCap,
|
||||
@@ -99,19 +98,19 @@ export function createPartialOptions({ brk }) {
|
||||
// All UTXOs - CohortAll (adjustedSopr + percentiles but no RelToMarketCap)
|
||||
createCohortFolderAll(ctx, cohortAll),
|
||||
|
||||
// Terms (STH/LTH) - Short is Full, Long is LongTerm
|
||||
// Terms (STH/LTH) - Short is Full, Long has nupl
|
||||
{
|
||||
name: "Terms",
|
||||
tree: [
|
||||
// Compare folder uses WithPercentiles (common capabilities)
|
||||
createCohortFolderWithPercentiles(ctx, {
|
||||
// Compare folder - both have nupl + percentiles
|
||||
createCohortFolderWithNupl(ctx, {
|
||||
name: "Compare",
|
||||
title: "Term",
|
||||
list: [termShort, termLong],
|
||||
}),
|
||||
// Individual cohorts with their specific capabilities
|
||||
createCohortFolderFull(ctx, termShort),
|
||||
createCohortFolderLongTerm(ctx, termLong),
|
||||
createCohortFolderWithNupl(ctx, termLong),
|
||||
],
|
||||
},
|
||||
|
||||
|
||||
@@ -2,6 +2,34 @@
|
||||
|
||||
import { Unit } from "../utils/units.js";
|
||||
|
||||
// ============================================================================
|
||||
// Shared percentile helper
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Create percentile series (max/min/median/pct75/pct25/pct90/pct10) from any stats pattern
|
||||
* Works with FullnessPattern, FeeRatePattern, AnyStatsPattern, DollarsPattern, etc.
|
||||
* @param {Colors} colors
|
||||
* @param {FullnessPattern<any> | FeeRatePattern<any> | AnyStatsPattern | DollarsPattern<any>} pattern
|
||||
* @param {Unit} unit
|
||||
* @param {string} title
|
||||
* @param {{ type?: "Dots" }} [options]
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
function percentileSeries(colors, pattern, unit, title, { type } = {}) {
|
||||
const { stat } = colors;
|
||||
const base = { unit, defaultActive: false };
|
||||
return [
|
||||
{ type, metric: pattern.max, title: `${title} max`.trim(), color: stat.max, ...base },
|
||||
{ type, metric: pattern.min, title: `${title} min`.trim(), color: stat.min, ...base },
|
||||
{ type, metric: pattern.median, title: `${title} median`.trim(), color: stat.median, ...base },
|
||||
{ type, metric: pattern.pct75, title: `${title} pct75`.trim(), color: stat.pct75, ...base },
|
||||
{ type, metric: pattern.pct25, title: `${title} pct25`.trim(), color: stat.pct25, ...base },
|
||||
{ type, metric: pattern.pct90, title: `${title} pct90`.trim(), color: stat.pct90, ...base },
|
||||
{ type, metric: pattern.pct10, title: `${title} pct10`.trim(), color: stat.pct10, ...base },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Line series
|
||||
* @param {Object} args
|
||||
@@ -180,158 +208,6 @@ export function histogram({
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create series from a BlockCountPattern ({ base, sum, cumulative })
|
||||
* @param {Colors} colors
|
||||
* @param {BlockCountPattern<any>} pattern
|
||||
* @param {string} title
|
||||
* @param {Color} [color]
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function fromBlockCount(colors, pattern, title, color) {
|
||||
return [
|
||||
{ metric: pattern.sum, title, color: color ?? colors.default },
|
||||
{
|
||||
metric: pattern.cumulative,
|
||||
title: `${title} cumulative`,
|
||||
color: colors.stat.cumulative,
|
||||
defaultActive: false,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create series from a FullnessPattern ({ base, sum, cumulative, average, min, max, percentiles })
|
||||
* @param {Colors} colors
|
||||
* @param {FullnessPattern<any>} pattern
|
||||
* @param {string} title
|
||||
* @param {Color} [color]
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function fromBitcoin(colors, pattern, title, color) {
|
||||
const { stat } = colors;
|
||||
return [
|
||||
{ metric: pattern.base, title, color: color ?? colors.default },
|
||||
{
|
||||
metric: pattern.average,
|
||||
title: `${title} avg`,
|
||||
color: stat.avg,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.max,
|
||||
title: `${title} max`,
|
||||
color: stat.max,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.min,
|
||||
title: `${title} min`,
|
||||
color: stat.min,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.median,
|
||||
title: `${title} median`,
|
||||
color: stat.median,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.pct75,
|
||||
title: `${title} pct75`,
|
||||
color: stat.pct75,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.pct25,
|
||||
title: `${title} pct25`,
|
||||
color: stat.pct25,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.pct90,
|
||||
title: `${title} pct90`,
|
||||
color: stat.pct90,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.pct10,
|
||||
title: `${title} pct10`,
|
||||
color: stat.pct10,
|
||||
defaultActive: false,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create series from a SizePattern ({ sum, cumulative, average, min, max, percentiles })
|
||||
* @param {Colors} colors
|
||||
* @param {AnyStatsPattern} pattern
|
||||
* @param {string} title
|
||||
* @param {Color} [color]
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function fromBlockSize(colors, pattern, title, color) {
|
||||
const { stat } = colors;
|
||||
return [
|
||||
{ metric: pattern.sum, title, color: color ?? colors.default },
|
||||
{
|
||||
metric: pattern.average,
|
||||
title: `${title} avg`,
|
||||
color: stat.avg,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.cumulative,
|
||||
title: `${title} cumulative`,
|
||||
color: stat.cumulative,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.max,
|
||||
title: `${title} max`,
|
||||
color: stat.max,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.min,
|
||||
title: `${title} min`,
|
||||
color: stat.min,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.median,
|
||||
title: `${title} median`,
|
||||
color: stat.median,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.pct75,
|
||||
title: `${title} pct75`,
|
||||
color: stat.pct75,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.pct25,
|
||||
title: `${title} pct25`,
|
||||
color: stat.pct25,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.pct90,
|
||||
title: `${title} pct90`,
|
||||
color: stat.pct90,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.pct10,
|
||||
title: `${title} pct10`,
|
||||
color: stat.pct10,
|
||||
defaultActive: false,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create series from a SizePattern ({ average, sum, cumulative, min, max, percentiles })
|
||||
* @param {Colors} colors
|
||||
@@ -344,69 +220,9 @@ export function fromSizePattern(colors, pattern, unit, title = "") {
|
||||
const { stat } = colors;
|
||||
return [
|
||||
{ metric: pattern.average, title: `${title} avg`.trim(), unit },
|
||||
{
|
||||
metric: pattern.sum,
|
||||
title: `${title} sum`.trim(),
|
||||
color: stat.sum,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.cumulative,
|
||||
title: `${title} cumulative`.trim(),
|
||||
color: stat.cumulative,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.max,
|
||||
title: `${title} max`.trim(),
|
||||
color: stat.max,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.min,
|
||||
title: `${title} min`.trim(),
|
||||
color: stat.min,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.median,
|
||||
title: `${title} median`.trim(),
|
||||
color: stat.median,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.pct75,
|
||||
title: `${title} pct75`.trim(),
|
||||
color: stat.pct75,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.pct25,
|
||||
title: `${title} pct25`.trim(),
|
||||
color: stat.pct25,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.pct90,
|
||||
title: `${title} pct90`.trim(),
|
||||
color: stat.pct90,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.pct10,
|
||||
title: `${title} pct10`.trim(),
|
||||
color: stat.pct10,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
},
|
||||
{ metric: pattern.sum, title: `${title} sum`.trim(), color: stat.sum, unit, defaultActive: false },
|
||||
{ metric: pattern.cumulative, title: `${title} cumulative`.trim(), color: stat.cumulative, unit, defaultActive: false },
|
||||
...percentileSeries(colors, pattern, unit, title),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -422,61 +238,8 @@ export function fromFullnessPattern(colors, pattern, unit, title = "") {
|
||||
const { stat } = colors;
|
||||
return [
|
||||
{ metric: pattern.base, title: title || "base", unit },
|
||||
{
|
||||
metric: pattern.average,
|
||||
title: `${title} avg`.trim(),
|
||||
color: stat.avg,
|
||||
unit,
|
||||
},
|
||||
{
|
||||
metric: pattern.max,
|
||||
title: `${title} max`.trim(),
|
||||
color: stat.max,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.min,
|
||||
title: `${title} min`.trim(),
|
||||
color: stat.min,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.median,
|
||||
title: `${title} median`.trim(),
|
||||
color: stat.median,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.pct75,
|
||||
title: `${title} pct75`.trim(),
|
||||
color: stat.pct75,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.pct25,
|
||||
title: `${title} pct25`.trim(),
|
||||
color: stat.pct25,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.pct90,
|
||||
title: `${title} pct90`.trim(),
|
||||
color: stat.pct90,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.pct10,
|
||||
title: `${title} pct10`.trim(),
|
||||
color: stat.pct10,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
},
|
||||
{ metric: pattern.average, title: `${title} avg`.trim(), color: stat.avg, unit },
|
||||
...percentileSeries(colors, pattern, unit, title),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -492,75 +255,10 @@ export function fromDollarsPattern(colors, pattern, unit, title = "") {
|
||||
const { stat } = colors;
|
||||
return [
|
||||
{ metric: pattern.base, title: title || "base", unit },
|
||||
{
|
||||
metric: pattern.sum,
|
||||
title: `${title} sum`.trim(),
|
||||
color: stat.sum,
|
||||
unit,
|
||||
},
|
||||
{
|
||||
metric: pattern.cumulative,
|
||||
title: `${title} cumulative`.trim(),
|
||||
color: stat.cumulative,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.average,
|
||||
title: `${title} avg`.trim(),
|
||||
color: stat.avg,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.max,
|
||||
title: `${title} max`.trim(),
|
||||
color: stat.max,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.min,
|
||||
title: `${title} min`.trim(),
|
||||
color: stat.min,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.median,
|
||||
title: `${title} median`.trim(),
|
||||
color: stat.median,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.pct75,
|
||||
title: `${title} pct75`.trim(),
|
||||
color: stat.pct75,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.pct25,
|
||||
title: `${title} pct25`.trim(),
|
||||
color: stat.pct25,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.pct90,
|
||||
title: `${title} pct90`.trim(),
|
||||
color: stat.pct90,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.pct10,
|
||||
title: `${title} pct10`.trim(),
|
||||
color: stat.pct10,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
},
|
||||
{ metric: pattern.sum, title: `${title} sum`.trim(), color: stat.sum, unit },
|
||||
{ metric: pattern.cumulative, title: `${title} cumulative`.trim(), color: stat.cumulative, unit, defaultActive: false },
|
||||
{ metric: pattern.average, title: `${title} avg`.trim(), color: stat.avg, unit, defaultActive: false },
|
||||
...percentileSeries(colors, pattern, unit, title),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -573,85 +271,41 @@ export function fromDollarsPattern(colors, pattern, unit, title = "") {
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function fromFeeRatePattern(colors, pattern, unit, title = "") {
|
||||
const { stat } = colors;
|
||||
return [
|
||||
{
|
||||
type: "Dots",
|
||||
metric: pattern.average,
|
||||
title: `${title} avg`.trim(),
|
||||
unit,
|
||||
},
|
||||
{
|
||||
type: "Dots",
|
||||
metric: pattern.max,
|
||||
title: `${title} max`.trim(),
|
||||
color: stat.max,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
type: "Dots",
|
||||
metric: pattern.min,
|
||||
title: `${title} min`.trim(),
|
||||
color: stat.min,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
type: "Dots",
|
||||
metric: pattern.median,
|
||||
title: `${title} median`.trim(),
|
||||
color: stat.median,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
type: "Dots",
|
||||
metric: pattern.pct75,
|
||||
title: `${title} pct75`.trim(),
|
||||
color: stat.pct75,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
type: "Dots",
|
||||
metric: pattern.pct25,
|
||||
title: `${title} pct25`.trim(),
|
||||
color: stat.pct25,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
type: "Dots",
|
||||
metric: pattern.pct90,
|
||||
title: `${title} pct90`.trim(),
|
||||
color: stat.pct90,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
type: "Dots",
|
||||
metric: pattern.pct10,
|
||||
title: `${title} pct10`.trim(),
|
||||
color: stat.pct10,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
},
|
||||
{ type: "Dots", metric: pattern.average, title: `${title} avg`.trim(), unit },
|
||||
...percentileSeries(colors, pattern, unit, title, { type: "Dots" }),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create series from a CoinbasePattern ({ sats, bitcoin, dollars } each as FullnessPattern)
|
||||
* Create series from a pattern with sum and cumulative (fullness stats + sum + cumulative)
|
||||
* @param {Colors} colors
|
||||
* @param {FullnessPatternWithSumCumulative} pattern
|
||||
* @param {Unit} unit
|
||||
* @param {string} [title]
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function fromFullnessPatternWithSumCumulative(colors, pattern, unit, title = "") {
|
||||
const { stat } = colors;
|
||||
return [
|
||||
...fromFullnessPattern(colors, pattern, unit, title),
|
||||
{ metric: pattern.sum, title: `${title} sum`.trim(), color: stat.sum, unit },
|
||||
{ metric: pattern.cumulative, title: `${title} cumulative`.trim(), color: stat.cumulative, unit, defaultActive: false },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create series from a CoinbasePattern ({ sats, bitcoin, dollars } each with stats + sum + cumulative)
|
||||
* @param {Colors} colors
|
||||
* @param {CoinbasePattern} pattern
|
||||
* @param {string} [title]
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function fromCoinbasePattern(colors, pattern, title) {
|
||||
export function fromCoinbasePattern(colors, pattern, title = "") {
|
||||
return [
|
||||
...fromFullnessPattern(colors, pattern.bitcoin, Unit.btc, title),
|
||||
...fromFullnessPattern(colors, pattern.sats, Unit.sats, title),
|
||||
...fromFullnessPattern(colors, pattern.dollars, Unit.usd, title),
|
||||
...fromFullnessPatternWithSumCumulative(colors, pattern.bitcoin, Unit.btc, title),
|
||||
...fromFullnessPatternWithSumCumulative(colors, pattern.sats, Unit.sats, title),
|
||||
...fromFullnessPatternWithSumCumulative(colors, pattern.dollars, Unit.usd, title),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -797,62 +451,8 @@ export function fromIntervalPattern(colors, pattern, unit, title = "", color) {
|
||||
const { stat } = colors;
|
||||
return [
|
||||
{ metric: pattern.base, title: title ?? "base", color, unit },
|
||||
{
|
||||
metric: pattern.average,
|
||||
title: `${title} avg`.trim(),
|
||||
color: stat.avg,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.max,
|
||||
title: `${title} max`.trim(),
|
||||
color: stat.max,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.min,
|
||||
title: `${title} min`.trim(),
|
||||
color: stat.min,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.median,
|
||||
title: `${title} median`.trim(),
|
||||
color: stat.median,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.pct75,
|
||||
title: `${title} pct75`.trim(),
|
||||
color: stat.pct75,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.pct25,
|
||||
title: `${title} pct25`.trim(),
|
||||
color: stat.pct25,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.pct90,
|
||||
title: `${title} pct90`.trim(),
|
||||
color: stat.pct90,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.pct10,
|
||||
title: `${title} pct10`.trim(),
|
||||
color: stat.pct10,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
},
|
||||
{ metric: pattern.average, title: `${title} avg`.trim(), color: stat.avg, unit, defaultActive: false },
|
||||
...percentileSeries(colors, pattern, unit, title),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,14 @@ import { Unit } from "../utils/units.js";
|
||||
import { line, baseline } from "./series.js";
|
||||
import { priceLine, priceLines } from "./constants.js";
|
||||
|
||||
/**
|
||||
* Create a title formatter for chart titles
|
||||
* @param {string} [cohortTitle]
|
||||
* @returns {(metric: string) => string}
|
||||
*/
|
||||
export const formatCohortTitle = (cohortTitle) =>
|
||||
(metric) => cohortTitle ? `${metric}: ${cohortTitle}` : metric;
|
||||
|
||||
/**
|
||||
* Create sats/btc/usd line series from a pattern with .sats/.bitcoin/.dollars
|
||||
* @param {{ sats: AnyMetricPattern, bitcoin: AnyMetricPattern, dollars: AnyMetricPattern }} pattern
|
||||
@@ -144,7 +152,7 @@ export function ratioSmas(colors, ratio) {
|
||||
* Create ratio chart from ActivePriceRatioPattern
|
||||
* @param {PartialContext} ctx
|
||||
* @param {Object} args
|
||||
* @param {string} args.title
|
||||
* @param {(metric: string) => string} args.title
|
||||
* @param {AnyMetricPattern} args.price - The price metric to show in top pane
|
||||
* @param {ActivePriceRatioPattern} args.ratio - The ratio pattern
|
||||
* @param {Color} args.color
|
||||
@@ -156,7 +164,7 @@ export function createRatioChart(ctx, { title, price, ratio, color, name }) {
|
||||
|
||||
return {
|
||||
name: name ?? "ratio",
|
||||
title: name ? (title ? `${name} - ${title}` : name) : `${title} Ratio`,
|
||||
title: title(name ?? "Ratio"),
|
||||
top: [
|
||||
line({ metric: price, name: "price", color, unit: Unit.usd }),
|
||||
...percentileUsdMap(colors, ratio).map(({ name, prop, color }) =>
|
||||
|
||||
@@ -289,6 +289,11 @@
|
||||
* @property {readonly AddressCohortObject[]} list
|
||||
*
|
||||
* @typedef {UtxoCohortGroupObject | AddressCohortGroupObject} CohortGroupObject
|
||||
*
|
||||
* @typedef {Object} CohortGroupAddress
|
||||
* @property {string} name
|
||||
* @property {string} title
|
||||
* @property {readonly CohortAddress[]} list
|
||||
*/
|
||||
|
||||
// Re-export for type consumers
|
||||
|
||||
@@ -1,8 +1,19 @@
|
||||
import { localhost } from "../utils/env.js";
|
||||
import { serdeChartableIndex } from "../utils/serde.js";
|
||||
|
||||
/** @type {Map<AnyMetricPattern, string[]> | null} */
|
||||
export const unused = localhost ? new Map() : null;
|
||||
|
||||
/**
|
||||
* Check if a metric pattern has at least one chartable index
|
||||
* @param {AnyMetricPattern} node
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function hasChartableIndex(node) {
|
||||
const indexes = node.indexes();
|
||||
return indexes.some((idx) => serdeChartableIndex.serialize(idx) !== null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {TreeNode | null | undefined} node
|
||||
* @param {Map<AnyMetricPattern, string[]>} map
|
||||
@@ -10,7 +21,9 @@ export const unused = localhost ? new Map() : null;
|
||||
*/
|
||||
function walk(node, map, path) {
|
||||
if (node && "by" in node) {
|
||||
map.set(/** @type {AnyMetricPattern} */ (node), path);
|
||||
const metricNode = /** @type {AnyMetricPattern} */ (node);
|
||||
if (!hasChartableIndex(metricNode)) return;
|
||||
map.set(metricNode, path);
|
||||
} else if (node && typeof node === "object") {
|
||||
for (const [key, value] of Object.entries(node)) {
|
||||
const kn = key.toLowerCase();
|
||||
@@ -19,12 +32,16 @@ function walk(node, map, path) {
|
||||
kn === "time" ||
|
||||
kn === "height" ||
|
||||
kn === "constants" ||
|
||||
kn === "blockhash" ||
|
||||
kn === "oracle" ||
|
||||
kn === "split" ||
|
||||
kn === "ohlc" ||
|
||||
kn === "outpoint" ||
|
||||
kn === "positions" ||
|
||||
kn === "outputtype" ||
|
||||
kn === "heighttopool" ||
|
||||
kn === "txid" ||
|
||||
kn.endsWith("state") ||
|
||||
kn.endsWith("index") ||
|
||||
kn.endsWith("indexes") ||
|
||||
kn.endsWith("bytes") ||
|
||||
|
||||
Reference in New Issue
Block a user