mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-25 07:09:59 -07:00
global: snap
This commit is contained in:
@@ -353,6 +353,17 @@ export function createCointimeSection() {
|
||||
{
|
||||
name: "Indicators",
|
||||
tree: [
|
||||
{
|
||||
name: "AVIV",
|
||||
title: "AVIV Ratio",
|
||||
bottom: [
|
||||
line({
|
||||
series: cap.aviv.ratio,
|
||||
name: "AVIV",
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Reserve Risk",
|
||||
title: "Reserve Risk",
|
||||
@@ -365,22 +376,11 @@ export function createCointimeSection() {
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "AVIV",
|
||||
title: "AVIV Ratio",
|
||||
bottom: [
|
||||
line({
|
||||
series: cap.aviv.ratio,
|
||||
name: "AVIV",
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
name: "Cointime-Adjusted",
|
||||
name: "Adjusted",
|
||||
tree: [
|
||||
{
|
||||
name: "Inflation",
|
||||
|
||||
@@ -4,14 +4,14 @@ import { brk } from "../../utils/client.js";
|
||||
|
||||
/** @type {readonly AddressableType[]} */
|
||||
const ADDRESSABLE_TYPES = [
|
||||
"p2pk65",
|
||||
"p2pk33",
|
||||
"p2pkh",
|
||||
"p2sh",
|
||||
"p2wpkh",
|
||||
"p2wsh",
|
||||
"p2tr",
|
||||
"p2a",
|
||||
"p2tr",
|
||||
"p2wsh",
|
||||
"p2wpkh",
|
||||
"p2sh",
|
||||
"p2pkh",
|
||||
"p2pk33",
|
||||
"p2pk65",
|
||||
];
|
||||
|
||||
/** @type {(key: SpendableType) => key is AddressableType} */
|
||||
@@ -162,12 +162,13 @@ export function buildCohortData() {
|
||||
},
|
||||
);
|
||||
|
||||
const typeAddressable = ADDRESSABLE_TYPES.map((key, i, arr) => {
|
||||
const typeAddressable = ADDRESSABLE_TYPES.map((key) => {
|
||||
const names = SPENDABLE_TYPE_NAMES[key];
|
||||
return {
|
||||
key,
|
||||
name: names.short,
|
||||
title: names.short,
|
||||
color: colors.at(i, arr.length),
|
||||
color: colors.scriptType[key],
|
||||
tree: utxoCohorts.type[key],
|
||||
addressCount: {
|
||||
base: addrs.funded[key],
|
||||
@@ -178,10 +179,11 @@ export function buildCohortData() {
|
||||
|
||||
const typeOther = entries(SPENDABLE_TYPE_NAMES)
|
||||
.filter(([key]) => !isAddressable(key))
|
||||
.map(([key, names], i, arr) => ({
|
||||
.map(([key, names]) => ({
|
||||
key,
|
||||
name: names.short,
|
||||
title: names.short,
|
||||
color: colors.at(i, arr.length),
|
||||
color: colors.scriptType[key],
|
||||
tree: utxoCohorts.type[key],
|
||||
}));
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
} from "../series.js";
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { colors } from "../../utils/colors.js";
|
||||
import { brk } from "../../utils/client.js";
|
||||
|
||||
// Section builders
|
||||
import {
|
||||
@@ -743,3 +744,19 @@ export function createUtxoProfitabilitySection({ range, profit, loss }) {
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gini leaf for Distribution > Address Balance
|
||||
* @returns {AnyPartialOption}
|
||||
*/
|
||||
export function createAddressBalanceGiniLeaf() {
|
||||
return {
|
||||
name: "Gini",
|
||||
title: "Address Balance Gini Coefficient",
|
||||
bottom: percentRatio({
|
||||
pattern: brk.series.indicators.gini,
|
||||
name: "Gini",
|
||||
color: colors.loss,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -653,120 +653,117 @@ export function createMarketSection() {
|
||||
],
|
||||
},
|
||||
|
||||
// Momentum
|
||||
// RSI
|
||||
{
|
||||
name: "Momentum",
|
||||
name: "RSI",
|
||||
tree: [
|
||||
{
|
||||
name: "RSI",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: "RSI",
|
||||
bottom: [
|
||||
...ROLLING_WINDOWS_TO_1M.flatMap((w) =>
|
||||
indexRatio({
|
||||
pattern: technical.rsi[w.key].rsi,
|
||||
name: w.name,
|
||||
color: w.color,
|
||||
}),
|
||||
),
|
||||
priceLine({ unit: Unit.index, number: 70 }),
|
||||
priceLine({ unit: Unit.index, number: 30 }),
|
||||
],
|
||||
},
|
||||
...ROLLING_WINDOWS_TO_1M.map((w) => {
|
||||
const rsi = technical.rsi[w.key];
|
||||
return {
|
||||
name: "Compare",
|
||||
title: "RSI",
|
||||
bottom: [
|
||||
...ROLLING_WINDOWS_TO_1M.flatMap((w) =>
|
||||
indexRatio({
|
||||
pattern: technical.rsi[w.key].rsi,
|
||||
name: w.name,
|
||||
title: `${w.title} RSI`,
|
||||
bottom: [
|
||||
...indexRatio({
|
||||
pattern: rsi.rsi,
|
||||
name: "RSI",
|
||||
color: colors.indicator.main,
|
||||
}),
|
||||
priceLine({ unit: Unit.index, number: 70 }),
|
||||
priceLine({
|
||||
unit: Unit.index,
|
||||
number: 50,
|
||||
defaultActive: false,
|
||||
}),
|
||||
priceLine({ unit: Unit.index, number: 30 }),
|
||||
],
|
||||
};
|
||||
}),
|
||||
{
|
||||
name: "Stochastic",
|
||||
tree: ROLLING_WINDOWS_TO_1M.map((w) => {
|
||||
const rsi = technical.rsi[w.key];
|
||||
return {
|
||||
name: w.name,
|
||||
title: `${w.title} Stochastic RSI`,
|
||||
bottom: [
|
||||
...indexRatio({
|
||||
pattern: rsi.stochRsiK,
|
||||
name: "K",
|
||||
color: colors.indicator.fast,
|
||||
}),
|
||||
...indexRatio({
|
||||
pattern: rsi.stochRsiD,
|
||||
name: "D",
|
||||
color: colors.indicator.slow,
|
||||
}),
|
||||
...priceLines({
|
||||
unit: Unit.index,
|
||||
numbers: [80, 20],
|
||||
}),
|
||||
],
|
||||
};
|
||||
color: w.color,
|
||||
}),
|
||||
},
|
||||
),
|
||||
priceLine({ unit: Unit.index, number: 70 }),
|
||||
priceLine({ unit: Unit.index, number: 30 }),
|
||||
],
|
||||
},
|
||||
...ROLLING_WINDOWS_TO_1M.map((w) => {
|
||||
const rsi = technical.rsi[w.key];
|
||||
return {
|
||||
name: w.name,
|
||||
title: `${w.title} RSI`,
|
||||
bottom: [
|
||||
...indexRatio({
|
||||
pattern: rsi.rsi,
|
||||
name: "RSI",
|
||||
color: colors.indicator.main,
|
||||
}),
|
||||
priceLine({ unit: Unit.index, number: 70 }),
|
||||
priceLine({
|
||||
unit: Unit.index,
|
||||
number: 50,
|
||||
defaultActive: false,
|
||||
}),
|
||||
priceLine({ unit: Unit.index, number: 30 }),
|
||||
],
|
||||
};
|
||||
}),
|
||||
{
|
||||
name: "MACD",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: "MACD",
|
||||
bottom: ROLLING_WINDOWS_TO_1M.map((w) =>
|
||||
line({
|
||||
series: technical.macd[w.key].line,
|
||||
name: w.name,
|
||||
color: w.color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
},
|
||||
...ROLLING_WINDOWS_TO_1M.map((w) => ({
|
||||
name: "Stochastic",
|
||||
tree: ROLLING_WINDOWS_TO_1M.map((w) => {
|
||||
const rsi = technical.rsi[w.key];
|
||||
return {
|
||||
name: w.name,
|
||||
title: `${w.title} MACD`,
|
||||
title: `${w.title} Stochastic RSI`,
|
||||
bottom: [
|
||||
line({
|
||||
series: technical.macd[w.key].line,
|
||||
name: "MACD",
|
||||
...indexRatio({
|
||||
pattern: rsi.stochRsiK,
|
||||
name: "K",
|
||||
color: colors.indicator.fast,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
series: technical.macd[w.key].signal,
|
||||
name: "Signal",
|
||||
...indexRatio({
|
||||
pattern: rsi.stochRsiD,
|
||||
name: "D",
|
||||
color: colors.indicator.slow,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
histogram({
|
||||
series: technical.macd[w.key].histogram,
|
||||
name: "Histogram",
|
||||
unit: Unit.usd,
|
||||
...priceLines({
|
||||
unit: Unit.index,
|
||||
numbers: [80, 20],
|
||||
}),
|
||||
],
|
||||
})),
|
||||
],
|
||||
};
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// MACD
|
||||
{
|
||||
name: "MACD",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: "MACD",
|
||||
bottom: ROLLING_WINDOWS_TO_1M.map((w) =>
|
||||
line({
|
||||
series: technical.macd[w.key].line,
|
||||
name: w.name,
|
||||
color: w.color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
},
|
||||
...ROLLING_WINDOWS_TO_1M.map((w) => ({
|
||||
name: w.name,
|
||||
title: `${w.title} MACD`,
|
||||
bottom: [
|
||||
line({
|
||||
series: technical.macd[w.key].line,
|
||||
name: "MACD",
|
||||
color: colors.indicator.fast,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
series: technical.macd[w.key].signal,
|
||||
name: "Signal",
|
||||
color: colors.indicator.slow,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
histogram({
|
||||
series: technical.macd[w.key].histogram,
|
||||
name: "Histogram",
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
})),
|
||||
],
|
||||
},
|
||||
|
||||
// Volatility
|
||||
{
|
||||
name: "Volatility",
|
||||
@@ -925,201 +922,190 @@ export function createMarketSection() {
|
||||
name: "Indicators",
|
||||
tree: [
|
||||
{
|
||||
name: "Envelope",
|
||||
title: "Realized Envelope",
|
||||
top: priceBands(percentileBands(indicators.realizedEnvelope), {
|
||||
defaultActive: true,
|
||||
name: "Rarity Meter",
|
||||
tree: /** @type {const} */ ([
|
||||
{ key: "full", name: "Full", title: "Rarity Meter" },
|
||||
{ key: "local", name: "Local", title: "Local Rarity Meter" },
|
||||
{ key: "cycle", name: "Cycle", title: "Cycle Rarity Meter" },
|
||||
]).map((v) => {
|
||||
const m = indicators.rarityMeter[v.key];
|
||||
return {
|
||||
name: v.name,
|
||||
title: v.title,
|
||||
top: priceBands(percentileBands(m), { defaultActive: true }),
|
||||
bottom: [
|
||||
histogram({
|
||||
series: m.index,
|
||||
name: "Index",
|
||||
unit: Unit.count,
|
||||
colorFn: (v) =>
|
||||
/** @type {const} */ ([
|
||||
colors.ratioPct._0_5,
|
||||
colors.ratioPct._1,
|
||||
colors.ratioPct._2,
|
||||
colors.ratioPct._5,
|
||||
colors.transparent,
|
||||
colors.ratioPct._95,
|
||||
colors.ratioPct._98,
|
||||
colors.ratioPct._99,
|
||||
colors.ratioPct._99_5,
|
||||
])[v + 4],
|
||||
}),
|
||||
baseline({
|
||||
series: m.score,
|
||||
name: "Score",
|
||||
unit: Unit.count,
|
||||
color: [colors.ratioPct._99, colors.ratioPct._1],
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
};
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "NVT",
|
||||
title: "NVT Ratio",
|
||||
bottom: [
|
||||
histogram({
|
||||
series: indicators.realizedEnvelope.index,
|
||||
name: "Index",
|
||||
unit: Unit.count,
|
||||
colorFn: (v) =>
|
||||
/** @type {const} */ ([
|
||||
colors.ratioPct._0_5,
|
||||
colors.ratioPct._1,
|
||||
colors.ratioPct._2,
|
||||
colors.ratioPct._5,
|
||||
colors.transparent,
|
||||
colors.ratioPct._95,
|
||||
colors.ratioPct._98,
|
||||
colors.ratioPct._99,
|
||||
colors.ratioPct._99_5,
|
||||
])[v + 4],
|
||||
line({
|
||||
series: indicators.nvt.ratio,
|
||||
name: "NVT",
|
||||
color: colors.bitcoin,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Thermocap Multiple",
|
||||
title: "Thermocap Multiple",
|
||||
bottom: [
|
||||
line({
|
||||
series: indicators.thermoCapMultiple.ratio,
|
||||
name: "Thermocap",
|
||||
color: colors.bitcoin,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Puell Multiple",
|
||||
title: "Puell Multiple",
|
||||
bottom: [
|
||||
line({
|
||||
series: indicators.puellMultiple.ratio,
|
||||
name: "Puell",
|
||||
color: colors.usd,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "RHODL Ratio",
|
||||
title: "RHODL Ratio",
|
||||
bottom: [
|
||||
line({
|
||||
series: indicators.rhodlRatio.ratio,
|
||||
name: "RHODL",
|
||||
color: colors.bitcoin,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Stock-to-Flow",
|
||||
title: "Stock-to-Flow",
|
||||
bottom: [
|
||||
line({
|
||||
series: indicators.stockToFlow,
|
||||
name: "S2F",
|
||||
color: colors.bitcoin,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Pi Cycle",
|
||||
title: "Pi Cycle",
|
||||
top: [
|
||||
price({
|
||||
series: ma.sma._111d,
|
||||
name: "111d SMA",
|
||||
color: colors.indicator.upper,
|
||||
}),
|
||||
price({
|
||||
series: ma.sma._350d.x2,
|
||||
name: "350d SMA x2",
|
||||
color: colors.indicator.lower,
|
||||
}),
|
||||
],
|
||||
bottom: [
|
||||
baseline({
|
||||
series: indicators.realizedEnvelope.score,
|
||||
name: "Score",
|
||||
unit: Unit.count,
|
||||
color: [colors.ratioPct._99, colors.ratioPct._1],
|
||||
series: technical.piCycle.ratio,
|
||||
name: "Pi Cycle",
|
||||
unit: Unit.ratio,
|
||||
base: 1,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Dormancy",
|
||||
title: "Dormancy",
|
||||
bottom: [
|
||||
line({
|
||||
series: indicators.dormancy.supplyAdj,
|
||||
name: "Supply Adjusted",
|
||||
color: colors.bitcoin,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
line({
|
||||
series: indicators.dormancy.flow,
|
||||
name: "Flow",
|
||||
color: colors.usd,
|
||||
unit: Unit.ratio,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Valuation",
|
||||
tree: [
|
||||
{
|
||||
name: "NVT",
|
||||
title: "NVT Ratio",
|
||||
bottom: [
|
||||
line({
|
||||
series: indicators.nvt.ratio,
|
||||
name: "NVT",
|
||||
color: colors.bitcoin,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Thermocap Multiple",
|
||||
title: "Thermocap Multiple",
|
||||
bottom: [
|
||||
line({
|
||||
series: indicators.thermoCapMultiple.ratio,
|
||||
name: "Thermocap",
|
||||
color: colors.bitcoin,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
name: "Seller Exhaustion",
|
||||
title: "Seller Exhaustion Constant",
|
||||
bottom: [
|
||||
line({
|
||||
series: indicators.sellerExhaustion,
|
||||
name: "SEC",
|
||||
color: colors.bitcoin,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Cycle",
|
||||
name: "Coin Destruction",
|
||||
tree: [
|
||||
{
|
||||
name: "Pi Cycle",
|
||||
title: "Pi Cycle",
|
||||
top: [
|
||||
price({
|
||||
series: ma.sma._111d,
|
||||
name: "111d SMA",
|
||||
color: colors.indicator.upper,
|
||||
}),
|
||||
price({
|
||||
series: ma.sma._350d.x2,
|
||||
name: "350d SMA x2",
|
||||
color: colors.indicator.lower,
|
||||
}),
|
||||
],
|
||||
bottom: [
|
||||
baseline({
|
||||
series: technical.piCycle.ratio,
|
||||
name: "Pi Cycle",
|
||||
unit: Unit.ratio,
|
||||
base: 1,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Stock-to-Flow",
|
||||
title: "Stock-to-Flow",
|
||||
bottom: [
|
||||
line({
|
||||
series: indicators.stockToFlow,
|
||||
name: "S2F",
|
||||
color: colors.bitcoin,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Puell Multiple",
|
||||
title: "Puell Multiple",
|
||||
bottom: [
|
||||
line({
|
||||
series: indicators.puellMultiple.ratio,
|
||||
name: "Puell",
|
||||
color: colors.usd,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "RHODL Ratio",
|
||||
title: "RHODL Ratio",
|
||||
bottom: [
|
||||
line({
|
||||
series: indicators.rhodlRatio.ratio,
|
||||
name: "RHODL",
|
||||
color: colors.bitcoin,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Activity",
|
||||
tree: [
|
||||
{
|
||||
name: "Dormancy",
|
||||
title: "Dormancy",
|
||||
bottom: [
|
||||
line({
|
||||
series: indicators.dormancy.supplyAdj,
|
||||
name: "Supply Adjusted",
|
||||
color: colors.bitcoin,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
line({
|
||||
series: indicators.dormancy.flow,
|
||||
name: "Flow",
|
||||
color: colors.usd,
|
||||
unit: Unit.ratio,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Seller Exhaustion",
|
||||
title: "Seller Exhaustion Constant",
|
||||
bottom: [
|
||||
line({
|
||||
series: indicators.sellerExhaustion,
|
||||
name: "SEC",
|
||||
color: colors.bitcoin,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "CDD Supply Adjusted",
|
||||
name: "CDD",
|
||||
title: "Coindays Destroyed (Supply Adjusted)",
|
||||
bottom: [
|
||||
line({
|
||||
series: indicators.coindaysDestroyedSupplyAdj,
|
||||
name: "CDD SA",
|
||||
name: "CDD",
|
||||
color: colors.bitcoin,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "CYD Supply Adjusted",
|
||||
name: "CYD",
|
||||
title: "Coinyears Destroyed (Supply Adjusted)",
|
||||
bottom: [
|
||||
line({
|
||||
series: indicators.coinyearsDestroyedSupplyAdj,
|
||||
name: "CYD SA",
|
||||
color: colors.bitcoin,
|
||||
name: "CYD",
|
||||
color: colors.usd,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Gini",
|
||||
title: "Gini Coefficient",
|
||||
bottom: percentRatio({
|
||||
pattern: indicators.gini,
|
||||
name: "Gini",
|
||||
color: colors.loss,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -190,9 +190,10 @@ export function createMiningSection() {
|
||||
/**
|
||||
* @param {string} groupTitle
|
||||
* @param {typeof majorPoolData} poolList
|
||||
* @param {string} [name]
|
||||
*/
|
||||
const createPoolCompare = (groupTitle, poolList) => ({
|
||||
name: "Compare",
|
||||
const createPoolCompare = (groupTitle, poolList, name = "Compare") => ({
|
||||
name,
|
||||
tree: [
|
||||
{
|
||||
name: "Dominance",
|
||||
@@ -602,7 +603,7 @@ export function createMiningSection() {
|
||||
{
|
||||
name: "Pools",
|
||||
tree: [
|
||||
createPoolCompare("Major Pools", featuredPools),
|
||||
createPoolCompare("Major Pools", featuredPools, "Featured"),
|
||||
{
|
||||
name: "AntPool & Friends",
|
||||
tree: [
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Unit } from "../utils/units.js";
|
||||
import { entries } from "../utils/array.js";
|
||||
import {
|
||||
line,
|
||||
baseline,
|
||||
fromSupplyPattern,
|
||||
chartsFromFull,
|
||||
chartsFromFullPerBlock,
|
||||
@@ -14,6 +15,7 @@ import {
|
||||
chartsFromPercentCumulative,
|
||||
chartsFromPercentCumulativeEntries,
|
||||
chartsFromAggregatedPerBlock,
|
||||
distributionWindowsTree,
|
||||
averagesArray,
|
||||
simpleDeltaTree,
|
||||
ROLLING_WINDOWS,
|
||||
@@ -91,39 +93,6 @@ export function createNetworkSection() {
|
||||
{ key: "reactivated", name: "Reactivated" },
|
||||
]);
|
||||
|
||||
const countTypes = /** @type {const} */ ([
|
||||
{
|
||||
name: "Funded",
|
||||
title: "Address Count by Type",
|
||||
/** @param {AddressableType} t */
|
||||
getSeries: (t) => addrs.funded[t],
|
||||
},
|
||||
{
|
||||
name: "Empty",
|
||||
title: "Empty Address Count by Type",
|
||||
/** @param {AddressableType} t */
|
||||
getSeries: (t) => addrs.empty[t],
|
||||
},
|
||||
{
|
||||
name: "Total",
|
||||
title: "Total Address Count by Type",
|
||||
/** @param {AddressableType} t */
|
||||
getSeries: (t) => addrs.total[t],
|
||||
},
|
||||
{
|
||||
name: "Funded Reused",
|
||||
title: "Funded Reused Address Count by Type",
|
||||
/** @param {AddressableType} t */
|
||||
getSeries: (t) => addrs.reused.count.funded[t],
|
||||
},
|
||||
{
|
||||
name: "Total Reused",
|
||||
title: "Total Reused Address Count by Type",
|
||||
/** @param {AddressableType} t */
|
||||
getSeries: (t) => addrs.reused.count.total[t],
|
||||
},
|
||||
]);
|
||||
|
||||
const countMetrics = /** @type {const} */ ([
|
||||
{ key: "funded", name: "Funded", color: undefined },
|
||||
{ key: "empty", name: "Empty", color: colors.gray },
|
||||
@@ -543,6 +512,261 @@ export function createNetworkSection() {
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Mirror of the per-type singles tree, but every leaf is a cross-type
|
||||
* comparison chart (same metric, same unit, one line per addr type).
|
||||
* Structure parallels `createAddressSeriesTreeForType` section-by-section
|
||||
* so users can compare anything they can view on a single type.
|
||||
*/
|
||||
const createAddressByTypeCompare = () => {
|
||||
const typeLines =
|
||||
/**
|
||||
* @param {(t: (typeof addressTypes)[number]) => AnySeriesPattern} getSeries
|
||||
* @param {Unit} [unit]
|
||||
*/
|
||||
(getSeries, unit = Unit.count) =>
|
||||
addressTypes.map((t) =>
|
||||
line({
|
||||
series: getSeries(t),
|
||||
name: t.name,
|
||||
color: t.color,
|
||||
unit,
|
||||
defaultActive: t.defaultActive,
|
||||
}),
|
||||
);
|
||||
|
||||
const typeBaselines =
|
||||
/**
|
||||
* @param {(t: (typeof addressTypes)[number]) => AnySeriesPattern} getSeries
|
||||
* @param {Unit} [unit]
|
||||
*/
|
||||
(getSeries, unit = Unit.count) =>
|
||||
addressTypes.map((t) =>
|
||||
baseline({
|
||||
series: getSeries(t),
|
||||
name: t.name,
|
||||
color: t.color,
|
||||
unit,
|
||||
defaultActive: t.defaultActive,
|
||||
}),
|
||||
);
|
||||
|
||||
return {
|
||||
name: "Compare",
|
||||
tree: [
|
||||
// Count (lifetime Funded/Empty/Total)
|
||||
{
|
||||
name: "Count",
|
||||
tree: countMetrics.map((m) => ({
|
||||
name: m.name,
|
||||
title: `${m.name} Address Count by Type`,
|
||||
bottom: typeLines((t) => addrs[m.key][t.key]),
|
||||
})),
|
||||
},
|
||||
|
||||
// New (rolling sums + cumulative)
|
||||
{
|
||||
name: "New",
|
||||
tree: groupedWindowsCumulative({
|
||||
list: addressTypes,
|
||||
title: (s) => s,
|
||||
metricTitle: "New Addresses by Type",
|
||||
getWindowSeries: (t, key) => addrs.new[t.key].sum[key],
|
||||
getCumulativeSeries: (t) => addrs.new[t.key].cumulative,
|
||||
seriesFn: line,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
},
|
||||
|
||||
// Change (rolling deltas, signed, baseline)
|
||||
{
|
||||
name: "Change",
|
||||
tree: ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: `${w.title} Address Count Change by Type`,
|
||||
bottom: typeBaselines(
|
||||
(t) => addrs.delta[t.key].absolute[w.key],
|
||||
),
|
||||
})),
|
||||
},
|
||||
|
||||
// Growth Rate (rolling percent rates)
|
||||
{
|
||||
name: "Growth Rate",
|
||||
tree: ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: `${w.title} Address Growth Rate by Type`,
|
||||
bottom: typeLines(
|
||||
(t) => addrs.delta[t.key].rate[w.key].percent,
|
||||
Unit.percentage,
|
||||
),
|
||||
})),
|
||||
},
|
||||
|
||||
// Activity (per activity type, per window)
|
||||
{
|
||||
name: "Activity",
|
||||
tree: activityTypes.map((a) => ({
|
||||
name: a.name,
|
||||
tree: ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: `${w.title} ${a.name} Addresses by Type`,
|
||||
bottom: typeLines(
|
||||
(t) => addrs.activity[t.key][a.key][w.key],
|
||||
),
|
||||
})),
|
||||
})),
|
||||
},
|
||||
|
||||
// Reused
|
||||
{
|
||||
name: "Reused",
|
||||
tree: [
|
||||
{
|
||||
name: "Funded",
|
||||
title: "Funded Reused Address Count by Type",
|
||||
bottom: typeLines((t) => addrs.reused.count.funded[t.key]),
|
||||
},
|
||||
{
|
||||
name: "Total",
|
||||
title: "Total Reused Address Count by Type",
|
||||
bottom: typeLines((t) => addrs.reused.count.total[t.key]),
|
||||
},
|
||||
{
|
||||
name: "Outputs",
|
||||
tree: [
|
||||
{
|
||||
name: "Count",
|
||||
tree: groupedWindowsCumulative({
|
||||
list: addressTypes,
|
||||
title: (s) => s,
|
||||
metricTitle:
|
||||
"Transaction Outputs to Reused Addresses by Type",
|
||||
getWindowSeries: (t, key) =>
|
||||
addrs.reused.events.outputToReusedAddrCount[t.key].sum[
|
||||
key
|
||||
],
|
||||
getCumulativeSeries: (t) =>
|
||||
addrs.reused.events.outputToReusedAddrCount[t.key]
|
||||
.cumulative,
|
||||
seriesFn: line,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Share",
|
||||
tree: [
|
||||
...ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: `${w.title} Share of Transaction Outputs to Reused Addresses by Type`,
|
||||
bottom: typeLines(
|
||||
(t) =>
|
||||
addrs.reused.events.outputToReusedAddrShare[t.key][
|
||||
w.key
|
||||
].percent,
|
||||
Unit.percentage,
|
||||
),
|
||||
})),
|
||||
{
|
||||
name: "Cumulative",
|
||||
title:
|
||||
"Cumulative Share of Transaction Outputs to Reused Addresses by Type",
|
||||
bottom: typeLines(
|
||||
(t) =>
|
||||
addrs.reused.events.outputToReusedAddrShare[t.key]
|
||||
.percent,
|
||||
Unit.percentage,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Inputs",
|
||||
tree: [
|
||||
{
|
||||
name: "Count",
|
||||
tree: groupedWindowsCumulative({
|
||||
list: addressTypes,
|
||||
title: (s) => s,
|
||||
metricTitle:
|
||||
"Transaction Inputs from Reused Addresses by Type",
|
||||
getWindowSeries: (t, key) =>
|
||||
addrs.reused.events.inputFromReusedAddrCount[t.key].sum[
|
||||
key
|
||||
],
|
||||
getCumulativeSeries: (t) =>
|
||||
addrs.reused.events.inputFromReusedAddrCount[t.key]
|
||||
.cumulative,
|
||||
seriesFn: line,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Share",
|
||||
tree: [
|
||||
...ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: `${w.title} Share of Transaction Inputs from Reused Addresses by Type`,
|
||||
bottom: typeLines(
|
||||
(t) =>
|
||||
addrs.reused.events.inputFromReusedAddrShare[t.key][
|
||||
w.key
|
||||
].percent,
|
||||
Unit.percentage,
|
||||
),
|
||||
})),
|
||||
{
|
||||
name: "Cumulative",
|
||||
title:
|
||||
"Cumulative Share of Transaction Inputs from Reused Addresses by Type",
|
||||
bottom: typeLines(
|
||||
(t) =>
|
||||
addrs.reused.events.inputFromReusedAddrShare[t.key]
|
||||
.percent,
|
||||
Unit.percentage,
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// Exposed
|
||||
{
|
||||
name: "Exposed",
|
||||
tree: [
|
||||
{
|
||||
name: "Funded",
|
||||
title: "Funded Exposed Address Count by Type",
|
||||
bottom: typeLines((t) => addrs.exposed.count.funded[t.key]),
|
||||
},
|
||||
{
|
||||
name: "Total",
|
||||
title: "Total Exposed Address Count by Type",
|
||||
bottom: typeLines((t) => addrs.exposed.count.total[t.key]),
|
||||
},
|
||||
{
|
||||
name: "Supply",
|
||||
title: "Supply in Exposed Addresses by Type",
|
||||
bottom: addressTypes.flatMap((t) =>
|
||||
satsBtcUsd({
|
||||
pattern: addrs.exposed.supply[t.key],
|
||||
name: t.name,
|
||||
color: t.color,
|
||||
defaultActive: t.defaultActive,
|
||||
}),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Build a "By Type" subtree: Compare (count / tx count / tx %) plus a
|
||||
* per-type drill-down with the same three metrics.
|
||||
@@ -754,7 +978,7 @@ export function createNetworkSection() {
|
||||
name: "Unspendable",
|
||||
tree: [
|
||||
{
|
||||
name: "Total",
|
||||
name: "All",
|
||||
title: "Unspendable Supply",
|
||||
bottom: satsBtcUsdFrom({
|
||||
source: supply.burned,
|
||||
@@ -1002,19 +1226,74 @@ export function createNetworkSection() {
|
||||
tree: [
|
||||
{
|
||||
name: "Count",
|
||||
tree: chartsFromAggregatedPerBlock({
|
||||
pattern: outputs.count.total,
|
||||
metric: "Output Count",
|
||||
unit: Unit.count,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Spendable",
|
||||
tree: chartsFromCount({
|
||||
pattern: outputs.byType.spendableOutputCount,
|
||||
metric: "Spendable Output Count",
|
||||
unit: Unit.count,
|
||||
}),
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: "Output Count",
|
||||
bottom: ROLLING_WINDOWS.map((w) =>
|
||||
line({
|
||||
series: outputs.count.total.rolling.average[w.key],
|
||||
name: w.name,
|
||||
color: w.color,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
),
|
||||
},
|
||||
...ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: `${w.title} Output Count`,
|
||||
bottom: [
|
||||
line({
|
||||
series: outputs.count.total.rolling.sum[w.key],
|
||||
name: "Total (Sum)",
|
||||
color: w.color,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
line({
|
||||
series: outputs.byType.spendableOutputCount.sum[w.key],
|
||||
name: "Spendable (Sum)",
|
||||
color: colors.gray,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
line({
|
||||
series: outputs.count.total.rolling.average[w.key],
|
||||
name: "Total (Avg)",
|
||||
color: w.color,
|
||||
unit: Unit.count,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
series: outputs.byType.spendableOutputCount.average[w.key],
|
||||
name: "Spendable (Avg)",
|
||||
color: colors.gray,
|
||||
unit: Unit.count,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
})),
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: "Cumulative Output Count",
|
||||
bottom: [
|
||||
line({
|
||||
series: outputs.count.total.cumulative,
|
||||
name: "Total",
|
||||
unit: Unit.count,
|
||||
}),
|
||||
line({
|
||||
series: outputs.byType.spendableOutputCount.cumulative,
|
||||
name: "Spendable",
|
||||
color: colors.gray,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
],
|
||||
},
|
||||
distributionWindowsTree({
|
||||
pattern: outputs.count.total.rolling,
|
||||
metric: "Output Count per Block",
|
||||
unit: Unit.count,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Per Second",
|
||||
@@ -1105,22 +1384,7 @@ export function createNetworkSection() {
|
||||
{
|
||||
name: "By Type",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
tree: countTypes.map((c) => ({
|
||||
name: c.name,
|
||||
title: c.title,
|
||||
bottom: addressTypes.map((t) =>
|
||||
line({
|
||||
series: c.getSeries(t.key),
|
||||
name: t.name,
|
||||
color: t.color,
|
||||
unit: Unit.count,
|
||||
defaultActive: t.defaultActive,
|
||||
}),
|
||||
),
|
||||
})),
|
||||
},
|
||||
createAddressByTypeCompare(),
|
||||
...addressTypes.map((t) => ({
|
||||
name: t.name,
|
||||
tree: createAddressSeriesTreeForType(t.key, t.name),
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
createGroupedCohortFolderAddress,
|
||||
createGroupedAddressCohortFolder,
|
||||
createUtxoProfitabilitySection,
|
||||
createAddressBalanceGiniLeaf,
|
||||
} from "./distribution/index.js";
|
||||
import { createMarketSection } from "./market.js";
|
||||
import { createNetworkSection } from "./network.js";
|
||||
@@ -91,7 +92,7 @@ export function createPartialOptions() {
|
||||
name: "UTXO Age",
|
||||
tree: [
|
||||
{
|
||||
name: "Younger Than",
|
||||
name: "Under",
|
||||
tree: [
|
||||
createGroupedCohortFolderWithAdjusted({
|
||||
name: "Compare",
|
||||
@@ -103,7 +104,7 @@ export function createPartialOptions() {
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Older Than",
|
||||
name: "Over",
|
||||
tree: [
|
||||
createGroupedCohortFolderWithAdjusted({
|
||||
name: "Compare",
|
||||
@@ -133,7 +134,7 @@ export function createPartialOptions() {
|
||||
name: "UTXO Size",
|
||||
tree: [
|
||||
{
|
||||
name: "Less Than",
|
||||
name: "Under",
|
||||
tree: [
|
||||
createGroupedCohortFolderBasicWithMarketCap({
|
||||
name: "Compare",
|
||||
@@ -147,7 +148,7 @@ export function createPartialOptions() {
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "More Than",
|
||||
name: "Over",
|
||||
tree: [
|
||||
createGroupedCohortFolderBasicWithMarketCap({
|
||||
name: "Compare",
|
||||
@@ -187,7 +188,7 @@ export function createPartialOptions() {
|
||||
name: "Address Balance",
|
||||
tree: [
|
||||
{
|
||||
name: "Less Than",
|
||||
name: "Under",
|
||||
tree: [
|
||||
createGroupedAddressCohortFolder({
|
||||
name: "Compare",
|
||||
@@ -199,7 +200,7 @@ export function createPartialOptions() {
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "More Than",
|
||||
name: "Over",
|
||||
tree: [
|
||||
createGroupedAddressCohortFolder({
|
||||
name: "Compare",
|
||||
@@ -222,6 +223,7 @@ export function createPartialOptions() {
|
||||
...addressesAmountRange.map(createAddressCohortFolder),
|
||||
],
|
||||
},
|
||||
createAddressBalanceGiniLeaf(),
|
||||
],
|
||||
},
|
||||
|
||||
@@ -234,8 +236,25 @@ export function createPartialOptions() {
|
||||
list: typeAddressable,
|
||||
all: cohortAll,
|
||||
}),
|
||||
...typeAddressable.map(createCohortFolderAddress),
|
||||
...typeOther.map(createCohortFolderWithoutRelative),
|
||||
.../** @satisfies {readonly SpendableType[]} */ ([
|
||||
"p2a",
|
||||
"p2tr",
|
||||
"p2wsh",
|
||||
"p2wpkh",
|
||||
"p2sh",
|
||||
"p2ms",
|
||||
"p2pkh",
|
||||
"p2pk33",
|
||||
"p2pk65",
|
||||
"empty",
|
||||
"unknown",
|
||||
]).flatMap((key) => {
|
||||
const addr = typeAddressable.find((t) => t.key === key);
|
||||
if (addr) return [createCohortFolderAddress(addr)];
|
||||
const other = typeOther.find((t) => t.key === key);
|
||||
if (other) return [createCohortFolderWithoutRelative(other)];
|
||||
return [];
|
||||
}),
|
||||
],
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user