website: snapshot

This commit is contained in:
nym21
2026-01-24 19:22:03 +01:00
parent 9b706dfaee
commit 7cdf47a9e4
33 changed files with 3031 additions and 2132 deletions

View File

@@ -67,32 +67,65 @@ function getLightDarkValue(property) {
return dark ? _dark : light;
}
const red = createColor(() => getColor("red"));
const orange = createColor(() => getColor("orange"));
const amber = createColor(() => getColor("amber"));
const yellow = createColor(() => getColor("yellow"));
const avocado = createColor(() => getColor("avocado"));
const lime = createColor(() => getColor("lime"));
const green = createColor(() => getColor("green"));
const emerald = createColor(() => getColor("emerald"));
const teal = createColor(() => getColor("teal"));
const cyan = createColor(() => getColor("cyan"));
const sky = createColor(() => getColor("sky"));
const blue = createColor(() => getColor("blue"));
const indigo = createColor(() => getColor("indigo"));
const violet = createColor(() => getColor("violet"));
const purple = createColor(() => getColor("purple"));
const fuchsia = createColor(() => getColor("fuchsia"));
const pink = createColor(() => getColor("pink"));
const rose = createColor(() => getColor("rose"));
export const colors = {
default: createColor(() => getLightDarkValue("--color")),
gray: createColor(() => getColor("gray")),
border: createColor(() => getLightDarkValue("--border-color")),
red: createColor(() => getColor("red")),
orange: createColor(() => getColor("orange")),
amber: createColor(() => getColor("amber")),
yellow: createColor(() => getColor("yellow")),
avocado: createColor(() => getColor("avocado")),
lime: createColor(() => getColor("lime")),
green: createColor(() => getColor("green")),
emerald: createColor(() => getColor("emerald")),
teal: createColor(() => getColor("teal")),
cyan: createColor(() => getColor("cyan")),
sky: createColor(() => getColor("sky")),
blue: createColor(() => getColor("blue")),
indigo: createColor(() => getColor("indigo")),
violet: createColor(() => getColor("violet")),
purple: createColor(() => getColor("purple")),
fuchsia: createColor(() => getColor("fuchsia")),
pink: createColor(() => getColor("pink")),
rose: createColor(() => getColor("rose")),
red,
orange,
amber,
yellow,
avocado,
lime,
green,
emerald,
teal,
cyan,
sky,
blue,
indigo,
violet,
purple,
fuchsia,
pink,
rose,
/** Semantic stat colors for pattern helpers */
stat: {
sum: blue,
cumulative: indigo,
avg: orange,
max: green,
pct90: cyan,
pct75: blue,
median: yellow,
pct25: violet,
pct10: fuchsia,
min: red,
},
};
/**
* @typedef {typeof colors} Colors
* @typedef {keyof Colors} ColorName
* @typedef {Exclude<keyof Colors, "stat">} ColorName
*/

View File

@@ -219,6 +219,10 @@ export function createChart({ parent, id: chartId, brk, fitContent }) {
? initialRange.to - initialRange.from
: Infinity;
/** @param {number} count */
const getDotsRadius = (count) =>
count > 1000 ? 1 : count > 200 ? 1.5 : count > 100 ? 2 : 3;
/** @type {Set<ZoomChangeCallback>} */
const onZoomChange = new Set();
@@ -1023,11 +1027,12 @@ export function createChart({ parent, id: chartId, brk, fitContent }) {
let active = defaultActive !== false;
let highlighted = true;
const showLastValue = options?.lastValueVisible !== false;
function update() {
iseries.applyOptions({
visible: active,
lastValueVisible: highlighted,
lastValueVisible: showLastValue && highlighted,
color: color.highlight(highlighted),
});
}
@@ -1117,21 +1122,21 @@ export function createChart({ parent, id: chartId, brk, fitContent }) {
let active = defaultActive !== false;
let highlighted = true;
let radius =
visibleBarsCount > 1000 ? 1 : visibleBarsCount > 200 ? 1.5 : 2;
let radius = getDotsRadius(visibleBarsCount);
function update() {
iseries.applyOptions({
visible: active,
lastValueVisible: highlighted,
color: color.highlight(highlighted),
pointMarkersRadius: radius,
});
}
update();
/** @type {ZoomChangeCallback} */
function handleZoom(count) {
const newRadius = count > 1000 ? 1 : count > 200 ? 1.5 : 2;
const newRadius = getDotsRadius(count);
if (newRadius === radius) return;
radius = newRadius;
iseries.applyOptions({ pointMarkersRadius: radius });
@@ -1507,8 +1512,13 @@ export function createChart({ parent, id: chartId, brk, fitContent }) {
}
const defaultUnit = units[0];
const sortedUnitIds = units
.map((u) => u.id)
.sort()
.join(",");
const persistedUnit = createPersistedValue({
defaultValue: /** @type {string} */ (defaultUnit.id),
storageKey: `unit-${sortedUnitIds}`,
urlKey: paneIndex === 0 ? "u0" : "u1",
serialize: (v) => v,
deserialize: (s) => s,

View File

@@ -33,6 +33,7 @@ export function createChainSection(ctx) {
market,
scripts,
supply,
distribution,
} = brk.metrics;
// Build pools tree dynamically
@@ -49,10 +50,10 @@ export function createChainSection(ctx) {
name: "Dominance",
title: `${poolName} Dominance`,
bottom: [
line({
dots({
metric: pool._24hDominance,
name: "24h",
color: colors.orange,
color: colors.pink,
unit: Unit.percentage,
defaultActive: false,
}),
@@ -88,7 +89,7 @@ export function createChainSection(ctx) {
name: "Blocks mined",
title: `${poolName} Blocks`,
bottom: [
line({
dots({
metric: pool.blocksMined.sum,
name: "Sum",
unit: Unit.count,
@@ -98,6 +99,14 @@ export function createChainSection(ctx) {
name: "Cumulative",
color: colors.blue,
unit: Unit.count,
defaultActive: false,
}),
line({
metric: pool._24hBlocksMined,
name: "24h Sum",
color: colors.pink,
unit: Unit.count,
defaultActive: false,
}),
line({
metric: pool._1wBlocksMined,
@@ -142,12 +151,17 @@ export function createChainSection(ctx) {
],
},
{
name: "Days since block",
title: `${poolName} Last Block`,
name: "Since last block",
title: `${poolName} Since Last Block`,
bottom: [
line({
metric: pool.blocksSinceBlock,
name: "Blocks",
unit: Unit.count,
}),
line({
metric: pool.daysSinceBlock,
name: "Since block",
name: "Days",
unit: Unit.days,
}),
],
@@ -227,12 +241,35 @@ export function createChainSection(ctx) {
title: "Transaction Count",
bottom: fromDollarsPattern(transactions.count.txCount, Unit.count),
},
{
name: "Speed",
title: "Transactions Per Second",
bottom: [
dots({
metric: transactions.volume.txPerSec,
name: "Transactions",
unit: Unit.perSec,
}),
],
},
{
name: "Volume",
title: "Transaction Volume",
bottom: [
...satsBtcUsd(transactions.volume.sentSum, "Sent"),
...satsBtcUsd(transactions.volume.receivedSum, "Received", colors.cyan, {
...satsBtcUsd(
transactions.volume.receivedSum,
"Received",
colors.cyan,
{
defaultActive: false,
},
),
line({
metric: transactions.volume.annualizedVolume.bitcoin,
name: "annualized",
color: colors.red,
unit: Unit.btc,
defaultActive: false,
}),
line({
@@ -242,13 +279,6 @@ export function createChainSection(ctx) {
unit: Unit.sats,
defaultActive: false,
}),
line({
metric: transactions.volume.annualizedVolume.bitcoin,
name: "annualized",
color: colors.red,
unit: Unit.btc,
defaultActive: false,
}),
line({
metric: transactions.volume.annualizedVolume.dollars,
name: "annualized",
@@ -266,6 +296,11 @@ export function createChainSection(ctx) {
...fromFeeRatePattern(transactions.size.vsize, Unit.vb),
],
},
{
name: "Fee Rate",
title: "Fee Rate",
bottom: fromFeeRatePattern(transactions.fees.feeRate, Unit.feeRate),
},
{
name: "Versions",
title: "Transaction Versions",
@@ -310,17 +345,6 @@ export function createChainSection(ctx) {
}),
],
},
{
name: "Speed",
title: "Transactions Per Second",
bottom: [
dots({
metric: transactions.volume.txPerSec,
name: "Transactions",
unit: Unit.perSec,
}),
],
},
],
},
@@ -356,6 +380,11 @@ 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",
@@ -387,70 +416,99 @@ export function createChainSection(ctx) {
],
},
// Coinbase
// Supply
{
name: "Coinbase",
title: "Coinbase Rewards",
bottom: fromCoinbasePattern(blocks.rewards.coinbase, "Coinbase"),
},
// Subsidy
{
name: "Subsidy",
title: "Block Subsidy",
bottom: [
...fromCoinbasePattern(blocks.rewards.subsidy, "Subsidy"),
line({
metric: blocks.rewards.subsidyDominance,
name: "Dominance",
color: colors.purple,
unit: Unit.percentage,
defaultActive: false,
}),
name: "Supply",
tree: [
{
name: "Circulating",
title: "Circulating Supply",
bottom: fromSupplyPattern(supply.circulating, "Supply"),
},
{
name: "Inflation",
title: "Inflation Rate",
bottom: [
dots({
metric: supply.inflation,
name: "Rate",
unit: Unit.percentage,
}),
],
},
{
name: "Unspendable",
title: "Unspendable Supply",
bottom: fromValuePattern(supply.burned.unspendable),
},
{
name: "OP_RETURN",
title: "OP_RETURN Supply",
bottom: fromValuePattern(supply.burned.opreturn),
},
],
},
// Fee
// Rewards
{
name: "Fee",
name: "Rewards",
tree: [
{
name: "Total",
name: "Coinbase",
title: "Coinbase Rewards",
bottom: fromCoinbasePattern(blocks.rewards.coinbase),
},
{
name: "Subsidy",
title: "Block Subsidy",
bottom: [
...fromCoinbasePattern(blocks.rewards.subsidy),
line({
metric: blocks.rewards.subsidyDominance,
name: "Dominance",
color: colors.purple,
unit: Unit.percentage,
defaultActive: false,
}),
],
},
{
name: "Fee",
title: "Transaction Fees",
bottom: [
line({
metric: transactions.fees.fee.sats.sum,
name: "Sum",
unit: Unit.sats,
}),
line({
metric: transactions.fees.fee.sats.cumulative,
name: "Cumulative",
color: colors.blue,
unit: Unit.sats,
defaultActive: false,
}),
line({
metric: transactions.fees.fee.bitcoin.sum,
name: "Sum",
name: "sum",
unit: Unit.btc,
}),
line({
metric: transactions.fees.fee.bitcoin.cumulative,
name: "Cumulative",
color: colors.blue,
name: "cumulative",
color: colors.stat.cumulative,
unit: Unit.btc,
defaultActive: false,
}),
line({
metric: transactions.fees.fee.sats.sum,
name: "sum",
unit: Unit.sats,
}),
line({
metric: transactions.fees.fee.sats.cumulative,
name: "cumulative",
color: colors.stat.cumulative,
unit: Unit.sats,
defaultActive: false,
}),
line({
metric: transactions.fees.fee.dollars.sum,
name: "Sum",
name: "sum",
unit: Unit.usd,
}),
line({
metric: transactions.fees.fee.dollars.cumulative,
name: "Cumulative",
color: colors.blue,
name: "cumulative",
color: colors.stat.cumulative,
unit: Unit.usd,
defaultActive: false,
}),
@@ -464,64 +522,98 @@ export function createChainSection(ctx) {
],
},
{
name: "Rate",
title: "Fee Rate",
bottom: [
line({
metric: transactions.fees.feeRate.median,
name: "Median",
color: colors.purple,
unit: Unit.feeRate,
}),
line({
metric: transactions.fees.feeRate.average,
name: "Average",
color: colors.blue,
unit: Unit.feeRate,
defaultActive: false,
}),
line({
metric: transactions.fees.feeRate.min,
name: "Min",
color: colors.red,
unit: Unit.feeRate,
defaultActive: false,
}),
line({
metric: transactions.fees.feeRate.max,
name: "Max",
color: colors.green,
unit: Unit.feeRate,
defaultActive: false,
}),
line({
metric: transactions.fees.feeRate.pct10,
name: "pct10",
color: colors.rose,
unit: Unit.feeRate,
defaultActive: false,
}),
line({
metric: transactions.fees.feeRate.pct25,
name: "pct25",
color: colors.pink,
unit: Unit.feeRate,
defaultActive: false,
}),
line({
metric: transactions.fees.feeRate.pct75,
name: "pct75",
color: colors.violet,
unit: Unit.feeRate,
defaultActive: false,
}),
line({
metric: transactions.fees.feeRate.pct90,
name: "pct90",
color: colors.fuchsia,
unit: Unit.feeRate,
defaultActive: false,
}),
name: "Unclaimed",
title: "Unclaimed Rewards",
bottom: fromValuePattern(
blocks.rewards.unclaimedRewards,
"Unclaimed",
),
},
],
},
// Addresses
{
name: "Addresses",
tree: [
{
name: "Count",
tree: [
{
name: "All",
title: "Total Address Count",
bottom: [
line({
metric: distribution.addrCount.all,
name: "Loaded",
unit: Unit.count,
}),
line({
metric: distribution.emptyAddrCount.all,
name: "Empty",
color: colors.gray,
unit: Unit.count,
defaultActive: false,
}),
],
},
{
name: "By Type",
title: "Address Count by Type",
bottom: [
line({
metric: distribution.addrCount.p2pkh,
name: "P2PKH",
color: colors.orange,
unit: Unit.count,
}),
line({
metric: distribution.addrCount.p2sh,
name: "P2SH",
color: colors.yellow,
unit: Unit.count,
}),
line({
metric: distribution.addrCount.p2wpkh,
name: "P2WPKH",
color: colors.green,
unit: Unit.count,
}),
line({
metric: distribution.addrCount.p2wsh,
name: "P2WSH",
color: colors.teal,
unit: Unit.count,
}),
line({
metric: distribution.addrCount.p2tr,
name: "P2TR",
color: colors.purple,
unit: Unit.count,
}),
line({
metric: distribution.addrCount.p2pk65,
name: "P2PK65",
color: colors.pink,
unit: Unit.count,
defaultActive: false,
}),
line({
metric: distribution.addrCount.p2pk33,
name: "P2PK33",
color: colors.red,
unit: Unit.count,
defaultActive: false,
}),
line({
metric: distribution.addrCount.p2a,
name: "P2A",
color: colors.blue,
unit: Unit.count,
defaultActive: false,
}),
],
},
],
},
],
@@ -568,6 +660,13 @@ export function createChainSection(ctx) {
unit: Unit.hashRate,
defaultActive: false,
}),
line({
metric: blocks.difficulty.asHash,
name: "Difficulty",
color: colors.default,
unit: Unit.hashRate,
options: { lineStyle: 1 },
}),
],
},
{
@@ -579,34 +678,17 @@ export function createChainSection(ctx) {
name: "Difficulty",
unit: Unit.difficulty,
}),
line({
metric: blocks.difficulty.adjustment,
name: "Adjustment",
color: colors.orange,
unit: Unit.percentage,
defaultActive: false,
}),
line({
metric: blocks.difficulty.asHash,
name: "As hash",
color: colors.default,
unit: Unit.hashRate,
defaultActive: false,
options: { lineStyle: 1 },
}),
line({
metric: blocks.difficulty.blocksBeforeNextAdjustment,
name: "Blocks until adj.",
name: "before next",
color: colors.indigo,
unit: Unit.blocks,
defaultActive: false,
}),
line({
metric: blocks.difficulty.daysBeforeNextAdjustment,
name: "Days until adj.",
name: "before next",
color: colors.purple,
unit: Unit.days,
defaultActive: false,
}),
],
},
@@ -619,6 +701,7 @@ export function createChainSection(ctx) {
name: "Difficulty Change",
unit: Unit.percentage,
}),
priceLine({ ctx, number: 0, unit: Unit.percentage }),
],
},
{
@@ -701,23 +784,22 @@ export function createChainSection(ctx) {
name: "Halving",
title: "Halving",
bottom: [
line({
metric: blocks.halving.blocksBeforeNextHalving,
name: "Blocks before next",
unit: Unit.blocks,
}),
line({
metric: blocks.halving.daysBeforeNextHalving,
name: "Days before next",
color: colors.orange,
unit: Unit.days,
}),
line({
metric: blocks.halving.epoch,
name: "Epoch",
color: colors.purple,
unit: Unit.epoch,
defaultActive: false,
}),
line({
metric: blocks.halving.blocksBeforeNextHalving,
name: "before next",
unit: Unit.blocks,
}),
line({
metric: blocks.halving.daysBeforeNextHalving,
name: "before next",
color: colors.blue,
unit: Unit.days,
}),
],
},
@@ -725,10 +807,11 @@ export function createChainSection(ctx) {
name: "Puell Multiple",
title: "Puell Multiple",
bottom: [
line({
baseline({
metric: market.indicators.puellMultiple,
name: "Puell Multiple",
unit: Unit.ratio,
base: 1,
}),
priceLine({ ctx, unit: Unit.ratio, number: 1 }),
],
@@ -741,60 +824,6 @@ export function createChainSection(ctx) {
name: "Pools",
tree: poolsTree,
},
// Unspendable
{
name: "Unspendable",
tree: [
{
name: "Supply",
title: "Unspendable Supply",
bottom: fromValuePattern(supply.burned.unspendable, "Supply"),
},
{
name: "OP_RETURN",
tree: [
{
name: "Outputs",
title: "OP_RETURN Outputs",
bottom: fromFullnessPattern(scripts.count.opreturn, Unit.count),
},
{
name: "Supply",
title: "OP_RETURN Supply",
bottom: fromValuePattern(supply.burned.opreturn, "Supply"),
},
],
},
],
},
// Supply
{
name: "Supply",
title: "Circulating Supply",
bottom: fromSupplyPattern(supply.circulating, "Supply"),
},
// Inflation
{
name: "Inflation",
title: "Inflation Rate",
bottom: [
line({
metric: supply.inflation,
name: "Rate",
unit: Unit.percentage,
}),
],
},
// Unclaimed Rewards
{
name: "Unclaimed Rewards",
title: "Unclaimed Rewards",
bottom: fromValuePattern(blocks.rewards.unclaimedRewards, "Unclaimed"),
},
],
};
}

View File

@@ -6,7 +6,8 @@ import {
percentileUsdMap,
percentileMap,
sdPatterns,
sdBands,
sdBandsUsd,
sdBandsRatio,
} from "./shared.js";
/**
@@ -56,8 +57,8 @@ function createCointimePriceWithRatioOptions(
baseline({
metric: ratio.ratio,
name: "Ratio",
color,
unit: Unit.ratio,
base: 1,
}),
line({
metric: ratio.ratio1wSma,
@@ -191,7 +192,7 @@ function createCointimePriceWithRatioOptions(
title: `${title} ${titleAddon} Z-Score`,
top: [
line({ metric: price, name: legend, color, unit: Unit.usd }),
...sdBands(colors, sd).map(
...sdBandsUsd(colors, sd).map(
({ name: bandName, prop, color: bandColor }) =>
line({
metric: prop,
@@ -203,7 +204,33 @@ function createCointimePriceWithRatioOptions(
),
],
bottom: [
line({ metric: sd.zscore, name: "Z-Score", color, unit: Unit.sd }),
baseline({
metric: sd.zscore,
name: "Z-Score",
unit: Unit.sd,
}),
baseline({
metric: ratio.ratio,
name: "Ratio",
unit: Unit.ratio,
base: 1,
}),
line({
metric: sd.sd,
name: "Volatility",
color: colors.gray,
unit: Unit.percentage,
}),
...sdBandsRatio(colors, sd).map(
({ name: bandName, prop, color: bandColor }) =>
line({
metric: prop,
name: bandName,
color: bandColor,
unit: Unit.ratio,
defaultActive: false,
}),
),
...priceLines({
ctx,
unit: Unit.sd,

View File

@@ -15,35 +15,51 @@ import {
} from "./series.js";
import { colors } from "../chart/colors.js";
/**
* @template {(arg: any, ...args: any[]) => any} F
* @typedef {F extends (arg: any, ...args: infer P) => infer R ? (...args: P) => R : never} OmitFirstArg
*/
/** @typedef {ReturnType<typeof createContext>} PartialContext */
/**
* Create a context object with all dependencies for building partial options
* @param {Object} args
* @param {BrkClient} args.brk
* @returns {PartialContext}
*/
export function createContext({ brk }) {
return {
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),
/** @type {OmitFirstArg<typeof fromFullnessPattern>} */
fromFullnessPattern: (pattern, unit, title) =>
fromFullnessPattern(colors, pattern, unit, title),
/** @type {OmitFirstArg<typeof fromDollarsPattern>} */
fromDollarsPattern: (pattern, unit, title) =>
fromDollarsPattern(colors, pattern, unit, title),
/** @type {OmitFirstArg<typeof fromFeeRatePattern>} */
fromFeeRatePattern: (pattern, unit, title) =>
fromFeeRatePattern(colors, pattern, unit, title),
/** @type {OmitFirstArg<typeof fromCoinbasePattern>} */
fromCoinbasePattern: (pattern, title) =>
fromCoinbasePattern(colors, pattern, title),
/** @type {OmitFirstArg<typeof fromValuePattern>} */
fromValuePattern: (pattern, title, sumColor, cumulativeColor) =>
fromValuePattern(colors, pattern, title, sumColor, cumulativeColor),
/** @type {OmitFirstArg<typeof fromBitcoinPatternWithUnit>} */
fromBitcoinPatternWithUnit: (
pattern,
title,
@@ -59,6 +75,7 @@ export function createContext({ brk }) {
sumColor,
cumulativeColor,
),
/** @type {OmitFirstArg<typeof fromBlockCountWithUnit>} */
fromBlockCountWithUnit: (pattern, unit, title, sumColor, cumulativeColor) =>
fromBlockCountWithUnit(
colors,
@@ -68,9 +85,11 @@ export function createContext({ brk }) {
sumColor,
cumulativeColor,
),
/** @type {OmitFirstArg<typeof fromIntervalPattern>} */
fromIntervalPattern: (pattern, unit, title, color) =>
fromIntervalPattern(colors, pattern, unit, title, color),
/** @type {fromSupplyPattern} */
fromSupplyPattern: (pattern, title, color) =>
fromSupplyPattern(colors, pattern, title, color),
fromSupplyPattern(pattern, title, color),
};
}

View File

@@ -51,7 +51,7 @@ export function createAddressCohortFolder(ctx, args) {
{
name: "total",
title: `Supply ${title}`,
bottom: createGroupedSupplyTotalSeries(ctx, list),
bottom: createGroupedSupplyTotalSeries(list),
},
{
name: "in profit",

View File

@@ -23,6 +23,12 @@ const entries = (obj) =>
Object.entries(obj)
);
/** @type {readonly AddressableType[]} */
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));
/**
* Build all cohort data from brk tree
* @param {Colors} colors
@@ -31,6 +37,7 @@ const entries = (obj) =>
export function buildCohortData(colors, brk) {
const utxoCohorts = brk.metrics.distribution.utxoCohorts;
const addressCohorts = brk.metrics.distribution.addressCohorts;
const { addrCount } = brk.metrics.distribution;
const {
TERM_NAMES,
EPOCH_NAMES,
@@ -51,6 +58,7 @@ export function buildCohortData(colors, brk) {
title: "",
color: colors.orange,
tree: utxoCohorts.all,
addrCount: addrCount.all,
};
// Term cohorts - split because short is CohortFull, long is CohortWithPercentiles
@@ -200,18 +208,32 @@ export function buildCohortData(colors, brk) {
},
);
// Spendable type cohorts - CohortBasic (neither adjustedSopr nor percentiles)
/** @type {readonly CohortBasic[]} */
const type = entries(utxoCohorts.type).map(([key, tree]) => {
// 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,
color: colors[spendableTypeColors[key]],
tree,
tree: utxoCohorts.type[key],
addrCount: addrCount[key],
};
});
/** @type {readonly CohortBasic[]} */
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,
color: colors[spendableTypeColors[key]],
tree,
};
});
// Year cohorts - CohortBasic (neither adjustedSopr nor percentiles)
/** @type {readonly CohortBasic[]} */
const year = entries(utxoCohorts.year).map(([key, tree]) => {
@@ -238,7 +260,8 @@ export function buildCohortData(colors, brk) {
addressesUnderAmount,
utxosAmountRanges,
addressesAmountRanges,
type,
typeAddressable,
typeOther,
year,
};
}

View File

@@ -12,6 +12,7 @@ export {
createCohortFolderWithAdjusted,
createCohortFolderWithPercentiles,
createCohortFolderBasic,
createCohortFolderAddress,
} from "./utxo.js";
export { createAddressCohortFolder } from "./address.js";

View File

@@ -2,7 +2,7 @@
import { Unit } from "../../utils/units.js";
import { priceLine } from "../constants.js";
import { line } from "../series.js";
import { baseline, line } from "../series.js";
import { satsBtcUsd } from "../shared.js";
/**
@@ -74,25 +74,22 @@ export function createSingleSupplySeries(ctx, cohort) {
/**
* Create supply total series for grouped cohorts
* @param {PartialContext} ctx
* @param {readonly CohortObject[]} list
* @returns {AnyFetchedSeriesBlueprint[]}
*/
export function createGroupedSupplyTotalSeries(ctx, list) {
const { brk } = ctx;
const constant100 = brk.metrics.constants.constant100;
export function createGroupedSupplyTotalSeries(list) {
return list.flatMap(({ color, name, tree }) => [
...satsBtcUsd(tree.supply.total, name, color),
line({
metric:
"supplyRelToCirculatingSupply" in tree.relative
? tree.relative.supplyRelToCirculatingSupply
: constant100,
name,
color,
unit: Unit.pctSupply,
}),
...("supplyRelToCirculatingSupply" in tree.relative
? [
line({
metric: tree.relative.supplyRelToCirculatingSupply,
name,
color,
unit: Unit.pctSupply,
}),
]
: []),
]);
}
@@ -194,12 +191,12 @@ export function createRealizedPriceSeries(list) {
*/
export function createRealizedPriceRatioSeries(ctx, list) {
return [
...list.map(({ color, name, tree }) =>
line({
...list.map(({ name, tree }) =>
baseline({
metric: tree.realized.realizedPriceExtra.ratio,
name,
color,
unit: Unit.ratio,
base: 1,
}),
),
priceLine({ ctx, unit: Unit.ratio, number: 1 }),

View File

@@ -7,7 +7,8 @@ import {
percentileUsdMap,
percentileMap,
sdPatterns,
sdBands,
sdBandsUsd,
sdBandsRatio,
} from "../shared.js";
import { periodIdToName } from "./utils.js";
@@ -90,7 +91,6 @@ export function createPriceWithRatioOptions(
metric: ratio.ratio,
name: "Ratio",
base: 1,
color,
unit: Unit.ratio,
}),
.../** @type {const} */ ([
@@ -222,7 +222,7 @@ export function createPriceWithRatioOptions(
title: `${title} ${titleAddon} Z-Score`,
top: [
line({ metric: priceMetric, name: legend, color, unit: Unit.usd }),
...sdBands(colors, sd).map(
...sdBandsUsd(colors, sd).map(
({ name: bandName, prop, color: bandColor }) =>
line({
metric: prop,
@@ -237,9 +237,31 @@ export function createPriceWithRatioOptions(
baseline({
metric: sd.zscore,
name: "Z-Score",
color,
unit: Unit.sd,
}),
baseline({
metric: ratio.ratio,
name: "Ratio",
unit: Unit.ratio,
base: 1,
}),
line({
metric: sd.sd,
name: "Volatility",
color: colors.gray,
unit: Unit.percentage,
}),
...sdBandsRatio(colors, sd).map(
({ name: bandName, prop, color: bandColor }) =>
line({
metric: prop,
name: bandName,
color: bandColor,
unit: Unit.ratio,
defaultActive: false,
}),
),
priceLine({ ctx, unit: Unit.ratio, number: 1 }),
priceLine({
ctx,
unit: Unit.sd,

View File

@@ -8,8 +8,9 @@ import {
createCohortFolderWithAdjusted,
createCohortFolderWithPercentiles,
createCohortFolderBasic,
createCohortFolderAddress,
createAddressCohortFolder,
} from "./cohorts/index.js";
} from "./distribution/index.js";
import { createMarketSection } from "./market/index.js";
import { createChainSection } from "./chain.js";
import { createCointimeSection } from "./cointime.js";
@@ -17,6 +18,7 @@ import { colors } from "../chart/colors.js";
// Re-export types for external consumers
export * from "./types.js";
export * from "./context.js";
/**
* Create partial options tree
@@ -43,17 +45,22 @@ export function createPartialOptions({ brk }) {
addressesUnderAmount,
utxosAmountRanges,
addressesAmountRanges,
type,
typeAddressable,
typeOther,
year,
} = buildCohortData(colors, brk);
// Helpers to map cohorts by capability type
/** @param {CohortWithAdjusted} cohort */
const mapWithAdjusted = (cohort) => createCohortFolderWithAdjusted(ctx, cohort);
const mapWithAdjusted = (cohort) =>
createCohortFolderWithAdjusted(ctx, cohort);
/** @param {CohortWithPercentiles} cohort */
const mapWithPercentiles = (cohort) => createCohortFolderWithPercentiles(ctx, cohort);
const mapWithPercentiles = (cohort) =>
createCohortFolderWithPercentiles(ctx, cohort);
/** @param {CohortBasic} cohort */
const mapBasic = (cohort) => createCohortFolderBasic(ctx, cohort);
/** @param {CohortAddress} cohort */
const mapAddress = (cohort) => createCohortFolderAddress(ctx, cohort);
/** @param {AddressCohortObject} cohort */
const mapAddressCohorts = (cohort) => createAddressCohortFolder(ctx, cohort);
@@ -81,7 +88,7 @@ export function createPartialOptions({ brk }) {
// Cohorts section
{
name: "Cohorts",
name: "Distribution",
tree: [
// All UTXOs - CohortAll (adjustedSopr + percentiles but no RelToMarketCap)
createCohortFolderAll(ctx, cohortAll),
@@ -90,22 +97,29 @@ export function createPartialOptions({ brk }) {
{
name: "Terms",
tree: [
// Compare folder uses WithPercentiles (common capabilities)
createCohortFolderWithPercentiles(ctx, {
name: "Compare",
title: "Term",
list: [termShort, termLong],
}),
// Individual cohorts with their specific capabilities
createCohortFolderFull(ctx, termShort),
createCohortFolderWithPercentiles(ctx, termLong),
],
},
// Types - CohortBasic
// Types - addressable types have addrCount, others don't
{
name: "Types",
tree: [
createCohortFolderBasic(ctx, {
createCohortFolderAddress(ctx, {
name: "Compare",
title: "Type",
list: type,
list: typeAddressable,
}),
...type.map(mapBasic),
...typeAddressable.map(mapAddress),
...typeOther.map(mapBasic),
],
},

View File

@@ -193,8 +193,8 @@ export function fromBlockCount(colors, pattern, title, color) {
{ metric: pattern.sum, title, color: color ?? colors.default },
{
metric: pattern.cumulative,
title: `${title} (cum.)`,
color: colors.cyan,
title: `${title} cumulative`,
color: colors.stat.cumulative,
defaultActive: false,
},
];
@@ -209,49 +209,55 @@ export function fromBlockCount(colors, pattern, title, 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: "Average", defaultActive: false },
{
metric: pattern.average,
title: `${title} avg`,
color: stat.avg,
defaultActive: false,
},
{
metric: pattern.max,
title: "Max",
color: colors.pink,
title: `${title} max`,
color: stat.max,
defaultActive: false,
},
{
metric: pattern.min,
title: "Min",
color: colors.green,
title: `${title} min`,
color: stat.min,
defaultActive: false,
},
{
metric: pattern.median,
title: "Median",
color: colors.amber,
title: `${title} median`,
color: stat.median,
defaultActive: false,
},
{
metric: pattern.pct75,
title: "pct75",
color: colors.red,
title: `${title} pct75`,
color: stat.pct75,
defaultActive: false,
},
{
metric: pattern.pct25,
title: "pct25",
color: colors.yellow,
title: `${title} pct25`,
color: stat.pct25,
defaultActive: false,
},
{
metric: pattern.pct90,
title: "pct90",
color: colors.rose,
title: `${title} pct90`,
color: stat.pct90,
defaultActive: false,
},
{
metric: pattern.pct10,
title: "pct10",
color: colors.lime,
title: `${title} pct10`,
color: stat.pct10,
defaultActive: false,
},
];
@@ -266,55 +272,61 @@ export function fromBitcoin(colors, pattern, title, 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: "Average", defaultActive: false },
{
metric: pattern.average,
title: `${title} avg`,
color: stat.avg,
defaultActive: false,
},
{
metric: pattern.cumulative,
title: `Cumulative`,
color: colors.cyan,
title: `${title} cumulative`,
color: stat.cumulative,
defaultActive: false,
},
{
metric: pattern.max,
title: "Max",
color: colors.pink,
title: `${title} max`,
color: stat.max,
defaultActive: false,
},
{
metric: pattern.min,
title: "Min",
color: colors.green,
title: `${title} min`,
color: stat.min,
defaultActive: false,
},
{
metric: pattern.median,
title: "Median",
color: colors.amber,
title: `${title} median`,
color: stat.median,
defaultActive: false,
},
{
metric: pattern.pct75,
title: "pct75",
color: colors.red,
title: `${title} pct75`,
color: stat.pct75,
defaultActive: false,
},
{
metric: pattern.pct25,
title: "pct25",
color: colors.yellow,
title: `${title} pct25`,
color: stat.pct25,
defaultActive: false,
},
{
metric: pattern.pct90,
title: "pct90",
color: colors.rose,
title: `${title} pct90`,
color: stat.pct90,
defaultActive: false,
},
{
metric: pattern.pct10,
title: "pct10",
color: colors.lime,
title: `${title} pct10`,
color: stat.pct10,
defaultActive: false,
},
];
@@ -329,68 +341,69 @@ export function fromBlockSize(colors, pattern, title, color) {
* @returns {AnyFetchedSeriesBlueprint[]}
*/
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: colors.blue,
color: stat.sum,
unit,
defaultActive: false,
},
{
metric: pattern.cumulative,
title: `${title} cumulative`.trim(),
color: colors.indigo,
unit,
defaultActive: false,
},
{
metric: pattern.min,
title: `${title} min`.trim(),
color: colors.red,
color: stat.cumulative,
unit,
defaultActive: false,
},
{
metric: pattern.max,
title: `${title} max`.trim(),
color: colors.green,
color: stat.max,
unit,
defaultActive: false,
},
{
metric: pattern.pct10,
title: `${title} pct10`.trim(),
color: colors.rose,
unit,
defaultActive: false,
},
{
metric: pattern.pct25,
title: `${title} pct25`.trim(),
color: colors.pink,
metric: pattern.min,
title: `${title} min`.trim(),
color: stat.min,
unit,
defaultActive: false,
},
{
metric: pattern.median,
title: `${title} median`.trim(),
color: colors.purple,
color: stat.median,
unit,
defaultActive: false,
},
{
metric: pattern.pct75,
title: `${title} pct75`.trim(),
color: colors.violet,
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: colors.fuchsia,
color: stat.pct90,
unit,
defaultActive: false,
},
{
metric: pattern.pct10,
title: `${title} pct10`.trim(),
color: stat.pct10,
unit,
defaultActive: false,
},
@@ -406,61 +419,61 @@ export function fromSizePattern(colors, pattern, unit, title = "") {
* @returns {AnyFetchedSeriesBlueprint[]}
*/
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: colors.purple,
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: colors.red,
unit,
defaultActive: false,
},
{
metric: pattern.max,
title: `${title} max`.trim(),
color: colors.green,
unit,
defaultActive: false,
},
{
metric: pattern.pct10,
title: `${title} pct10`.trim(),
color: colors.rose,
unit,
defaultActive: false,
},
{
metric: pattern.pct25,
title: `${title} pct25`.trim(),
color: colors.pink,
color: stat.min,
unit,
defaultActive: false,
},
{
metric: pattern.median,
title: `${title} median`.trim(),
color: colors.violet,
color: stat.median,
unit,
defaultActive: false,
},
{
metric: pattern.pct75,
title: `${title} pct75`.trim(),
color: colors.fuchsia,
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: colors.amber,
color: stat.pct90,
unit,
defaultActive: false,
},
{
metric: pattern.pct10,
title: `${title} pct10`.trim(),
color: stat.pct10,
unit,
defaultActive: false,
},
@@ -476,74 +489,75 @@ export function fromFullnessPattern(colors, pattern, unit, title = "") {
* @returns {AnyFetchedSeriesBlueprint[]}
*/
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: colors.blue,
color: stat.sum,
unit,
},
{
metric: pattern.cumulative,
title: `${title} cumulative`.trim(),
color: colors.cyan,
color: stat.cumulative,
unit,
defaultActive: false,
},
{
metric: pattern.average,
title: `${title} avg`.trim(),
color: colors.purple,
unit,
defaultActive: false,
},
{
metric: pattern.min,
title: `${title} min`.trim(),
color: colors.red,
color: stat.avg,
unit,
defaultActive: false,
},
{
metric: pattern.max,
title: `${title} max`.trim(),
color: colors.green,
color: stat.max,
unit,
defaultActive: false,
},
{
metric: pattern.pct10,
title: `${title} pct10`.trim(),
color: colors.rose,
unit,
defaultActive: false,
},
{
metric: pattern.pct25,
title: `${title} pct25`.trim(),
color: colors.pink,
metric: pattern.min,
title: `${title} min`.trim(),
color: stat.min,
unit,
defaultActive: false,
},
{
metric: pattern.median,
title: `${title} median`.trim(),
color: colors.violet,
color: stat.median,
unit,
defaultActive: false,
},
{
metric: pattern.pct75,
title: `${title} pct75`.trim(),
color: colors.fuchsia,
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: colors.amber,
color: stat.pct90,
unit,
defaultActive: false,
},
{
metric: pattern.pct10,
title: `${title} pct10`.trim(),
color: stat.pct10,
unit,
defaultActive: false,
},
@@ -559,54 +573,67 @@ export function fromDollarsPattern(colors, pattern, unit, title = "") {
* @returns {AnyFetchedSeriesBlueprint[]}
*/
export function fromFeeRatePattern(colors, pattern, unit, title = "") {
const { stat } = colors;
return [
{ metric: pattern.average, title: `${title} avg`.trim(), unit },
{
metric: pattern.min,
title: `${title} min`.trim(),
color: colors.red,
type: "Dots",
metric: pattern.average,
title: `${title} avg`.trim(),
unit,
defaultActive: false,
},
{
type: "Dots",
metric: pattern.max,
title: `${title} max`.trim(),
color: colors.green,
color: stat.max,
unit,
defaultActive: false,
},
{
metric: pattern.pct10,
title: `${title} pct10`.trim(),
color: colors.rose,
unit,
defaultActive: false,
},
{
metric: pattern.pct25,
title: `${title} pct25`.trim(),
color: colors.pink,
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: colors.purple,
color: stat.median,
unit,
defaultActive: false,
},
{
type: "Dots",
metric: pattern.pct75,
title: `${title} pct75`.trim(),
color: colors.violet,
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: colors.fuchsia,
color: stat.pct90,
unit,
defaultActive: false,
},
{
type: "Dots",
metric: pattern.pct10,
title: `${title} pct10`.trim(),
color: stat.pct10,
unit,
defaultActive: false,
},
@@ -617,13 +644,13 @@ export function fromFeeRatePattern(colors, pattern, unit, title = "") {
* Create series from a CoinbasePattern ({ sats, bitcoin, dollars } each as FullnessPattern)
* @param {Colors} colors
* @param {CoinbasePattern} pattern
* @param {string} title
* @param {string} [title]
* @returns {AnyFetchedSeriesBlueprint[]}
*/
export function fromCoinbasePattern(colors, pattern, title) {
return [
...fromFullnessPattern(colors, pattern.sats, Unit.sats, title),
...fromFullnessPattern(colors, pattern.bitcoin, Unit.btc, title),
...fromFullnessPattern(colors, pattern.sats, Unit.sats, title),
...fromFullnessPattern(colors, pattern.dollars, Unit.usd, title),
];
}
@@ -632,7 +659,7 @@ export function fromCoinbasePattern(colors, pattern, title) {
* Create series from a ValuePattern ({ sats, bitcoin, dollars } each as BlockCountPattern with sum + cumulative)
* @param {Colors} colors
* @param {ValuePattern} pattern
* @param {string} title
* @param {string} [title]
* @param {Color} [sumColor]
* @param {Color} [cumulativeColor]
* @returns {AnyFetchedSeriesBlueprint[]}
@@ -640,47 +667,47 @@ export function fromCoinbasePattern(colors, pattern, title) {
export function fromValuePattern(
colors,
pattern,
title,
title = "",
sumColor,
cumulativeColor,
) {
return [
{
metric: pattern.sats.sum,
title,
color: sumColor,
unit: Unit.sats,
},
{
metric: pattern.sats.cumulative,
title: `${title} cumulative`,
color: cumulativeColor ?? colors.blue,
unit: Unit.sats,
defaultActive: false,
},
{
metric: pattern.bitcoin.sum,
title,
title: title || "sum",
color: sumColor,
unit: Unit.btc,
},
{
metric: pattern.bitcoin.cumulative,
title: `${title} cumulative`,
color: cumulativeColor ?? colors.blue,
title: `${title} cumulative`.trim(),
color: cumulativeColor ?? colors.stat.cumulative,
unit: Unit.btc,
defaultActive: false,
},
{
metric: pattern.sats.sum,
title: title || "sum",
color: sumColor,
unit: Unit.sats,
},
{
metric: pattern.sats.cumulative,
title: `${title} cumulative`.trim(),
color: cumulativeColor ?? colors.stat.cumulative,
unit: Unit.sats,
defaultActive: false,
},
{
metric: pattern.dollars.sum,
title,
title: title || "sum",
color: sumColor,
unit: Unit.usd,
},
{
metric: pattern.dollars.cumulative,
title: `${title} cumulative`,
color: cumulativeColor ?? colors.blue,
title: `${title} cumulative`.trim(),
color: cumulativeColor ?? colors.stat.cumulative,
unit: Unit.usd,
defaultActive: false,
},
@@ -715,7 +742,7 @@ export function fromBitcoinPatternWithUnit(
{
metric: pattern.cumulative,
title: `${title} cumulative`,
color: cumulativeColor ?? colors.blue,
color: cumulativeColor ?? colors.stat.cumulative,
unit,
defaultActive: false,
},
@@ -750,7 +777,7 @@ export function fromBlockCountWithUnit(
{
metric: pattern.cumulative,
title: `${title} cumulative`.trim(),
color: cumulativeColor ?? colors.blue,
color: cumulativeColor ?? colors.stat.cumulative,
unit,
defaultActive: false,
},
@@ -767,61 +794,62 @@ export function fromBlockCountWithUnit(
* @returns {AnyFetchedSeriesBlueprint[]}
*/
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: colors.cyan,
unit,
defaultActive: false,
},
{
metric: pattern.min,
title: `${title} min`.trim(),
color: colors.red,
color: stat.avg,
unit,
defaultActive: false,
},
{
metric: pattern.max,
title: `${title} max`.trim(),
color: colors.green,
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: colors.violet,
unit,
defaultActive: false,
},
{
metric: pattern.pct10,
title: `${title} pct10`.trim(),
color: colors.rose,
unit,
defaultActive: false,
},
{
metric: pattern.pct25,
title: `${title} pct25`.trim(),
color: colors.pink,
color: stat.median,
unit,
defaultActive: false,
},
{
metric: pattern.pct75,
title: `${title} pct75`.trim(),
color: colors.fuchsia,
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: colors.amber,
color: stat.pct90,
unit,
defaultActive: false,
},
{
metric: pattern.pct10,
title: `${title} pct10`.trim(),
color: stat.pct10,
unit,
defaultActive: false,
},
@@ -830,30 +858,29 @@ export function fromIntervalPattern(colors, pattern, unit, title = "", color) {
/**
* Create series from a SupplyPattern (sats/bitcoin/dollars, no sum/cumulative)
* @param {Colors} colors
* @param {SupplyPattern} pattern
* @param {string} title
* @param {Color} [color]
* @returns {AnyFetchedSeriesBlueprint[]}
*/
export function fromSupplyPattern(colors, pattern, title, color) {
export function fromSupplyPattern(pattern, title, color) {
return [
{
metric: pattern.sats,
title,
color: color ?? colors.default,
unit: Unit.sats,
},
{
metric: pattern.bitcoin,
title,
color: color ?? colors.default,
color,
unit: Unit.btc,
},
{
metric: pattern.sats,
title,
color,
unit: Unit.sats,
},
{
metric: pattern.dollars,
title,
color: color ?? colors.default,
color,
unit: Unit.usd,
},
];

View File

@@ -1,7 +1,8 @@
/** Shared helpers for options */
import { Unit } from "../utils/units.js";
import { line } from "./series.js";
import { line, baseline } from "./series.js";
import { priceLine, priceLines } from "./constants.js";
/**
* Create sats/btc/usd line series from a pattern with .sats/.bitcoin/.dollars
@@ -14,7 +15,6 @@ import { line } from "./series.js";
export function satsBtcUsd(pattern, name, color, options) {
const { defaultActive } = options || {};
return [
line({ metric: pattern.sats, name, color, unit: Unit.sats, defaultActive }),
line({
metric: pattern.bitcoin,
name,
@@ -22,6 +22,7 @@ export function satsBtcUsd(pattern, name, color, options) {
unit: Unit.btc,
defaultActive,
}),
line({ metric: pattern.sats, name, color, unit: Unit.sats, defaultActive }),
line({
metric: pattern.dollars,
name,
@@ -82,7 +83,7 @@ export function sdPatterns(ratio) {
* @param {Colors} colors
* @param {Ratio1ySdPattern} sd
*/
export function sdBands(colors, sd) {
export function sdBandsUsd(colors, sd) {
return /** @type {const} */ ([
{ name: "0σ", prop: sd._0sdUsd, color: colors.lime },
{ name: "+0.5σ", prop: sd.p05sdUsd, color: colors.yellow },
@@ -95,7 +96,249 @@ export function sdBands(colors, sd) {
{ name: "2σ", prop: sd.m2sdUsd, color: colors.blue },
{ name: "+2.5σ", prop: sd.p25sdUsd, color: colors.rose },
{ name: "2.5σ", prop: sd.m25sdUsd, color: colors.indigo },
{ name: "+3σ", prop: sd.p3sdUsd, color: colors.pink },
{ name: "3σ", prop: sd.m3sdUsd, color: colors.violet },
]);
}
/**
* Build SD band mappings (ratio) from an SD pattern
* @param {Colors} colors
* @param {Ratio1ySdPattern} sd
*/
export function sdBandsRatio(colors, sd) {
return /** @type {const} */ ([
{ name: "0σ", prop: sd.sma, color: colors.lime },
{ name: "+0.5σ", prop: sd.p05sd, color: colors.yellow },
{ name: "0.5σ", prop: sd.m05sd, color: colors.teal },
{ name: "+1σ", prop: sd.p1sd, color: colors.amber },
{ name: "1σ", prop: sd.m1sd, color: colors.cyan },
{ name: "+1.5σ", prop: sd.p15sd, color: colors.orange },
{ name: "1.5σ", prop: sd.m15sd, color: colors.sky },
{ name: "+2σ", prop: sd.p2sd, color: colors.red },
{ name: "2σ", prop: sd.m2sd, color: colors.blue },
{ name: "+2.5σ", prop: sd.p25sd, color: colors.rose },
{ name: "2.5σ", prop: sd.m25sd, color: colors.indigo },
{ name: "+3σ", prop: sd.p3sd, color: colors.pink },
{ name: "3σ", prop: sd.m3sd, color: colors.violet },
]);
}
/**
* Build ratio SMA series from a ratio pattern
* @param {Colors} colors
* @param {ActivePriceRatioPattern} ratio
*/
export function ratioSmas(colors, ratio) {
return /** @type {const} */ ([
{ name: "1w SMA", metric: ratio.ratio1wSma, color: colors.lime },
{ name: "1m SMA", metric: ratio.ratio1mSma, color: colors.teal },
{ name: "1y SMA", metric: ratio.ratio1ySd.sma, color: colors.sky },
{ name: "2y SMA", metric: ratio.ratio2ySd.sma, color: colors.indigo },
{ name: "4y SMA", metric: ratio.ratio4ySd.sma, color: colors.purple },
{ name: "All SMA", metric: ratio.ratioSd.sma, color: colors.rose },
]);
}
/**
* Create ratio chart from ActivePriceRatioPattern
* @param {PartialContext} ctx
* @param {Object} args
* @param {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
* @returns {PartialChartOption}
*/
export function createRatioChart(ctx, { title, price, ratio, color }) {
const { colors } = ctx;
return {
name: "ratio",
title: `${title} Ratio`,
top: [
line({ metric: price, name: "price", color, unit: Unit.usd }),
...percentileUsdMap(colors, ratio).map(({ name, prop, color }) =>
line({
metric: prop,
name,
color,
defaultActive: false,
unit: Unit.usd,
options: { lineStyle: 1 },
}),
),
],
bottom: [
baseline({
metric: ratio.ratio,
name: "Ratio",
unit: Unit.ratio,
base: 1,
}),
...ratioSmas(colors, ratio).map(({ name, metric, color }) =>
line({ metric, name, color, unit: Unit.ratio, defaultActive: false }),
),
...percentileMap(colors, ratio).map(({ name, prop, color }) =>
line({
metric: prop,
name,
color,
defaultActive: false,
unit: Unit.ratio,
options: { lineStyle: 1 },
}),
),
priceLine({ ctx, unit: Unit.ratio, number: 1 }),
],
};
}
/**
* Create ZScores folder from ActivePriceRatioPattern
* @param {PartialContext} ctx
* @param {Object} args
* @param {string} args.title
* @param {string} args.legend
* @param {AnyMetricPattern} args.price - The price metric to show in top pane
* @param {ActivePriceRatioPattern} args.ratio - The ratio pattern
* @param {Color} args.color
* @returns {PartialOptionsGroup}
*/
export function createZScoresFolder(
ctx,
{ title, legend, price, ratio, color },
) {
const { colors } = ctx;
const sdPats = sdPatterns(ratio);
return {
name: "ZScores",
tree: [
{
name: "Compare",
title: `${title} Z-Scores`,
top: [
line({ metric: price, name: legend, color, unit: Unit.usd }),
line({
metric: ratio.ratio1ySd._0sdUsd,
name: "1y 0σ",
color: colors.orange,
defaultActive: false,
unit: Unit.usd,
}),
line({
metric: ratio.ratio2ySd._0sdUsd,
name: "2y 0σ",
color: colors.yellow,
defaultActive: false,
unit: Unit.usd,
}),
line({
metric: ratio.ratio4ySd._0sdUsd,
name: "4y 0σ",
color: colors.lime,
defaultActive: false,
unit: Unit.usd,
}),
line({
metric: ratio.ratioSd._0sdUsd,
name: "all 0σ",
color: colors.blue,
defaultActive: false,
unit: Unit.usd,
}),
],
bottom: [
line({
metric: ratio.ratioSd.zscore,
name: "all",
color: colors.blue,
unit: Unit.sd,
}),
line({
metric: ratio.ratio4ySd.zscore,
name: "4y",
color: colors.lime,
unit: Unit.sd,
}),
line({
metric: ratio.ratio2ySd.zscore,
name: "2y",
color: colors.yellow,
unit: Unit.sd,
}),
line({
metric: ratio.ratio1ySd.zscore,
name: "1y",
color: colors.orange,
unit: Unit.sd,
}),
...priceLines({
ctx,
unit: Unit.sd,
numbers: [0, 1, -1, 2, -2, 3, -3],
defaultActive: false,
}),
],
},
...sdPats.map(({ nameAddon, titleAddon, sd }) => ({
name: nameAddon,
title: `${title} ${titleAddon} Z-Score`,
top: [
line({ metric: price, name: legend, color, unit: Unit.usd }),
...sdBandsUsd(colors, sd).map(
({ name: bandName, prop, color: bandColor }) =>
line({
metric: prop,
name: bandName,
color: bandColor,
unit: Unit.usd,
defaultActive: false,
}),
),
],
bottom: [
baseline({
metric: sd.zscore,
name: "Z-Score",
unit: Unit.sd,
}),
baseline({
metric: ratio.ratio,
name: "Ratio",
unit: Unit.ratio,
base: 1,
}),
line({
metric: sd.sd,
name: "Volatility",
color: colors.gray,
unit: Unit.percentage,
}),
...sdBandsRatio(colors, sd).map(
({ name: bandName, prop, color: bandColor }) =>
line({
metric: prop,
name: bandName,
color: bandColor,
unit: Unit.ratio,
defaultActive: false,
}),
),
priceLine({ ctx, unit: Unit.ratio, number: 1 }),
priceLine({
ctx,
unit: Unit.sd,
}),
...priceLines({
ctx,
unit: Unit.sd,
numbers: [1, -1, 2, -2, 3, -3],
defaultActive: false,
}),
],
})),
],
};
}

View File

@@ -160,6 +160,7 @@
* @property {string} title
* @property {Color} color
* @property {PatternAll} tree
* @property {Brk.MetricPattern1<Brk.StoredU64>} addrCount
*
* Full cohort: adjustedSopr + percentiles + RelToMarketCap (term.short)
* @typedef {Object} CohortFull
@@ -190,6 +191,13 @@
* @property {PatternBasic} tree
*
* ============================================================================
* Extended Cohort Types (with address count)
* ============================================================================
*
* Basic cohort with address count (for "type" cohorts)
* @typedef {CohortBasic & { addrCount: Brk.MetricPattern1<Brk.StoredU64> }} CohortAddress
*
* ============================================================================
* Cohort Group Types (by capability)
* ============================================================================
*
@@ -233,23 +241,6 @@
* @property {readonly AddressCohortObject[]} list
*
* @typedef {UtxoCohortGroupObject | AddressCohortGroupObject} CohortGroupObject
*
* @typedef {Object} PartialContext
* @property {Colors} colors
* @property {BrkClient} brk
* @property {(pattern: BlockCountPattern<any>, title: string, color?: Color) => AnyFetchedSeriesBlueprint[]} fromBlockCount
* @property {(pattern: FullnessPattern<any>, title: string, color?: Color) => AnyFetchedSeriesBlueprint[]} fromBitcoin
* @property {(pattern: AnyStatsPattern, title: string, color?: Color) => AnyFetchedSeriesBlueprint[]} fromBlockSize
* @property {(pattern: AnyStatsPattern, unit: Unit, title?: string) => AnyFetchedSeriesBlueprint[]} fromSizePattern
* @property {(pattern: FullnessPattern<any>, unit: Unit, title?: string) => AnyFetchedSeriesBlueprint[]} fromFullnessPattern
* @property {(pattern: DollarsPattern<any>, unit: Unit, title?: string) => AnyFetchedSeriesBlueprint[]} fromDollarsPattern
* @property {(pattern: FeeRatePattern<any>, unit: Unit, title?: string) => AnyFetchedSeriesBlueprint[]} fromFeeRatePattern
* @property {(pattern: CoinbasePattern, title: string) => AnyFetchedSeriesBlueprint[]} fromCoinbasePattern
* @property {(pattern: ValuePattern, title: string, sumColor?: Color, cumulativeColor?: Color) => AnyFetchedSeriesBlueprint[]} fromValuePattern
* @property {(pattern: { sum: AnyMetricPattern, cumulative: AnyMetricPattern }, title: string, unit: Unit, sumColor?: Color, cumulativeColor?: Color) => AnyFetchedSeriesBlueprint[]} fromBitcoinPatternWithUnit
* @property {(pattern: BlockCountPattern<any>, unit: Unit, title?: string, sumColor?: Color, cumulativeColor?: Color) => AnyFetchedSeriesBlueprint[]} fromBlockCountWithUnit
* @property {(pattern: IntervalPattern, unit: Unit, title?: string, color?: Color) => AnyFetchedSeriesBlueprint[]} fromIntervalPattern
* @property {(pattern: SupplyPattern, title: string, color?: Color) => AnyFetchedSeriesBlueprint[]} fromSupplyPattern
*/
// Re-export for type consumers

View File

@@ -14,7 +14,7 @@
*
* @import { WebSockets } from "./utils/ws.js"
*
* @import { Option, PartialChartOption, ChartOption, AnyPartialOption, ProcessedOptionAddons, OptionsTree, SimulationOption, AnySeriesBlueprint, SeriesType, AnyFetchedSeriesBlueprint, TableOption, ExplorerOption, UrlOption, PartialOptionsGroup, OptionsGroup, PartialOptionsTree, UtxoCohortObject, AddressCohortObject, CohortObject, CohortGroupObject, FetchedLineSeriesBlueprint, FetchedBaselineSeriesBlueprint, FetchedHistogramSeriesBlueprint, PartialContext, PatternAll, PatternFull, PatternWithAdjusted, PatternWithPercentiles, PatternBasic, CohortAll, CohortFull, CohortWithAdjusted, CohortWithPercentiles, CohortBasic, CohortGroupFull, CohortGroupWithAdjusted, CohortGroupWithPercentiles, CohortGroupBasic, UtxoCohortGroupObject, AddressCohortGroupObject, FetchedDotsSeriesBlueprint, FetchedCandlestickSeriesBlueprint } from "./options/partial.js"
* @import { Option, PartialChartOption, ChartOption, AnyPartialOption, ProcessedOptionAddons, OptionsTree, SimulationOption, AnySeriesBlueprint, SeriesType, AnyFetchedSeriesBlueprint, TableOption, ExplorerOption, UrlOption, PartialOptionsGroup, OptionsGroup, PartialOptionsTree, UtxoCohortObject, AddressCohortObject, CohortObject, CohortGroupObject, FetchedLineSeriesBlueprint, FetchedBaselineSeriesBlueprint, FetchedHistogramSeriesBlueprint, PartialContext, PatternAll, PatternFull, PatternWithAdjusted, PatternWithPercentiles, PatternBasic, CohortAll, CohortFull, CohortWithAdjusted, CohortWithPercentiles, CohortBasic, CohortAddress, CohortGroupFull, CohortGroupWithAdjusted, CohortGroupWithPercentiles, CohortGroupBasic, UtxoCohortGroupObject, AddressCohortGroupObject, FetchedDotsSeriesBlueprint, FetchedCandlestickSeriesBlueprint } from "./options/partial.js"
*
*
* @import { UnitObject as Unit } from "./utils/units.js"
@@ -41,7 +41,7 @@
* @typedef {Brk._10yTo12yPattern} AgeRangePattern
* @typedef {Brk._0satsPattern2} UtxoAmountPattern
* @typedef {Brk._0satsPattern} AddressAmountPattern
* @typedef {Brk._100btcPattern} BasicUtxoPattern
* @typedef {Brk._0Pattern} BasicUtxoPattern
* @typedef {Brk._0satsPattern2} EpochPattern
* @typedef {Brk.Ratio1ySdPattern} Ratio1ySdPattern
* @typedef {Brk.Dollars} Dollars
@@ -53,6 +53,8 @@
* @typedef {Brk.AnyMetricEndpointBuilder} AnyMetricEndpoint
* @typedef {Brk.AnyMetricData} AnyMetricData
* @typedef {Brk.AddrCountPattern} AddrCountPattern
* @typedef {keyof Brk.MetricsTree_Distribution_UtxoCohorts_Type} SpendableType
* @typedef {keyof Brk.MetricsTree_Distribution_AnyAddressIndexes} AddressableType
* @typedef {FullnessPattern<any>} IntervalPattern
* @typedef {Brk.MetricsTree_Supply_Circulating} SupplyPattern
* @typedef {Brk.RelativePattern} GlobalRelativePattern

View File

@@ -31,7 +31,7 @@ export const numberToPercentage = new Intl.NumberFormat("en-US", {
export function numberToShortUSFormat(value, digits) {
const absoluteValue = Math.abs(value);
if (isNaN(value)) {
if (isNaN(value) || !isFinite(value)) {
return "";
} else if (absoluteValue < 10) {
return numberToUSNumber(value, Math.min(3, digits || 10));
@@ -41,13 +41,13 @@ export function numberToShortUSFormat(value, digits) {
return numberToUSNumber(value, Math.min(1, digits || 10));
} else if (absoluteValue < 1_000_000) {
return numberToUSNumber(value, 0);
} else if (absoluteValue >= 1_000_000_000_000_000_000_000) {
} else if (absoluteValue >= 1e27) {
return "Inf.";
}
const log = Math.floor(Math.log10(absoluteValue) - 6);
const suffices = ["M", "B", "T", "P", "E", "Z"];
const suffices = ["M", "B", "T", "P", "E", "Z", "Y"];
const letterIndex = Math.floor(log / 3);
const letter = suffices[letterIndex];

View File

@@ -16,7 +16,7 @@ export const Unit = /** @type {const} */ ({
sd: { id: "sd", name: "Std Dev" },
// Relative percentages
pctSupply: { id: "pct-supply", name: "% of Supply" },
pctSupply: { id: "pct-supply", name: "% of circulating Supply" },
pctOwn: { id: "pct-own", name: "% of Own Supply" },
pctMcap: { id: "pct-mcap", name: "% of Market Cap" },
pctRcap: { id: "pct-rcap", name: "% of Realized Cap" },