website: snapshot

This commit is contained in:
nym21
2026-02-03 23:43:52 +01:00
parent 277a0eb6a7
commit 0d5d7da70f
44 changed files with 2999 additions and 1591 deletions
+14 -13
View File
@@ -241,13 +241,14 @@ export function createChart({ parent, id: chartId, brk, fitContent }) {
);
// Debounced range persistence
ichart.timeScale().subscribeVisibleLogicalRangeChange(
debounce((range) => {
if (range && range.from < range.to) {
setRange({ from: range.from, to: range.to });
}
}, 100),
);
const debouncedSetRange = debounce((/** @type {Range | null} */ range) => {
if (range && range.from < range.to) {
setRange({ from: range.from, to: range.to });
}
}, 100);
// Cancel pending range saves on index change to prevent saving stale ranges to wrong index
index.onChange.add(() => debouncedSetRange.cancel());
ichart.timeScale().subscribeVisibleLogicalRangeChange(debouncedSetRange);
function applyColors() {
const defaultColor = colors.default();
@@ -734,8 +735,8 @@ export function createChart({ parent, id: chartId, brk, fitContent }) {
defaultActive,
options,
}) {
const upColor = customColors?.[0] ?? colors.green;
const downColor = customColors?.[1] ?? colors.red;
const upColor = customColors?.[0] ?? colors.bi.profitLoss[0];
const downColor = customColors?.[1] ?? colors.bi.profitLoss[1];
/** @type {CandlestickISeries} */
const candlestickISeries = /** @type {any} */ (
@@ -875,7 +876,7 @@ export function createChart({ parent, id: chartId, brk, fitContent }) {
metric,
name,
key,
color = [colors.green, colors.red],
color = colors.bi.profitLoss,
order,
unit,
paneIndex = 0,
@@ -1188,8 +1189,8 @@ export function createChart({ parent, id: chartId, brk, fitContent }) {
unit,
paneIndex: _paneIndex,
defaultActive,
topColor = colors.green,
bottomColor = colors.red,
topColor = colors.bi.profitLoss[0],
bottomColor = colors.bi.profitLoss[1],
options,
}) {
const paneIndex = _paneIndex ?? 0;
@@ -1351,7 +1352,7 @@ export function createChart({ parent, id: chartId, brk, fitContent }) {
const options = blueprint.options;
const indexes = Object.keys(blueprint.metric.by);
const defaultColor = unit === Unit.usd ? colors.green : colors.orange;
const defaultColor = unit === Unit.usd ? colors.usd : colors.bitcoin;
if (indexes.includes(idx)) {
switch (blueprint.type) {
+37 -37
View File
@@ -27,7 +27,7 @@ export function createCointimeSection() {
{
metric: all.realized.realizedCap,
name: "Realized",
color: colors.orange,
color: colors.realized,
},
]);
@@ -36,47 +36,47 @@ export function createCointimeSection() {
pricePattern: pricing.trueMarketMean,
ratio: pricing.trueMarketMeanRatio,
name: "True Market Mean",
color: colors.blue,
color: colors.trueMarketMean,
},
{
pricePattern: pricing.vaultedPrice,
ratio: pricing.vaultedPriceRatio,
name: "Vaulted",
color: colors.lime,
color: colors.vaulted,
},
{
pricePattern: pricing.activePrice,
ratio: pricing.activePriceRatio,
name: "Active",
color: colors.rose,
color: colors.active,
},
{
pricePattern: pricing.cointimePrice,
ratio: pricing.cointimePriceRatio,
name: "Cointime",
color: colors.yellow,
color: colors.cointime,
},
]);
const caps = /** @type {const} */ ([
{ metric: cap.vaultedCap, name: "Vaulted", color: colors.lime },
{ metric: cap.activeCap, name: "Active", color: colors.rose },
{ metric: cap.cointimeCap, name: "Cointime", color: colors.yellow },
{ metric: cap.investorCap, name: "Investor", color: colors.fuchsia },
{ metric: cap.thermoCap, name: "Thermo", color: colors.emerald },
{ metric: cap.vaultedCap, name: "Vaulted", color: colors.vaulted },
{ metric: cap.activeCap, name: "Active", color: colors.active },
{ metric: cap.cointimeCap, name: "Cointime", color: colors.cointime },
{ metric: cap.investorCap, name: "Investor", color: colors.investor },
{ metric: cap.thermoCap, name: "Thermo", color: colors.thermo },
]);
const supplyBreakdown = /** @type {const} */ ([
{ pattern: all.supply.total, name: "Total", color: colors.orange },
{ pattern: all.supply.total, name: "Total", color: colors.bitcoin },
{
pattern: cointimeSupply.vaultedSupply,
name: "Vaulted",
color: colors.lime,
color: colors.vaulted,
},
{
pattern: cointimeSupply.activeSupply,
name: "Active",
color: colors.rose,
color: colors.active,
},
]);
@@ -85,19 +85,19 @@ export function createCointimeSection() {
pattern: all.activity.coinblocksDestroyed,
name: "Destroyed",
title: "Coinblocks Destroyed",
color: colors.red,
color: colors.destroyed,
},
{
pattern: activity.coinblocksCreated,
name: "Created",
title: "Coinblocks Created",
color: colors.orange,
color: colors.created,
},
{
pattern: activity.coinblocksStored,
name: "Stored",
title: "Coinblocks Stored",
color: colors.green,
color: colors.stored,
},
]);
@@ -107,19 +107,19 @@ export function createCointimeSection() {
pattern: value.cointimeValueCreated,
name: "Created",
title: "Cointime Value Created",
color: colors.orange,
color: colors.created,
},
{
pattern: value.cointimeValueDestroyed,
name: "Destroyed",
title: "Cointime Value Destroyed",
color: colors.red,
color: colors.destroyed,
},
{
pattern: value.cointimeValueStored,
name: "Stored",
title: "Cointime Value Stored",
color: colors.green,
color: colors.stored,
},
]);
@@ -127,7 +127,7 @@ export function createCointimeSection() {
pattern: value.vocdd,
name: "VOCDD",
title: "Value of Coin Days Destroyed",
color: colors.purple,
color: colors.vocdd,
});
return {
@@ -144,12 +144,12 @@ export function createCointimeSection() {
price({
metric: all.realized.realizedPrice,
name: "Realized",
color: colors.orange,
color: colors.realized,
}),
price({
metric: all.realized.investorPrice,
name: "Investor",
color: colors.fuchsia,
color: colors.investor,
}),
...prices.map(({ pricePattern, name, color }) =>
price({ metric: pricePattern, name, color }),
@@ -168,7 +168,7 @@ export function createCointimeSection() {
price({
metric: all.realized.realizedPrice,
name: "Realized",
color: colors.orange,
color: colors.realized,
defaultActive: false,
}),
],
@@ -228,19 +228,19 @@ export function createCointimeSection() {
line({
metric: activity.liveliness,
name: "Liveliness",
color: colors.rose,
color: colors.liveliness,
unit: Unit.ratio,
}),
line({
metric: activity.vaultedness,
name: "Vaultedness",
color: colors.lime,
color: colors.vaulted,
unit: Unit.ratio,
}),
line({
metric: activity.activityToVaultednessRatio,
name: "L/V Ratio",
color: colors.purple,
color: colors.activity,
unit: Unit.ratio,
defaultActive: false,
}),
@@ -394,9 +394,9 @@ export function createCointimeSection() {
unit: Unit.usd,
}),
line({
metric: reserveRisk.vocdd365dSma,
name: "365d SMA",
color: colors.cyan,
metric: reserveRisk.vocdd365dMedian,
name: "365d Median",
color: colors.ma._1y,
unit: Unit.usd,
}),
],
@@ -429,7 +429,7 @@ export function createCointimeSection() {
line({
metric: reserveRisk.reserveRisk,
name: "Ratio",
color: colors.orange,
color: colors.reserveRisk,
unit: Unit.ratio,
}),
],
@@ -441,7 +441,7 @@ export function createCointimeSection() {
line({
metric: reserveRisk.hodlBank,
name: "Value",
color: colors.blue,
color: colors.hodlBank,
unit: Unit.usd,
}),
],
@@ -460,13 +460,13 @@ export function createCointimeSection() {
dots({
metric: supply.inflation,
name: "Base",
color: colors.orange,
color: colors.base,
unit: Unit.percentage,
}),
dots({
metric: adjusted.cointimeAdjInflationRate,
name: "Cointime-Adjusted",
color: colors.purple,
color: colors.adjusted,
unit: Unit.percentage,
}),
],
@@ -481,13 +481,13 @@ export function createCointimeSection() {
line({
metric: supply.velocity.btc,
name: "Base",
color: colors.orange,
color: colors.base,
unit: Unit.ratio,
}),
line({
metric: adjusted.cointimeAdjTxBtcVelocity,
name: "Cointime-Adjusted",
color: colors.red,
color: colors.adjusted,
unit: Unit.ratio,
}),
],
@@ -499,13 +499,13 @@ export function createCointimeSection() {
line({
metric: supply.velocity.usd,
name: "Base",
color: colors.emerald,
color: colors.thermo,
unit: Unit.ratio,
}),
line({
metric: adjusted.cointimeAdjTxUsdVelocity,
name: "Cointime-Adjusted",
color: colors.lime,
color: colors.vaulted,
unit: Unit.ratio,
}),
],
+44 -44
View File
@@ -242,52 +242,52 @@ function createRealizedPnlSection(args, title) {
line({
metric: realized.realizedProfit.sum,
name: "Profit",
color: colors.green,
color: colors.profit,
unit: Unit.usd,
}),
line({
metric: realized.realizedProfit7dEma,
name: "Profit 7d EMA",
color: colors.green,
color: colors.profit,
unit: Unit.usd,
}),
line({
metric: realized.realizedProfit.cumulative,
name: "Profit Cumulative",
color: colors.green,
color: colors.profit,
unit: Unit.usd,
defaultActive: false,
}),
line({
metric: realized.realizedLoss.sum,
name: "Loss",
color: colors.red,
color: colors.loss,
unit: Unit.usd,
}),
line({
metric: realized.realizedLoss7dEma,
name: "Loss 7d EMA",
color: colors.red,
color: colors.loss,
unit: Unit.usd,
}),
line({
metric: realized.realizedLoss.cumulative,
name: "Loss Cumulative",
color: colors.red,
color: colors.loss,
unit: Unit.usd,
defaultActive: false,
}),
line({
metric: realized.negRealizedLoss.sum,
name: "Negative Loss",
color: colors.red,
color: colors.loss,
unit: Unit.usd,
defaultActive: false,
}),
line({
metric: realized.negRealizedLoss.cumulative,
name: "Negative Loss Cumulative",
color: colors.red,
color: colors.loss,
unit: Unit.usd,
defaultActive: false,
}),
@@ -301,26 +301,26 @@ function createRealizedPnlSection(args, title) {
baseline({
metric: realized.realizedProfitRelToRealizedCap.sum,
name: "Profit",
color: colors.green,
color: colors.profit,
unit: Unit.pctRcap,
}),
baseline({
metric: realized.realizedProfitRelToRealizedCap.cumulative,
name: "Profit Cumulative",
color: colors.green,
color: colors.profit,
unit: Unit.pctRcap,
defaultActive: false,
}),
baseline({
metric: realized.realizedLossRelToRealizedCap.sum,
name: "Loss",
color: colors.red,
color: colors.loss,
unit: Unit.pctRcap,
}),
baseline({
metric: realized.realizedLossRelToRealizedCap.cumulative,
name: "Loss Cumulative",
color: colors.red,
color: colors.loss,
unit: Unit.pctRcap,
defaultActive: false,
}),
@@ -390,7 +390,7 @@ function createRealizedPnlSection(args, title) {
name: "SOPR",
title: title("SOPR"),
bottom: [
...createSingleSoprSeries(colors, args.tree),
...createSingleSoprSeries(args.tree),
priceLine({
unit: Unit.ratio,
number: 1,
@@ -400,7 +400,7 @@ function createRealizedPnlSection(args, title) {
{
name: "Sell Side Risk",
title: title("Sell Side Risk Ratio"),
bottom: createSingleSellSideRiskSeries(colors, args.tree),
bottom: createSingleSellSideRiskSeries(args.tree),
},
{
name: "Value",
@@ -408,17 +408,17 @@ function createRealizedPnlSection(args, title) {
{
name: "Created & Destroyed",
title: title("Value Created & Destroyed"),
bottom: createSingleValueCreatedDestroyedSeries(colors, args.tree),
bottom: createSingleValueCreatedDestroyedSeries(args.tree),
},
{
name: "Breakdown",
title: title("Value Flow Breakdown"),
bottom: createSingleValueFlowBreakdownSeries(colors, args.tree),
bottom: createSingleValueFlowBreakdownSeries(args.tree),
},
{
name: "Flow",
title: title("Capitulation & Profit Flow"),
bottom: createSingleCapitulationProfitFlowSeries(colors, args.tree),
bottom: createSingleCapitulationProfitFlowSeries(args.tree),
},
],
},
@@ -429,20 +429,20 @@ function createRealizedPnlSection(args, title) {
line({
metric: realized.peakRegret.sum,
name: "Sum",
color: colors.red,
color: colors.loss,
unit: Unit.usd,
}),
line({
metric: realized.peakRegret.cumulative,
name: "Cumulative",
color: colors.red,
color: colors.loss,
unit: Unit.usd,
defaultActive: false,
}),
baseline({
metric: realized.peakRegretRelToRealizedCap,
name: "Rel. to Realized Cap",
color: colors.orange,
color: colors.realized,
unit: Unit.pctRcap,
}),
],
@@ -457,39 +457,39 @@ function createRealizedPnlSection(args, title) {
line({
metric: realized.sentInProfit.bitcoin.sum,
name: "Sum",
color: colors.green,
color: colors.profit,
unit: Unit.btc,
}),
line({
metric: realized.sentInProfit.bitcoin.cumulative,
name: "Cumulative",
color: colors.green,
color: colors.profit,
unit: Unit.btc,
defaultActive: false,
}),
line({
metric: realized.sentInProfit.sats.sum,
name: "Sum",
color: colors.green,
color: colors.profit,
unit: Unit.sats,
}),
line({
metric: realized.sentInProfit.sats.cumulative,
name: "Cumulative",
color: colors.green,
color: colors.profit,
unit: Unit.sats,
defaultActive: false,
}),
line({
metric: realized.sentInProfit.dollars.sum,
name: "Sum",
color: colors.green,
color: colors.profit,
unit: Unit.usd,
}),
line({
metric: realized.sentInProfit.dollars.cumulative,
name: "Cumulative",
color: colors.green,
color: colors.profit,
unit: Unit.usd,
defaultActive: false,
}),
@@ -502,39 +502,39 @@ function createRealizedPnlSection(args, title) {
line({
metric: realized.sentInLoss.bitcoin.sum,
name: "Sum",
color: colors.red,
color: colors.loss,
unit: Unit.btc,
}),
line({
metric: realized.sentInLoss.bitcoin.cumulative,
name: "Cumulative",
color: colors.red,
color: colors.loss,
unit: Unit.btc,
defaultActive: false,
}),
line({
metric: realized.sentInLoss.sats.sum,
name: "Sum",
color: colors.red,
color: colors.loss,
unit: Unit.sats,
}),
line({
metric: realized.sentInLoss.sats.cumulative,
name: "Cumulative",
color: colors.red,
color: colors.loss,
unit: Unit.sats,
defaultActive: false,
}),
line({
metric: realized.sentInLoss.dollars.sum,
name: "Sum",
color: colors.red,
color: colors.loss,
unit: Unit.usd,
}),
line({
metric: realized.sentInLoss.dollars.cumulative,
name: "Cumulative",
color: colors.red,
color: colors.loss,
unit: Unit.usd,
defaultActive: false,
}),
@@ -546,7 +546,7 @@ function createRealizedPnlSection(args, title) {
bottom: satsBtcUsd({
pattern: realized.sentInProfit14dEma,
name: "14d EMA",
color: colors.green,
color: colors.profit,
}),
},
{
@@ -555,7 +555,7 @@ function createRealizedPnlSection(args, title) {
bottom: satsBtcUsd({
pattern: realized.sentInLoss14dEma,
name: "14d EMA",
color: colors.red,
color: colors.loss,
}),
},
],
@@ -820,7 +820,7 @@ function createUnrealizedSection(list, useGroupName, title) {
line({
metric: tree.unrealized.unrealizedProfit,
name: useGroupName ? name : "Profit",
color: useGroupName ? color : colors.green,
color: useGroupName ? color : colors.profit,
unit: Unit.usd,
}),
]),
@@ -832,7 +832,7 @@ function createUnrealizedSection(list, useGroupName, title) {
line({
metric: tree.unrealized.unrealizedLoss,
name: useGroupName ? name : "Loss",
color: useGroupName ? color : colors.red,
color: useGroupName ? color : colors.loss,
unit: Unit.usd,
}),
]),
@@ -856,7 +856,7 @@ function createUnrealizedSection(list, useGroupName, title) {
line({
metric: tree.unrealized.negUnrealizedLoss,
name: useGroupName ? name : "Negative Loss",
color: useGroupName ? color : colors.red,
color: useGroupName ? color : colors.loss,
unit: Unit.usd,
}),
]),
@@ -871,7 +871,7 @@ function createUnrealizedSection(list, useGroupName, title) {
line({
metric: tree.unrealized.investedCapitalInProfit,
name: useGroupName ? name : "In Profit",
color: useGroupName ? color : colors.green,
color: useGroupName ? color : colors.profit,
unit: Unit.usd,
}),
]),
@@ -883,7 +883,7 @@ function createUnrealizedSection(list, useGroupName, title) {
line({
metric: tree.unrealized.investedCapitalInLoss,
name: useGroupName ? name : "In Loss",
color: useGroupName ? color : colors.red,
color: useGroupName ? color : colors.loss,
unit: Unit.usd,
}),
]),
@@ -913,7 +913,7 @@ function createUnrealizedSection(list, useGroupName, title) {
line({
metric: tree.relative.unrealizedProfitRelToMarketCap,
name: useGroupName ? name : "Profit",
color: useGroupName ? color : colors.green,
color: useGroupName ? color : colors.profit,
unit: Unit.pctMcap,
}),
]),
@@ -925,7 +925,7 @@ function createUnrealizedSection(list, useGroupName, title) {
line({
metric: tree.relative.unrealizedLossRelToMarketCap,
name: useGroupName ? name : "Loss",
color: useGroupName ? color : colors.red,
color: useGroupName ? color : colors.loss,
unit: Unit.pctMcap,
}),
]),
@@ -949,7 +949,7 @@ function createUnrealizedSection(list, useGroupName, title) {
line({
metric: tree.relative.negUnrealizedLossRelToMarketCap,
name: useGroupName ? name : "Negative Loss",
color: useGroupName ? color : colors.red,
color: useGroupName ? color : colors.loss,
unit: Unit.pctMcap,
}),
]),
@@ -961,7 +961,7 @@ function createUnrealizedSection(list, useGroupName, title) {
baseline({
metric: tree.relative.investedCapitalInProfitPct,
name: useGroupName ? name : "In Profit",
color: useGroupName ? color : colors.green,
color: useGroupName ? color : colors.profit,
unit: Unit.pctRcap,
}),
]),
@@ -973,7 +973,7 @@ function createUnrealizedSection(list, useGroupName, title) {
baseline({
metric: tree.relative.investedCapitalInLossPct,
name: useGroupName ? name : "In Loss",
color: useGroupName ? color : colors.red,
color: useGroupName ? color : colors.loss,
unit: Unit.pctRcap,
}),
]),
@@ -0,0 +1,453 @@
/**
* Cost Basis section builders
*
* Structure:
* - Summary: Key stats (avg + median active, quartiles/extremes available)
* - By Coin: BTC-weighted percentiles (IQR active: p25, p50, p75)
* - By Capital: USD-weighted percentiles (IQR active: p25, p50, p75)
* - Price Position: Spot percentile (both perspectives active)
*
* For cohorts WITHOUT percentiles: Summary only
*/
import { colors } from "../../utils/colors.js";
import { Unit } from "../../utils/units.js";
import { priceLines } from "../constants.js";
import { line, price } from "../series.js";
/**
* @param {PercentilesPattern} p
* @param {(name: string) => string} [n]
* @returns {FetchedPriceSeriesBlueprint[]}
*/
function createCorePercentileSeries(p, n = (x) => x) {
return [
price({
metric: p.pct95,
name: n("p95"),
color: colors.pct._95,
defaultActive: false,
}),
price({
metric: p.pct90,
name: n("p90"),
color: colors.pct._90,
defaultActive: false,
}),
price({
metric: p.pct85,
name: n("p85"),
color: colors.pct._85,
defaultActive: false,
}),
price({
metric: p.pct80,
name: n("p80"),
color: colors.pct._80,
defaultActive: false,
}),
price({ metric: p.pct75, name: n("p75"), color: colors.pct._75 }),
price({
metric: p.pct70,
name: n("p70"),
color: colors.pct._70,
defaultActive: false,
}),
price({
metric: p.pct65,
name: n("p65"),
color: colors.pct._65,
defaultActive: false,
}),
price({
metric: p.pct60,
name: n("p60"),
color: colors.pct._60,
defaultActive: false,
}),
price({
metric: p.pct55,
name: n("p55"),
color: colors.pct._55,
defaultActive: false,
}),
price({ metric: p.pct50, name: n("p50"), color: colors.pct._50 }),
price({
metric: p.pct45,
name: n("p45"),
color: colors.pct._45,
defaultActive: false,
}),
price({
metric: p.pct40,
name: n("p40"),
color: colors.pct._40,
defaultActive: false,
}),
price({
metric: p.pct35,
name: n("p35"),
color: colors.pct._35,
defaultActive: false,
}),
price({
metric: p.pct30,
name: n("p30"),
color: colors.pct._30,
defaultActive: false,
}),
price({ metric: p.pct25, name: n("p25"), color: colors.pct._25 }),
price({
metric: p.pct20,
name: n("p20"),
color: colors.pct._20,
defaultActive: false,
}),
price({
metric: p.pct15,
name: n("p15"),
color: colors.pct._15,
defaultActive: false,
}),
price({
metric: p.pct10,
name: n("p10"),
color: colors.pct._10,
defaultActive: false,
}),
price({
metric: p.pct05,
name: n("p05"),
color: colors.pct._05,
defaultActive: false,
}),
];
}
/**
* @param {UtxoCohortObject | CohortWithoutRelative} cohort
* @returns {FetchedPriceSeriesBlueprint[]}
*/
function createSingleSummarySeriesBasic(cohort) {
const { color, tree } = cohort;
return [
price({ metric: tree.realized.realizedPrice, name: "Average", color }),
price({
metric: tree.costBasis.max,
name: "Max",
color: colors.pct._100,
defaultActive: false,
}),
price({
metric: tree.costBasis.min,
name: "Min",
color: colors.pct._0,
defaultActive: false,
}),
];
}
/**
* @param {CohortAll | CohortFull | CohortWithPercentiles} cohort
* @returns {FetchedPriceSeriesBlueprint[]}
*/
function createSingleSummarySeriesWithPercentiles(cohort) {
const { color, tree } = cohort;
const p = tree.costBasis.percentiles;
return [
price({ metric: tree.realized.realizedPrice, name: "Average", color }),
price({
metric: tree.costBasis.max,
name: "Max (p100)",
color: colors.pct._100,
defaultActive: false,
}),
price({
metric: p.pct75,
name: "Q3 (p75)",
color: colors.pct._75,
defaultActive: false,
}),
price({ metric: p.pct50, name: "Median (p50)", color: colors.pct._50 }),
price({
metric: p.pct25,
name: "Q1 (p25)",
color: colors.pct._25,
defaultActive: false,
}),
price({
metric: tree.costBasis.min,
name: "Min (p0)",
color: colors.pct._0,
defaultActive: false,
}),
];
}
/**
* @template {readonly { name: string, color: Color, tree: { realized: { realizedPrice: ActivePricePattern } } }[]} T
* @param {T} list
* @returns {FetchedPriceSeriesBlueprint[]}
*/
function createGroupedSummarySeries(list) {
return list.map(({ name, color, tree }) =>
price({ metric: tree.realized.realizedPrice, name, color }),
);
}
/**
* @param {CohortAll | CohortFull | CohortWithPercentiles} cohort
* @returns {FetchedPriceSeriesBlueprint[]}
*/
function createSingleByCoinSeries(cohort) {
const { color, tree } = cohort;
const cb = tree.costBasis;
return [
price({ metric: tree.realized.realizedPrice, name: "Average", color }),
price({
metric: cb.max,
name: "p100",
color: colors.pct._100,
defaultActive: false,
}),
...createCorePercentileSeries(cb.percentiles),
price({
metric: cb.min,
name: "p0",
color: colors.pct._0,
defaultActive: false,
}),
];
}
/**
* @param {CohortAll | CohortFull | CohortWithPercentiles} cohort
* @returns {FetchedPriceSeriesBlueprint[]}
*/
function createSingleByCapitalSeries(cohort) {
const { color, tree } = cohort;
return [
price({ metric: tree.realized.investorPrice, name: "Average", color }),
...createCorePercentileSeries(tree.costBasis.investedCapital),
];
}
/**
* @param {CohortAll | CohortFull | CohortWithPercentiles} cohort
* @returns {AnyFetchedSeriesBlueprint[]}
*/
function createSinglePricePositionSeries(cohort) {
const { tree } = cohort;
return [
line({
metric: tree.costBasis.spotCostBasisPercentile,
name: "By Coin",
color: colors.bitcoin,
unit: Unit.percentage,
}),
line({
metric: tree.costBasis.spotInvestedCapitalPercentile,
name: "By Capital",
color: colors.usd,
unit: Unit.percentage,
}),
...priceLines({ numbers: [100, 50, 0], unit: Unit.percentage }),
];
}
/**
* @param {{ cohort: UtxoCohortObject | CohortWithoutRelative, title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createCostBasisSection({ cohort, title }) {
return {
name: "Cost Basis",
tree: [
{
name: "Summary",
title: title("Cost Basis Summary"),
top: createSingleSummarySeriesBasic(cohort),
},
],
};
}
/**
* @param {{ cohort: CohortAll | CohortFull | CohortWithPercentiles, title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createCostBasisSectionWithPercentiles({ cohort, title }) {
return {
name: "Cost Basis",
tree: [
{
name: "Summary",
title: title("Cost Basis Summary"),
top: createSingleSummarySeriesWithPercentiles(cohort),
},
{
name: "By Coin",
title: title("Cost Basis Distribution (BTC-weighted)"),
top: createSingleByCoinSeries(cohort),
},
{
name: "By Capital",
title: title("Cost Basis Distribution (USD-weighted)"),
top: createSingleByCapitalSeries(cohort),
},
{
name: "Price Position",
title: title("Current Price Position"),
bottom: createSinglePricePositionSeries(cohort),
},
],
};
}
/**
* @template {readonly (UtxoCohortObject | CohortWithoutRelative)[]} T
* @param {{ list: T, title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createGroupedCostBasisSection({ list, title }) {
return {
name: "Cost Basis",
tree: [
{
name: "Summary",
title: title("Cost Basis Summary"),
top: createGroupedSummarySeries(list),
},
],
};
}
/**
* @param {{ list: readonly (CohortAll | CohortFull | CohortWithPercentiles)[], title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createGroupedCostBasisSectionWithPercentiles({ list, title }) {
return {
name: "Cost Basis",
tree: [
{
name: "Summary",
title: title("Cost Basis Summary"),
top: createGroupedSummarySeries(list),
},
{
name: "By Coin",
tree: [
{
name: "Average",
title: title("Realized Price Comparison"),
top: list.map(({ name, color, tree }) =>
price({ metric: tree.realized.realizedPrice, name, color }),
),
},
{
name: "Median",
title: title("Cost Basis Median (BTC-weighted)"),
top: list.map(({ name, color, tree }) =>
price({ metric: tree.costBasis.percentiles.pct50, name, color }),
),
},
{
name: "Q3",
title: title("Cost Basis Q3 (BTC-weighted)"),
top: list.map(({ name, color, tree }) =>
price({ metric: tree.costBasis.percentiles.pct75, name, color }),
),
},
{
name: "Q1",
title: title("Cost Basis Q1 (BTC-weighted)"),
top: list.map(({ name, color, tree }) =>
price({ metric: tree.costBasis.percentiles.pct25, name, color }),
),
},
],
},
{
name: "By Capital",
tree: [
{
name: "Average",
title: title("Investor Price Comparison"),
top: list.map(({ name, color, tree }) =>
price({ metric: tree.realized.investorPrice, name, color }),
),
},
{
name: "Median",
title: title("Cost Basis Median (USD-weighted)"),
top: list.map(({ name, color, tree }) =>
price({
metric: tree.costBasis.investedCapital.pct50,
name,
color,
}),
),
},
{
name: "Q3",
title: title("Cost Basis Q3 (USD-weighted)"),
top: list.map(({ name, color, tree }) =>
price({
metric: tree.costBasis.investedCapital.pct75,
name,
color,
}),
),
},
{
name: "Q1",
title: title("Cost Basis Q1 (USD-weighted)"),
top: list.map(({ name, color, tree }) =>
price({
metric: tree.costBasis.investedCapital.pct25,
name,
color,
}),
),
},
],
},
{
name: "Price Position",
tree: [
{
name: "By Coin",
title: title("Price Position (BTC-weighted)"),
bottom: [
...list.map(({ name, color, tree }) =>
line({
metric: tree.costBasis.spotCostBasisPercentile,
name,
color,
unit: Unit.percentage,
}),
),
...priceLines({ numbers: [100, 50, 0], unit: Unit.percentage }),
],
},
{
name: "By Capital",
title: title("Price Position (USD-weighted)"),
bottom: [
...list.map(({ name, color, tree }) =>
line({
metric: tree.costBasis.spotInvestedCapitalPercentile,
name,
color,
unit: Unit.percentage,
}),
),
...priceLines({ numbers: [100, 50, 0], unit: Unit.percentage }),
],
},
],
},
],
};
}
+1 -1
View File
@@ -44,7 +44,7 @@ export function buildCohortData() {
const cohortAll = {
name: "",
title: "",
color: colors.orange,
color: colors.bitcoin,
tree: utxoCohorts.all,
addrCount: addrCount.all,
};
@@ -0,0 +1,479 @@
/**
* Holdings section builders
*
* Structure (Option C - optimized for UX):
* - Supply: Total BTC held (flat, one click)
* - UTXO Count: Number of UTXOs (flat, one click)
* - Address Count: Number of addresses (when available, flat)
* - 30d Changes/: Folder for change metrics
* - Supply: 30d supply change
* - Relative: % of circulating supply (when available)
*
* Rationale: Most-used metrics (Supply, UTXO Count) are immediately accessible.
* 30d changes are grouped together for consistency and cleaner navigation.
*/
import { Unit } from "../../utils/units.js";
import { line, baseline } from "../series.js";
import { satsBtcUsd, satsBtcUsdBaseline } from "../shared.js";
/**
* @param {UtxoCohortObject | CohortWithoutRelative} cohort
* @returns {AnyFetchedSeriesBlueprint[]}
*/
function createSingleSupplySeries(cohort) {
const { color, tree } = cohort;
return [...satsBtcUsd({ pattern: tree.supply.total, name: "Supply", color })];
}
/**
* @param {UtxoCohortObject | CohortWithoutRelative} cohort
* @returns {AnyFetchedSeriesBlueprint[]}
*/
function createSingle30dChangeSeries(cohort) {
return satsBtcUsdBaseline({ pattern: cohort.tree.supply._30dChange, name: "30d Change" });
}
/**
* @param {UtxoCohortObject | CohortWithoutRelative} cohort
* @returns {AnyFetchedSeriesBlueprint[]}
*/
function createSingleUtxoCountSeries(cohort) {
const { color, tree } = cohort;
return [
line({
metric: tree.outputs.utxoCount,
name: "UTXO Count",
color,
unit: Unit.count,
}),
];
}
/**
* @param {UtxoCohortObject | CohortWithoutRelative} cohort
* @returns {AnyFetchedSeriesBlueprint[]}
*/
function createSingleUtxoCount30dChangeSeries(cohort) {
return [
baseline({
metric: cohort.tree.outputs.utxoCount30dChange,
name: "30d Change",
unit: Unit.count,
}),
];
}
/**
* @param {CohortAll | CohortAddress} cohort
* @returns {AnyFetchedSeriesBlueprint[]}
*/
function createSingleAddrCount30dChangeSeries(cohort) {
return [
baseline({
metric: cohort.addrCount._30dChange,
name: "30d Change",
unit: Unit.count,
}),
];
}
/**
* @param {CohortFull | CohortWithAdjusted | CohortBasicWithMarketCap | CohortMinAge} cohort
* @returns {AnyFetchedSeriesBlueprint[]}
*/
function createSingleRelativeSeries(cohort) {
const { color, tree } = cohort;
return [
line({
metric: tree.relative.supplyRelToCirculatingSupply,
name: "% of Circulating",
color,
unit: Unit.pctSupply,
}),
];
}
/**
* @param {{ cohort: UtxoCohortObject | CohortWithoutRelative, title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createHoldingsSection({ cohort, title }) {
return {
name: "Holdings",
tree: [
{
name: "Supply",
title: title("Supply"),
bottom: createSingleSupplySeries(cohort),
},
{
name: "UTXO Count",
title: title("UTXO Count"),
bottom: createSingleUtxoCountSeries(cohort),
},
{
name: "30d Changes",
tree: [
{
name: "Supply",
title: title("Supply 30d Change"),
bottom: createSingle30dChangeSeries(cohort),
},
{
name: "UTXO Count",
title: title("UTXO Count 30d Change"),
bottom: createSingleUtxoCount30dChangeSeries(cohort),
},
],
},
],
};
}
/**
* @param {{ cohort: CohortFull | CohortWithAdjusted | CohortBasicWithMarketCap | CohortMinAge, title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createHoldingsSectionWithRelative({ cohort, title }) {
return {
name: "Holdings",
tree: [
{
name: "Supply",
title: title("Supply"),
bottom: createSingleSupplySeries(cohort),
},
{
name: "UTXO Count",
title: title("UTXO Count"),
bottom: createSingleUtxoCountSeries(cohort),
},
{
name: "30d Changes",
tree: [
{
name: "Supply",
title: title("Supply 30d Change"),
bottom: createSingle30dChangeSeries(cohort),
},
{
name: "UTXO Count",
title: title("UTXO Count 30d Change"),
bottom: createSingleUtxoCount30dChangeSeries(cohort),
},
],
},
{
name: "Relative",
title: title("Relative to Circulating Supply"),
bottom: createSingleRelativeSeries(cohort),
},
],
};
}
/**
* @param {{ cohort: CohortAll, title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createHoldingsSectionAll({ cohort, title }) {
return {
name: "Holdings",
tree: [
{
name: "Supply",
title: title("Supply"),
bottom: createSingleSupplySeries(cohort),
},
{
name: "UTXO Count",
title: title("UTXO Count"),
bottom: createSingleUtxoCountSeries(cohort),
},
{
name: "Address Count",
title: title("Address Count"),
bottom: [
line({
metric: cohort.addrCount.count,
name: "Address Count",
color: cohort.color,
unit: Unit.count,
}),
],
},
{
name: "30d Changes",
tree: [
{
name: "Supply",
title: title("Supply 30d Change"),
bottom: createSingle30dChangeSeries(cohort),
},
{
name: "UTXO Count",
title: title("UTXO Count 30d Change"),
bottom: createSingleUtxoCount30dChangeSeries(cohort),
},
{
name: "Address Count",
title: title("Address Count 30d Change"),
bottom: createSingleAddrCount30dChangeSeries(cohort),
},
],
},
],
};
}
/**
* @param {{ cohort: CohortAddress, title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createHoldingsSectionAddress({ cohort, title }) {
return {
name: "Holdings",
tree: [
{
name: "Supply",
title: title("Supply"),
bottom: createSingleSupplySeries(cohort),
},
{
name: "UTXO Count",
title: title("UTXO Count"),
bottom: createSingleUtxoCountSeries(cohort),
},
{
name: "Address Count",
title: title("Address Count"),
bottom: [
line({
metric: cohort.addrCount.count,
name: "Address Count",
color: cohort.color,
unit: Unit.count,
}),
],
},
{
name: "30d Changes",
tree: [
{
name: "Supply",
title: title("Supply 30d Change"),
bottom: createSingle30dChangeSeries(cohort),
},
{
name: "UTXO Count",
title: title("UTXO Count 30d Change"),
bottom: createSingleUtxoCount30dChangeSeries(cohort),
},
{
name: "Address Count",
title: title("Address Count 30d Change"),
bottom: createSingleAddrCount30dChangeSeries(cohort),
},
],
},
],
};
}
/**
* @param {{ list: readonly CohortAddress[], title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createGroupedHoldingsSectionAddress({ list, title }) {
return {
name: "Holdings",
tree: [
{
name: "Supply",
title: title("Supply"),
bottom: list.flatMap(({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.supply.total, name, color }),
),
},
{
name: "UTXO Count",
title: title("UTXO Count"),
bottom: list.map(({ name, color, tree }) =>
line({
metric: tree.outputs.utxoCount,
name,
color,
unit: Unit.count,
}),
),
},
{
name: "Address Count",
title: title("Address Count"),
bottom: list.map(({ name, color, addrCount }) =>
line({ metric: addrCount.count, name, color, unit: Unit.count }),
),
},
{
name: "30d Changes",
tree: [
{
name: "Supply",
title: title("Supply 30d Change"),
bottom: list.flatMap(({ name, color, tree }) =>
satsBtcUsdBaseline({ pattern: tree.supply._30dChange, name, color }),
),
},
{
name: "UTXO Count",
title: title("UTXO Count 30d Change"),
bottom: list.map(({ name, color, tree }) =>
baseline({
metric: tree.outputs.utxoCount30dChange,
name,
unit: Unit.count,
color,
}),
),
},
{
name: "Address Count",
title: title("Address Count 30d Change"),
bottom: list.map(({ name, color, addrCount }) =>
baseline({
metric: addrCount._30dChange,
name,
unit: Unit.count,
color,
}),
),
},
],
},
],
};
}
/**
* @template {readonly (UtxoCohortObject | CohortWithoutRelative)[]} T
* @param {{ list: T, title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createGroupedHoldingsSection({ list, title }) {
return {
name: "Holdings",
tree: [
{
name: "Supply",
title: title("Supply"),
bottom: list.flatMap(({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.supply.total, name, color }),
),
},
{
name: "UTXO Count",
title: title("UTXO Count"),
bottom: list.map(({ name, color, tree }) =>
line({
metric: tree.outputs.utxoCount,
name,
color,
unit: Unit.count,
}),
),
},
{
name: "30d Changes",
tree: [
{
name: "Supply",
title: title("Supply 30d Change"),
bottom: list.flatMap(({ name, color, tree }) =>
satsBtcUsdBaseline({ pattern: tree.supply._30dChange, name, color }),
),
},
{
name: "UTXO Count",
title: title("UTXO Count 30d Change"),
bottom: list.map(({ name, color, tree }) =>
baseline({
metric: tree.outputs.utxoCount30dChange,
name,
unit: Unit.count,
color,
}),
),
},
],
},
],
};
}
/**
* @param {{ list: readonly (CohortFull | CohortWithAdjusted | CohortBasicWithMarketCap | CohortMinAge)[], title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createGroupedHoldingsSectionWithRelative({ list, title }) {
return {
name: "Holdings",
tree: [
{
name: "Supply",
title: title("Supply"),
bottom: list.flatMap(({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.supply.total, name, color }),
),
},
{
name: "UTXO Count",
title: title("UTXO Count"),
bottom: list.map(({ name, color, tree }) =>
line({
metric: tree.outputs.utxoCount,
name,
color,
unit: Unit.count,
}),
),
},
{
name: "30d Changes",
tree: [
{
name: "Supply",
title: title("Supply 30d Change"),
bottom: list.flatMap(({ name, color, tree }) =>
satsBtcUsdBaseline({ pattern: tree.supply._30dChange, name, color }),
),
},
{
name: "UTXO Count",
title: title("UTXO Count 30d Change"),
bottom: list.map(({ name, color, tree }) =>
baseline({
metric: tree.outputs.utxoCount30dChange,
name,
unit: Unit.count,
color,
}),
),
},
],
},
{
name: "Relative",
title: title("Relative to Circulating Supply"),
bottom: list.map(({ name, color, tree }) =>
line({
metric: tree.relative.supplyRelToCirculatingSupply,
name,
color,
unit: Unit.pctSupply,
}),
),
},
],
};
}
@@ -0,0 +1,225 @@
/**
* Prices section builders
*
* Structure (single cohort):
* - Compare: Both prices on one chart
* - Realized: Price + Ratio (MVRV) + Z-Scores (for full cohorts)
* - Investor: Price + Ratio + Z-Scores (for full cohorts)
*
* Structure (grouped cohorts):
* - Realized: Price + Ratio comparison across cohorts
* - Investor: Price + Ratio comparison across cohorts
*
* For cohorts WITHOUT full ratio patterns: basic Price/Ratio charts only (no Z-Scores)
*/
import { colors } from "../../utils/colors.js";
import { createPriceRatioCharts } from "../shared.js";
import { baseline, price } from "../series.js";
import { Unit } from "../../utils/units.js";
/**
* Create prices section for cohorts with full ActivePriceRatioPattern
* (CohortAll, CohortFull, CohortWithPercentiles)
* @param {{ cohort: CohortAll | CohortFull | CohortWithPercentiles, title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createPricesSectionFull({ cohort, title }) {
const { tree, color } = cohort;
return {
name: "Prices",
tree: [
{
name: "Compare",
title: title("Prices"),
top: [
price({
metric: tree.realized.realizedPrice,
name: "Realized",
color: colors.realized,
}),
price({
metric: tree.realized.investorPrice,
name: "Investor",
color: colors.investor,
}),
],
},
{
name: "Realized",
tree: createPriceRatioCharts({
context: cohort.name,
legend: "Realized",
pricePattern: tree.realized.realizedPrice,
ratio: tree.realized.realizedPriceExtra,
color,
priceTitle: title("Realized Price"),
titlePrefix: "Realized Price",
}),
},
{
name: "Investor",
tree: createPriceRatioCharts({
context: cohort.name,
legend: "Investor",
pricePattern: tree.realized.investorPrice,
ratio: tree.realized.investorPriceExtra,
color,
priceTitle: title("Investor Price"),
titlePrefix: "Investor Price",
}),
},
],
};
}
/**
* Create prices section for cohorts with basic ratio patterns only
* (CohortWithAdjusted, CohortBasic, CohortAddress, CohortWithoutRelative)
* @param {{ cohort: CohortWithAdjusted | CohortBasic | CohortAddress | CohortWithoutRelative, title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createPricesSectionBasic({ cohort, title }) {
const { tree, color } = cohort;
return {
name: "Prices",
tree: [
{
name: "Compare",
title: title("Prices"),
top: [
price({
metric: tree.realized.realizedPrice,
name: "Realized",
color: colors.realized,
}),
price({
metric: tree.realized.investorPrice,
name: "Investor",
color: colors.investor,
}),
],
},
{
name: "Realized",
tree: [
{
name: "Price",
title: title("Realized Price"),
top: [
price({
metric: tree.realized.realizedPrice,
name: "Realized",
color,
}),
],
},
{
name: "Ratio",
title: title("Realized Price Ratio"),
bottom: [
baseline({
metric: tree.realized.realizedPriceExtra.ratio,
name: "Ratio",
unit: Unit.ratio,
base: 1,
}),
],
},
],
},
{
name: "Investor",
tree: [
{
name: "Price",
title: title("Investor Price"),
top: [
price({
metric: tree.realized.investorPrice,
name: "Investor",
color,
}),
],
},
{
name: "Ratio",
title: title("Investor Price Ratio"),
bottom: [
baseline({
metric: tree.realized.investorPriceExtra.ratio,
name: "Ratio",
unit: Unit.ratio,
base: 1,
}),
],
},
],
},
],
};
}
/**
* Create prices section for grouped cohorts
* @template {readonly (CohortAll | CohortFull | CohortWithPercentiles | CohortWithAdjusted | CohortBasic | CohortAddress | CohortWithoutRelative)[]} T
* @param {{ list: T, title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createGroupedPricesSection({ list, title }) {
return {
name: "Prices",
tree: [
{
name: "Realized",
tree: [
{
name: "Price",
title: title("Realized Price"),
top: list.map(({ name, color, tree }) =>
price({ metric: tree.realized.realizedPrice, name, color }),
),
},
{
name: "Ratio",
title: title("Realized Price Ratio"),
bottom: list.map(({ name, color, tree }) =>
baseline({
metric: tree.realized.realizedPriceExtra.ratio,
name,
color,
unit: Unit.ratio,
base: 1,
}),
),
},
],
},
{
name: "Investor",
tree: [
{
name: "Price",
title: title("Investor Price"),
top: list.map(({ name, color, tree }) =>
price({ metric: tree.realized.investorPrice, name, color }),
),
},
{
name: "Ratio",
title: title("Investor Price Ratio"),
bottom: list.map(({ name, color, tree }) =>
baseline({
metric: tree.realized.investorPriceExtra.ratio,
name,
color,
unit: Unit.ratio,
base: 1,
}),
),
},
],
},
],
};
}
+73 -86
View File
@@ -176,17 +176,17 @@ function createSingleSupplySeriesBase(cohort) {
...satsBtcUsd({
pattern: tree.supply._30dChange,
name: "30d Change",
color: colors.orange,
color: colors.bitcoin,
}),
...satsBtcUsd({
pattern: tree.unrealized.supplyInProfit,
name: "In Profit",
color: colors.green,
color: colors.profit,
}),
...satsBtcUsd({
pattern: tree.unrealized.supplyInLoss,
name: "In Loss",
color: colors.red,
color: colors.loss,
}),
...satsBtcUsd({
pattern: tree.supply.halved,
@@ -211,13 +211,13 @@ function createSingleSupplyRelativeToOwnMetrics(cohort) {
line({
metric: tree.relative.supplyInProfitRelToOwnSupply,
name: "In Profit",
color: colors.green,
color: colors.profit,
unit: Unit.pctOwn,
}),
line({
metric: tree.relative.supplyInLossRelToOwnSupply,
name: "In Loss",
color: colors.red,
color: colors.loss,
unit: Unit.pctOwn,
}),
priceLine({
@@ -403,13 +403,13 @@ export function createSupplyPnlRelativeToCirculatingSeries(cohort) {
line({
metric: cohort.tree.relative.supplyInProfitRelToCirculatingSupply,
name: "In Profit",
color: colors.green,
color: colors.profit,
unit: Unit.pctSupply,
}),
line({
metric: cohort.tree.relative.supplyInLossRelToCirculatingSupply,
name: "In Loss",
color: colors.red,
color: colors.loss,
unit: Unit.pctSupply,
}),
];
@@ -513,7 +513,7 @@ export function createAddressCountSeries(list, useGroupName) {
line({
metric: tree.addrCount,
name: useGroupName ? name : "Count",
color: useGroupName ? color : colors.orange,
color: useGroupName ? color : colors.bitcoin,
unit: Unit.count,
}),
]);
@@ -566,12 +566,11 @@ export function createRealizedCapSeries(list, useGroupName) {
/**
* Create cost basis percentile series (only for cohorts with CostBasisPattern2)
* Includes min (p0) and max (p100) with full rainbow coloring
* @param {Colors} colors
* @param {readonly CohortWithCostBasisPercentiles[]} list
* @param {boolean} useGroupName
* @returns {FetchedPriceSeriesBlueprint[]}
*/
export function createCostBasisPercentilesSeries(colors, list, useGroupName) {
export function createCostBasisPercentilesSeries(list, useGroupName) {
return list.flatMap(({ name, tree }) => {
const cb = tree.costBasis;
const p = cb.percentiles;
@@ -581,122 +580,122 @@ export function createCostBasisPercentilesSeries(colors, list, useGroupName) {
price({
metric: cb.max,
name: n(100),
color: colors.purple,
color: colors.pct._100,
defaultActive: false,
}),
price({
metric: p.pct95,
name: n(95),
color: colors.fuchsia,
color: colors.pct._95,
defaultActive: false,
}),
price({
metric: p.pct90,
name: n(90),
color: colors.pink,
color: colors.pct._90,
defaultActive: false,
}),
price({
metric: p.pct85,
name: n(85),
color: colors.pink,
color: colors.pct._85,
defaultActive: false,
}),
price({
metric: p.pct80,
name: n(80),
color: colors.rose,
color: colors.pct._80,
defaultActive: false,
}),
price({
metric: p.pct75,
name: n(75),
color: colors.red,
color: colors.pct._75,
defaultActive: false,
}),
price({
metric: p.pct70,
name: n(70),
color: colors.orange,
color: colors.pct._70,
defaultActive: false,
}),
price({
metric: p.pct65,
name: n(65),
color: colors.amber,
color: colors.pct._65,
defaultActive: false,
}),
price({
metric: p.pct60,
name: n(60),
color: colors.yellow,
color: colors.pct._60,
defaultActive: false,
}),
price({
metric: p.pct55,
name: n(55),
color: colors.yellow,
color: colors.pct._55,
defaultActive: false,
}),
price({ metric: p.pct50, name: n(50), color: colors.avocado }),
price({ metric: p.pct50, name: n(50), color: colors.pct._50 }),
price({
metric: p.pct45,
name: n(45),
color: colors.lime,
color: colors.pct._45,
defaultActive: false,
}),
price({
metric: p.pct40,
name: n(40),
color: colors.green,
color: colors.pct._40,
defaultActive: false,
}),
price({
metric: p.pct35,
name: n(35),
color: colors.emerald,
color: colors.pct._35,
defaultActive: false,
}),
price({
metric: p.pct30,
name: n(30),
color: colors.teal,
color: colors.pct._30,
defaultActive: false,
}),
price({
metric: p.pct25,
name: n(25),
color: colors.teal,
color: colors.pct._25,
defaultActive: false,
}),
price({
metric: p.pct20,
name: n(20),
color: colors.cyan,
color: colors.pct._20,
defaultActive: false,
}),
price({
metric: p.pct15,
name: n(15),
color: colors.sky,
color: colors.pct._15,
defaultActive: false,
}),
price({
metric: p.pct10,
name: n(10),
color: colors.blue,
color: colors.pct._10,
defaultActive: false,
}),
price({
metric: p.pct05,
name: n(5),
color: colors.indigo,
color: colors.pct._05,
defaultActive: false,
}),
price({
metric: cb.min,
name: n(0),
color: colors.violet,
color: colors.pct._0,
defaultActive: false,
}),
];
@@ -706,16 +705,11 @@ export function createCostBasisPercentilesSeries(colors, list, useGroupName) {
/**
* Create invested capital percentile series (only for cohorts with CostBasisPattern2)
* Shows invested capital at each percentile level
* @param {Colors} colors
* @param {readonly CohortWithCostBasisPercentiles[]} list
* @param {boolean} useGroupName
* @returns {FetchedPriceSeriesBlueprint[]}
*/
export function createInvestedCapitalPercentilesSeries(
colors,
list,
useGroupName,
) {
export function createInvestedCapitalPercentilesSeries(list, useGroupName) {
return list.flatMap(({ name, tree }) => {
const ic = tree.costBasis.investedCapital;
const n = (/** @type {number} */ pct) =>
@@ -724,110 +718,110 @@ export function createInvestedCapitalPercentilesSeries(
price({
metric: ic.pct95,
name: n(95),
color: colors.fuchsia,
color: colors.pct._95,
defaultActive: false,
}),
price({
metric: ic.pct90,
name: n(90),
color: colors.pink,
color: colors.pct._90,
defaultActive: false,
}),
price({
metric: ic.pct85,
name: n(85),
color: colors.pink,
color: colors.pct._85,
defaultActive: false,
}),
price({
metric: ic.pct80,
name: n(80),
color: colors.rose,
color: colors.pct._80,
defaultActive: false,
}),
price({
metric: ic.pct75,
name: n(75),
color: colors.red,
color: colors.pct._75,
defaultActive: false,
}),
price({
metric: ic.pct70,
name: n(70),
color: colors.orange,
color: colors.pct._70,
defaultActive: false,
}),
price({
metric: ic.pct65,
name: n(65),
color: colors.amber,
color: colors.pct._65,
defaultActive: false,
}),
price({
metric: ic.pct60,
name: n(60),
color: colors.yellow,
color: colors.pct._60,
defaultActive: false,
}),
price({
metric: ic.pct55,
name: n(55),
color: colors.yellow,
color: colors.pct._55,
defaultActive: false,
}),
price({ metric: ic.pct50, name: n(50), color: colors.avocado }),
price({ metric: ic.pct50, name: n(50), color: colors.pct._50 }),
price({
metric: ic.pct45,
name: n(45),
color: colors.lime,
color: colors.pct._45,
defaultActive: false,
}),
price({
metric: ic.pct40,
name: n(40),
color: colors.green,
color: colors.pct._40,
defaultActive: false,
}),
price({
metric: ic.pct35,
name: n(35),
color: colors.emerald,
color: colors.pct._35,
defaultActive: false,
}),
price({
metric: ic.pct30,
name: n(30),
color: colors.teal,
color: colors.pct._30,
defaultActive: false,
}),
price({
metric: ic.pct25,
name: n(25),
color: colors.teal,
color: colors.pct._25,
defaultActive: false,
}),
price({
metric: ic.pct20,
name: n(20),
color: colors.cyan,
color: colors.pct._20,
defaultActive: false,
}),
price({
metric: ic.pct15,
name: n(15),
color: colors.sky,
color: colors.pct._15,
defaultActive: false,
}),
price({
metric: ic.pct10,
name: n(10),
color: colors.blue,
color: colors.pct._10,
defaultActive: false,
}),
price({
metric: ic.pct05,
name: n(5),
color: colors.indigo,
color: colors.pct._05,
defaultActive: false,
}),
];
@@ -836,12 +830,11 @@ export function createInvestedCapitalPercentilesSeries(
/**
* Create spot percentile series (shows current percentile of price relative to cost basis/invested capital)
* @param {Colors} colors
* @param {readonly CohortWithCostBasisPercentiles[]} list
* @param {boolean} useGroupName
* @returns {FetchedBaselineSeriesBlueprint[]}
*/
export function createSpotPercentileSeries(colors, list, useGroupName) {
export function createSpotPercentileSeries(list, useGroupName) {
return list.flatMap(({ name, color, tree }) => [
baseline({
metric: tree.costBasis.spotCostBasisPercentile,
@@ -852,7 +845,7 @@ export function createSpotPercentileSeries(colors, list, useGroupName) {
baseline({
metric: tree.costBasis.spotInvestedCapitalPercentile,
name: useGroupName ? `${name} Invested Capital` : "Invested Capital",
color: useGroupName ? color : colors.orange,
color: useGroupName ? color : colors.bitcoin,
unit: Unit.ratio,
defaultActive: false,
}),
@@ -990,28 +983,27 @@ export function createSingleSentSeries(cohort) {
/**
* Create sell side risk ratio series for single cohort
* @param {Colors} colors
* @param {{ realized: AnyRealizedPattern }} tree
* @returns {AnyFetchedSeriesBlueprint[]}
*/
export function createSingleSellSideRiskSeries(colors, tree) {
export function createSingleSellSideRiskSeries(tree) {
return [
dots({
metric: tree.realized.sellSideRiskRatio,
name: "Raw",
color: colors.orange,
color: colors.bitcoin,
unit: Unit.ratio,
}),
line({
metric: tree.realized.sellSideRiskRatio7dEma,
name: "7d EMA",
color: colors.red,
color: colors.ma._1w,
unit: Unit.ratio,
}),
line({
metric: tree.realized.sellSideRiskRatio30dEma,
name: "30d EMA",
color: colors.pink,
color: colors.ma._1m,
unit: Unit.ratio,
}),
];
@@ -1039,22 +1031,21 @@ export function createGroupedSellSideRiskSeries(list) {
/**
* Create value created & destroyed series for single cohort
* @param {Colors} colors
* @param {{ realized: AnyRealizedPattern }} tree
* @returns {AnyFetchedSeriesBlueprint[]}
*/
export function createSingleValueCreatedDestroyedSeries(colors, tree) {
export function createSingleValueCreatedDestroyedSeries(tree) {
return [
line({
metric: tree.realized.valueCreated,
name: "Created",
color: colors.emerald,
color: colors.usd,
unit: Unit.usd,
}),
line({
metric: tree.realized.valueDestroyed,
name: "Destroyed",
color: colors.red,
color: colors.loss,
unit: Unit.usd,
}),
];
@@ -1063,36 +1054,35 @@ export function createSingleValueCreatedDestroyedSeries(colors, tree) {
/**
* Create profit/loss value breakdown series for single cohort
* Shows profit value created/destroyed and loss value created/destroyed
* @param {Colors} colors
* @param {{ realized: AnyRealizedPattern }} tree
* @returns {AnyFetchedSeriesBlueprint[]}
*/
export function createSingleValueFlowBreakdownSeries(colors, tree) {
export function createSingleValueFlowBreakdownSeries(tree) {
return [
line({
metric: tree.realized.profitValueCreated,
name: "Profit Created",
color: colors.green,
color: colors.profit,
unit: Unit.usd,
}),
line({
metric: tree.realized.profitValueDestroyed,
name: "Profit Destroyed",
color: colors.lime,
color: colors.loss,
unit: Unit.usd,
defaultActive: false,
}),
line({
metric: tree.realized.lossValueCreated,
name: "Loss Created",
color: colors.orange,
color: colors.bitcoin,
unit: Unit.usd,
defaultActive: false,
}),
line({
metric: tree.realized.lossValueDestroyed,
name: "Loss Destroyed",
color: colors.red,
color: colors.loss,
unit: Unit.usd,
}),
];
@@ -1100,22 +1090,21 @@ export function createSingleValueFlowBreakdownSeries(colors, tree) {
/**
* Create capitulation & profit flow series for single cohort
* @param {Colors} colors
* @param {{ realized: AnyRealizedPattern }} tree
* @returns {AnyFetchedSeriesBlueprint[]}
*/
export function createSingleCapitulationProfitFlowSeries(colors, tree) {
export function createSingleCapitulationProfitFlowSeries(tree) {
return [
line({
metric: tree.realized.profitFlow,
name: "Profit Flow",
color: colors.green,
color: colors.profit,
unit: Unit.usd,
}),
line({
metric: tree.realized.capitulationFlow,
name: "Capitulation Flow",
color: colors.red,
color: colors.loss,
unit: Unit.usd,
}),
];
@@ -1127,11 +1116,10 @@ export function createSingleCapitulationProfitFlowSeries(colors, tree) {
/**
* Create base SOPR series for single cohort (all cohorts have base SOPR)
* @param {Colors} colors
* @param {{ realized: AnyRealizedPattern }} tree
* @returns {AnyFetchedSeriesBlueprint[]}
*/
export function createSingleSoprSeries(colors, tree) {
export function createSingleSoprSeries(tree) {
return [
baseline({
metric: tree.realized.sopr,
@@ -1142,7 +1130,7 @@ export function createSingleSoprSeries(colors, tree) {
baseline({
metric: tree.realized.sopr7dEma,
name: "7d EMA",
color: [colors.lime, colors.rose],
color: colors.bi.sopr7d,
unit: Unit.ratio,
defaultActive: false,
base: 1,
@@ -1150,7 +1138,7 @@ export function createSingleSoprSeries(colors, tree) {
baseline({
metric: tree.realized.sopr30dEma,
name: "30d EMA",
color: [colors.avocado, colors.pink],
color: colors.bi.sopr30d,
unit: Unit.ratio,
defaultActive: false,
base: 1,
@@ -1337,11 +1325,10 @@ export function createGroupedRealizedAthRegretSeries(list) {
/**
* Create sentiment series for single cohort
* @param {Colors} colors
* @param {{ unrealized: UnrealizedPattern }} tree
* @returns {AnyFetchedSeriesBlueprint[]}
*/
export function createSingleSentimentSeries(colors, tree) {
export function createSingleSentimentSeries(tree) {
return [
baseline({
metric: tree.unrealized.netSentiment,
@@ -1351,13 +1338,13 @@ export function createSingleSentimentSeries(colors, tree) {
line({
metric: tree.unrealized.greedIndex,
name: "Greed Index",
color: colors.green,
color: colors.profit,
unit: Unit.usd,
}),
line({
metric: tree.unrealized.painIndex,
name: "Pain Index",
color: colors.red,
color: colors.loss,
unit: Unit.usd,
}),
];
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,165 @@
/**
* Valuation section builders
*
* Structure:
* - Realized Cap: Total value at cost basis (USD)
* - 30d Change: Recent realized cap changes
* - MVRV: Market Value to Realized Value ratio
*
* For cohorts WITH full ratio patterns: MVRV uses createRatioChart (price + percentiles)
* For cohorts WITHOUT full ratio patterns: MVRV is simple baseline
*/
import { Unit } from "../../utils/units.js";
import { line, baseline } from "../series.js";
import { createRatioChart } from "../shared.js";
/**
* @param {UtxoCohortObject | CohortWithoutRelative} cohort
* @returns {AnyFetchedSeriesBlueprint[]}
*/
function createSingleRealizedCapSeries(cohort) {
const { color, tree } = cohort;
return [
line({
metric: tree.realized.realizedCap,
name: "Realized Cap",
color,
unit: Unit.usd,
}),
];
}
/**
* @param {UtxoCohortObject | CohortWithoutRelative} cohort
* @returns {AnyFetchedSeriesBlueprint[]}
*/
function createSingle30dChangeSeries(cohort) {
return [
baseline({
metric: cohort.tree.realized.realizedCap30dDelta,
name: "30d Change",
unit: Unit.usd,
}),
];
}
/**
* Create valuation section for cohorts with full ratio patterns
* (CohortAll, CohortFull, CohortWithPercentiles)
* @param {{ cohort: CohortAll | CohortFull | CohortWithPercentiles, title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createValuationSectionFull({ cohort, title }) {
const { tree, color } = cohort;
return {
name: "Valuation",
tree: [
{
name: "Realized Cap",
title: title("Realized Cap"),
bottom: createSingleRealizedCapSeries(cohort),
},
{
name: "30d Change",
title: title("Realized Cap 30d Change"),
bottom: createSingle30dChangeSeries(cohort),
},
createRatioChart({
title,
pricePattern: tree.realized.realizedPrice,
ratio: tree.realized.realizedPriceExtra,
color,
name: "MVRV",
}),
],
};
}
/**
* Create valuation section for cohorts with basic ratio patterns
* (CohortWithAdjusted, CohortBasic, CohortAddress, CohortWithoutRelative)
* @param {{ cohort: CohortWithAdjusted | CohortBasic | CohortAddress | CohortWithoutRelative, title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createValuationSection({ cohort, title }) {
const { tree, color } = cohort;
return {
name: "Valuation",
tree: [
{
name: "Realized Cap",
title: title("Realized Cap"),
bottom: createSingleRealizedCapSeries(cohort),
},
{
name: "30d Change",
title: title("Realized Cap 30d Change"),
bottom: createSingle30dChangeSeries(cohort),
},
{
name: "MVRV",
title: title("MVRV"),
bottom: [
baseline({
metric: tree.realized.realizedPriceExtra.ratio,
name: "MVRV",
color,
unit: Unit.ratio,
base: 1,
}),
],
},
],
};
}
/**
* @template {readonly (UtxoCohortObject | CohortWithoutRelative)[]} T
* @param {{ list: T, title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createGroupedValuationSection({ list, title }) {
return {
name: "Valuation",
tree: [
{
name: "Realized Cap",
title: title("Realized Cap"),
bottom: list.map(({ name, color, tree }) =>
line({
metric: tree.realized.realizedCap,
name,
color,
unit: Unit.usd,
}),
),
},
{
name: "30d Change",
title: title("Realized Cap 30d Change"),
bottom: list.map(({ name, color, tree }) =>
baseline({
metric: tree.realized.realizedCap30dDelta,
name,
color,
unit: Unit.usd,
}),
),
},
{
name: "MVRV",
title: title("MVRV"),
bottom: list.map(({ name, color, tree }) =>
baseline({
metric: tree.realized.realizedPriceExtra.ratio,
name,
color,
unit: Unit.ratio,
base: 1,
}),
),
},
],
};
}
+30 -38
View File
@@ -68,12 +68,11 @@ const periodName = (key) => periodIdToName(key.slice(1), true);
/**
* Build DCA class entry from year
* @param {Colors} colors
* @param {MarketDca} dca
* @param {DcaYear} year
* @returns {BaseEntryItem}
*/
function buildYearEntry(colors, dca, year) {
function buildYearEntry(dca, year) {
const key = /** @type {DcaYearKey} */ (`_${year}`);
return {
name: `${year}`,
@@ -184,11 +183,10 @@ function createCompareFolder(context, items) {
/**
* Create single entry tree structure
* @param {Colors} colors
* @param {BaseEntryItem & { titlePrefix?: string }} item
* @param {object[]} returnsBottom - Bottom pane items for returns chart
*/
function createSingleEntryTree(colors, item, returnsBottom) {
function createSingleEntryTree(item, returnsBottom) {
const {
name,
titlePrefix = name,
@@ -217,13 +215,13 @@ function createSingleEntryTree(colors, item, returnsBottom) {
line({
metric: daysInProfit,
name: "Days in Profit",
color: colors.green,
color: colors.profit,
unit: Unit.days,
}),
line({
metric: daysInLoss,
name: "Days in Loss",
color: colors.red,
color: colors.loss,
unit: Unit.days,
}),
],
@@ -240,24 +238,23 @@ function createSingleEntryTree(colors, item, returnsBottom) {
/**
* Create a single entry from a base item (no CAGR)
* @param {Colors} colors
* @param {BaseEntryItem & { titlePrefix?: string }} item
*/
function createShortSingleEntry(colors, item) {
function createShortSingleEntry(item) {
const { returns, minReturn, maxReturn } = item;
return createSingleEntryTree(colors, item, [
return createSingleEntryTree(item, [
baseline({ metric: returns, name: "Current", unit: Unit.percentage }),
dotted({
metric: maxReturn,
name: "Max",
color: colors.green,
color: colors.profit,
unit: Unit.percentage,
defaultActive: false,
}),
dotted({
metric: minReturn,
name: "Min",
color: colors.red,
color: colors.loss,
unit: Unit.percentage,
defaultActive: false,
}),
@@ -266,25 +263,24 @@ function createShortSingleEntry(colors, item) {
/**
* Create a single entry from a long item (with CAGR)
* @param {Colors} colors
* @param {LongEntryItem & { titlePrefix?: string }} item
*/
function createLongSingleEntry(colors, item) {
function createLongSingleEntry(item) {
const { returns, minReturn, maxReturn, cagr } = item;
return createSingleEntryTree(colors, item, [
return createSingleEntryTree(item, [
baseline({ metric: returns, name: "Current", unit: Unit.percentage }),
baseline({ metric: cagr, name: "CAGR", unit: Unit.cagr }),
dotted({
metric: maxReturn,
name: "Max",
color: colors.green,
color: colors.profit,
unit: Unit.percentage,
defaultActive: false,
}),
dotted({
metric: minReturn,
name: "Min",
color: colors.red,
color: colors.loss,
unit: Unit.percentage,
defaultActive: false,
}),
@@ -304,9 +300,9 @@ export function createDcaVsLumpSumSection({ dca, lookback, returns }) {
price({
metric: dca.periodAveragePrice[key],
name: "DCA",
color: colors.green,
color: colors.profit,
}),
price({ metric: lookback[key], name: "Lump Sum", color: colors.orange }),
price({ metric: lookback[key], name: "Lump Sum", color: colors.bitcoin }),
];
/** @param {string} name @param {AllPeriodKey} key */
@@ -331,7 +327,7 @@ export function createDcaVsLumpSumSection({ dca, lookback, returns }) {
baseline({
metric: dca.periodLumpSumMaxReturn[key],
name: "Lump Sum",
color: [colors.cyan, colors.orange],
color: colors.bi.lumpSum,
unit: Unit.percentage,
}),
],
@@ -349,7 +345,7 @@ export function createDcaVsLumpSumSection({ dca, lookback, returns }) {
baseline({
metric: dca.periodLumpSumMinReturn[key],
name: "Lump Sum",
color: [colors.cyan, colors.orange],
color: colors.bi.lumpSum,
unit: Unit.percentage,
}),
],
@@ -373,7 +369,7 @@ export function createDcaVsLumpSumSection({ dca, lookback, returns }) {
baseline({
metric: dca.periodLumpSumReturns[key],
name: "Lump Sum",
color: [colors.cyan, colors.orange],
color: colors.bi.lumpSum,
unit: Unit.percentage,
}),
],
@@ -399,7 +395,7 @@ export function createDcaVsLumpSumSection({ dca, lookback, returns }) {
baseline({
metric: dca.periodLumpSumReturns[key],
name: "Lump Sum",
color: [colors.cyan, colors.orange],
color: colors.bi.lumpSum,
unit: Unit.percentage,
}),
baseline({
@@ -410,7 +406,7 @@ export function createDcaVsLumpSumSection({ dca, lookback, returns }) {
baseline({
metric: returns.cagr[key],
name: "Lump Sum",
color: [colors.cyan, colors.orange],
color: colors.bi.lumpSum,
unit: Unit.cagr,
}),
],
@@ -431,13 +427,13 @@ export function createDcaVsLumpSumSection({ dca, lookback, returns }) {
line({
metric: dca.periodDaysInProfit[key],
name: "DCA",
color: colors.green,
color: colors.profit,
unit: Unit.days,
}),
line({
metric: dca.periodLumpSumDaysInProfit[key],
name: "Lump Sum",
color: colors.orange,
color: colors.bitcoin,
unit: Unit.days,
}),
],
@@ -450,13 +446,13 @@ export function createDcaVsLumpSumSection({ dca, lookback, returns }) {
line({
metric: dca.periodDaysInLoss[key],
name: "DCA",
color: colors.green,
color: colors.profit,
unit: Unit.days,
}),
line({
metric: dca.periodLumpSumDaysInLoss[key],
name: "Lump Sum",
color: colors.orange,
color: colors.bitcoin,
unit: Unit.days,
}),
],
@@ -473,12 +469,12 @@ export function createDcaVsLumpSumSection({ dca, lookback, returns }) {
...satsBtcUsd({
pattern: dca.periodStack[key],
name: "DCA",
color: colors.green,
color: colors.profit,
}),
...satsBtcUsd({
pattern: dca.periodLumpSumStack[key],
name: "Lump Sum",
color: colors.orange,
color: colors.bitcoin,
}),
],
});
@@ -570,14 +566,14 @@ function createPeriodSection({ dca, lookback, returns }) {
/** @param {BaseEntryItem} entry */
const createShortEntry = (entry) =>
createShortSingleEntry(colors, {
createShortSingleEntry({
...entry,
titlePrefix: `${entry.name} ${suffix}`,
});
/** @param {LongEntryItem} entry */
const createLongEntry = (entry) =>
createLongSingleEntry(colors, {
createLongSingleEntry({
...entry,
titlePrefix: `${entry.name} ${suffix}`,
});
@@ -647,7 +643,7 @@ export function createDcaByStartYearSection({ dca }) {
tree: [
createCompareFolder(`${name} DCA`, entries),
...entries.map((entry) =>
createShortSingleEntry(colors, {
createShortSingleEntry({
...entry,
titlePrefix: `${entry.name} DCA`,
}),
@@ -655,12 +651,8 @@ export function createDcaByStartYearSection({ dca }) {
],
});
const entries2020s = YEARS_2020S.map((year) =>
buildYearEntry(colors, dca, year),
);
const entries2010s = YEARS_2010S.map((year) =>
buildYearEntry(colors, dca, year),
);
const entries2020s = YEARS_2020S.map((year) => buildYearEntry(dca, year));
const entries2010s = YEARS_2010S.map((year) => buildYearEntry(dca, year));
return {
name: "DCA by Start Year",
+49 -62
View File
@@ -75,20 +75,19 @@ function createMaSubSection(label, averages) {
}
/**
* @param {Colors} colors
* @param {string} name
* @param {string} title
* @param {Unit} unit
* @param {{ _1w: AnyMetricPattern, _1m: AnyMetricPattern, _1y: AnyMetricPattern }} metrics
*/
function volatilityChart(colors, name, title, unit, metrics) {
function volatilityChart(name, title, unit, metrics) {
return {
name,
title,
bottom: [
line({ metric: metrics._1w, name: "1w", color: colors.red, unit }),
line({ metric: metrics._1m, name: "1m", color: colors.orange, unit }),
line({ metric: metrics._1y, name: "1y", color: colors.lime, unit }),
line({ metric: metrics._1w, name: "1w", color: colors.time._1w, unit }),
line({ metric: metrics._1m, name: "1m", color: colors.time._1m, unit }),
line({ metric: metrics._1y, name: "1y", color: colors.time._1y, unit }),
],
};
}
@@ -423,7 +422,7 @@ export function createMarketSection() {
line({
metric: ath.priceDrawdown,
name: "Drawdown",
color: colors.red,
color: colors.loss,
unit: Unit.percentage,
}),
],
@@ -446,13 +445,13 @@ export function createMarketSection() {
line({
metric: ath.maxDaysBetweenPriceAths,
name: "Max",
color: colors.red,
color: colors.loss,
unit: Unit.days,
}),
line({
metric: ath.maxYearsBetweenPriceAths,
name: "Max",
color: colors.red,
color: colors.loss,
unit: Unit.years,
}),
],
@@ -484,17 +483,11 @@ export function createMarketSection() {
{
name: "Volatility",
tree: [
volatilityChart(
colors,
"Index",
"Volatility Index",
Unit.percentage,
{
_1w: volatility.price1wVolatility,
_1m: volatility.price1mVolatility,
_1y: volatility.price1yVolatility,
},
),
volatilityChart("Index", "Volatility Index", Unit.percentage, {
_1w: volatility.price1wVolatility,
_1m: volatility.price1mVolatility,
_1y: volatility.price1yVolatility,
}),
{
name: "True Range",
title: "True Range",
@@ -502,13 +495,13 @@ export function createMarketSection() {
line({
metric: range.priceTrueRange,
name: "Daily",
color: colors.yellow,
color: colors.time._24h,
unit: Unit.usd,
}),
line({
metric: range.priceTrueRange2wSum,
name: "2w Sum",
color: colors.orange,
color: colors.time._1w,
unit: Unit.usd,
defaultActive: false,
}),
@@ -521,28 +514,22 @@ export function createMarketSection() {
line({
metric: range.price2wChoppinessIndex,
name: "2w",
color: colors.red,
color: colors.indicator.main,
unit: Unit.index,
}),
...priceLines({ unit: Unit.index, numbers: [61.8, 38.2] }),
],
},
volatilityChart(colors, "Sharpe Ratio", "Sharpe Ratio", Unit.ratio, {
volatilityChart("Sharpe Ratio", "Sharpe Ratio", Unit.ratio, {
_1w: volatility.sharpe1w,
_1m: volatility.sharpe1m,
_1y: volatility.sharpe1y,
}),
volatilityChart(
colors,
"Sortino Ratio",
"Sortino Ratio",
Unit.ratio,
{
_1w: volatility.sortino1w,
_1m: volatility.sortino1m,
_1y: volatility.sortino1y,
},
),
volatilityChart("Sortino Ratio", "Sortino Ratio", Unit.ratio, {
_1w: volatility.sortino1w,
_1m: volatility.sortino1m,
_1y: volatility.sortino1y,
}),
],
},
@@ -623,17 +610,17 @@ export function createMarketSection() {
name: p.id,
title: `${p.name} MinMax`,
top: [
price({
metric: p.min,
name: "Min",
key: "price-min",
color: colors.red,
}),
price({
metric: p.max,
name: "Max",
key: "price-max",
color: colors.green,
color: colors.stat.max,
}),
price({
metric: p.min,
name: "Min",
key: "price-min",
color: colors.stat.min,
}),
],
})),
@@ -645,17 +632,17 @@ export function createMarketSection() {
price({
metric: ma.price200dSma.price,
name: "200d SMA",
color: colors.yellow,
color: colors.ma._200d,
}),
price({
metric: ma.price200dSmaX24,
name: "200d SMA x2.4",
color: colors.green,
color: colors.indicator.upper,
}),
price({
metric: ma.price200dSmaX08,
name: "200d SMA x0.8",
color: colors.red,
color: colors.indicator.lower,
}),
],
},
@@ -672,20 +659,20 @@ export function createMarketSection() {
line({
metric: indicators.rsi14d,
name: "RSI",
color: colors.indigo,
unit: Unit.index,
}),
line({
metric: indicators.rsi14dMin,
name: "Min",
color: colors.red,
defaultActive: false,
color: colors.indicator.main,
unit: Unit.index,
}),
line({
metric: indicators.rsi14dMax,
name: "Max",
color: colors.green,
color: colors.stat.max,
defaultActive: false,
unit: Unit.index,
}),
line({
metric: indicators.rsi14dMin,
name: "Min",
color: colors.stat.min,
defaultActive: false,
unit: Unit.index,
}),
@@ -701,13 +688,13 @@ export function createMarketSection() {
line({
metric: indicators.stochRsiK,
name: "K",
color: colors.blue,
color: colors.indicator.fast,
unit: Unit.index,
}),
line({
metric: indicators.stochRsiD,
name: "D",
color: colors.orange,
color: colors.indicator.slow,
unit: Unit.index,
}),
...priceLines({ unit: Unit.index, numbers: [80, 20] }),
@@ -720,13 +707,13 @@ export function createMarketSection() {
line({
metric: indicators.macdLine,
name: "MACD",
color: colors.blue,
color: colors.indicator.fast,
unit: Unit.usd,
}),
line({
metric: indicators.macdSignal,
name: "Signal",
color: colors.orange,
color: colors.indicator.slow,
unit: Unit.usd,
}),
histogram({
@@ -769,12 +756,12 @@ export function createMarketSection() {
price({
metric: ma.price111dSma.price,
name: "111d SMA",
color: colors.green,
color: colors.indicator.upper,
}),
price({
metric: ma.price350dSmaX2,
name: "350d SMA x2",
color: colors.red,
color: colors.indicator.lower,
}),
],
bottom: [
@@ -793,7 +780,7 @@ export function createMarketSection() {
line({
metric: indicators.puellMultiple,
name: "Puell",
color: colors.green,
color: colors.usd,
unit: Unit.ratio,
}),
],
@@ -805,7 +792,7 @@ export function createMarketSection() {
line({
metric: indicators.nvt,
name: "NVT",
color: colors.orange,
color: colors.bitcoin,
unit: Unit.ratio,
}),
],
@@ -817,7 +804,7 @@ export function createMarketSection() {
line({
metric: indicators.gini,
name: "Gini",
color: colors.red,
color: colors.loss,
unit: Unit.ratio,
}),
],
+102 -62
View File
@@ -2,7 +2,7 @@
import { Unit } from "../utils/units.js";
import { entries, includes } from "../utils/array.js";
import { colorAt, colors } from "../utils/colors.js";
import { colors } from "../utils/colors.js";
import {
line,
baseline,
@@ -217,47 +217,89 @@ export function createMiningSection() {
// Hashrate
{
name: "Hashrate",
title: "Network Hashrate",
bottom: [
dots({
metric: blocks.mining.hashRate,
name: "Hashrate",
unit: Unit.hashRate,
}),
line({
metric: blocks.mining.hashRate1wSma,
name: "1w SMA",
color: colors.time._1w,
unit: Unit.hashRate,
defaultActive: false,
}),
line({
metric: blocks.mining.hashRate1mSma,
name: "1m SMA",
color: colors.time._1m,
unit: Unit.hashRate,
defaultActive: false,
}),
line({
metric: blocks.mining.hashRate2mSma,
name: "2m SMA",
color: colors.orange,
unit: Unit.hashRate,
defaultActive: false,
}),
line({
metric: blocks.mining.hashRate1ySma,
name: "1y SMA",
color: colors.time._1y,
unit: Unit.hashRate,
defaultActive: false,
}),
dotted({
metric: blocks.difficulty.asHash,
name: "Difficulty",
color: colors.default,
unit: Unit.hashRate,
}),
tree: [
{
name: "Current",
title: "Network Hashrate",
bottom: [
dots({
metric: blocks.mining.hashRate,
name: "Hashrate",
unit: Unit.hashRate,
}),
line({
metric: blocks.mining.hashRate1wSma,
name: "1w SMA",
color: colors.time._1w,
unit: Unit.hashRate,
defaultActive: false,
}),
line({
metric: blocks.mining.hashRate1mSma,
name: "1m SMA",
color: colors.time._1m,
unit: Unit.hashRate,
defaultActive: false,
}),
line({
metric: blocks.mining.hashRate2mSma,
name: "2m SMA",
color: colors.ma._2m,
unit: Unit.hashRate,
defaultActive: false,
}),
line({
metric: blocks.mining.hashRate1ySma,
name: "1y SMA",
color: colors.time._1y,
unit: Unit.hashRate,
defaultActive: false,
}),
dotted({
metric: blocks.difficulty.asHash,
name: "Difficulty",
color: colors.default,
unit: Unit.hashRate,
}),
line({
metric: blocks.mining.hashRateAth,
name: "ATH",
color: colors.loss,
unit: Unit.hashRate,
defaultActive: false,
}),
],
},
{
name: "ATH",
title: "Network Hashrate ATH",
bottom: [
line({
metric: blocks.mining.hashRateAth,
name: "ATH",
color: colors.loss,
unit: Unit.hashRate,
}),
dots({
metric: blocks.mining.hashRate,
name: "Hashrate",
color: colors.bitcoin,
unit: Unit.hashRate,
}),
],
},
{
name: "Drawdown",
title: "Network Hashrate Drawdown",
bottom: [
line({
metric: blocks.mining.hashRateDrawdown,
name: "Drawdown",
unit: Unit.percentage,
color: colors.loss,
}),
],
},
],
},
@@ -305,13 +347,11 @@ export function createMiningSection() {
line({
metric: blocks.difficulty.blocksBeforeNextAdjustment,
name: "Remaining",
color: colors.indigo,
unit: Unit.blocks,
}),
line({
metric: blocks.difficulty.daysBeforeNextAdjustment,
name: "Remaining",
color: colors.purple,
unit: Unit.days,
}),
],
@@ -466,13 +506,13 @@ export function createMiningSection() {
line({
metric: blocks.rewards.subsidyDominance,
name: "Subsidy",
color: colors.lime,
color: colors.mining.subsidy,
unit: Unit.percentage,
}),
line({
metric: blocks.rewards.feeDominance,
name: "Fees",
color: colors.cyan,
color: colors.mining.fee,
unit: Unit.percentage,
}),
],
@@ -514,25 +554,25 @@ export function createMiningSection() {
line({
metric: blocks.mining.hashPriceThs,
name: "TH/s",
color: colors.emerald,
color: colors.usd,
unit: Unit.usdPerThsPerDay,
}),
line({
metric: blocks.mining.hashPricePhs,
name: "PH/s",
color: colors.emerald,
color: colors.usd,
unit: Unit.usdPerPhsPerDay,
}),
dotted({
metric: blocks.mining.hashPriceThsMin,
name: "TH/s Min",
color: colors.red,
color: colors.stat.min,
unit: Unit.usdPerThsPerDay,
}),
dotted({
metric: blocks.mining.hashPricePhsMin,
name: "PH/s Min",
color: colors.red,
color: colors.stat.min,
unit: Unit.usdPerPhsPerDay,
}),
],
@@ -544,25 +584,25 @@ export function createMiningSection() {
line({
metric: blocks.mining.hashValueThs,
name: "TH/s",
color: colors.orange,
color: colors.bitcoin,
unit: Unit.satsPerThsPerDay,
}),
line({
metric: blocks.mining.hashValuePhs,
name: "PH/s",
color: colors.orange,
color: colors.bitcoin,
unit: Unit.satsPerPhsPerDay,
}),
dotted({
metric: blocks.mining.hashValueThsMin,
name: "TH/s Min",
color: colors.red,
color: colors.stat.min,
unit: Unit.satsPerThsPerDay,
}),
dotted({
metric: blocks.mining.hashValuePhsMin,
name: "PH/s Min",
color: colors.red,
color: colors.stat.min,
unit: Unit.satsPerPhsPerDay,
}),
],
@@ -574,13 +614,13 @@ export function createMiningSection() {
line({
metric: blocks.mining.hashPriceRebound,
name: "Hash Price",
color: colors.emerald,
color: colors.usd,
unit: Unit.percentage,
}),
line({
metric: blocks.mining.hashValueRebound,
name: "Hash Value",
color: colors.orange,
color: colors.bitcoin,
unit: Unit.percentage,
}),
],
@@ -637,7 +677,7 @@ export function createMiningSection() {
line({
metric: p.pool._1mDominance,
name: p.name,
color: colorAt(i),
color: colors.at(i),
unit: Unit.percentage,
}),
),
@@ -649,7 +689,7 @@ export function createMiningSection() {
line({
metric: p.pool._1mBlocksMined,
name: p.name,
color: colorAt(i),
color: colors.at(i),
unit: Unit.count,
}),
),
@@ -662,7 +702,7 @@ export function createMiningSection() {
source: p.pool.coinbase,
key: "sum",
name: p.name,
color: colorAt(i),
color: colors.at(i),
}),
),
},
@@ -679,7 +719,7 @@ export function createMiningSection() {
line({
metric: p.pool._1mDominance,
name: p.name,
color: colorAt(i),
color: colors.at(i),
unit: Unit.percentage,
}),
),
@@ -691,7 +731,7 @@ export function createMiningSection() {
line({
metric: p.pool._1mBlocksMined,
name: p.name,
color: colorAt(i),
color: colors.at(i),
unit: Unit.count,
}),
),
@@ -704,7 +744,7 @@ export function createMiningSection() {
source: p.pool.coinbase,
key: "sum",
name: p.name,
color: colorAt(i),
color: colors.at(i),
}),
),
},
+89 -52
View File
@@ -7,6 +7,7 @@ import { priceLine } from "./constants.js";
import {
line,
dots,
baseline,
fromSupplyPattern,
fromBaseStatsPattern,
chartsFromFull,
@@ -126,17 +127,25 @@ export function createNetworkSection() {
]);
// Count types for comparison charts
// addrCount and emptyAddrCount have .count, totalAddrCount doesn't
const countTypes = /** @type {const} */ ([
{ key: "addrCount", name: "Funded", title: "Address Count by Type" },
{
key: "emptyAddrCount",
name: "Empty",
title: "Empty Address Count by Type",
name: "Funded",
title: "Address Count by Type",
/** @param {AddressableType} t */
getMetric: (t) => distribution.addrCount[t].count,
},
{
name: "Empty",
title: "Empty Address Count by Type",
/** @param {AddressableType} t */
getMetric: (t) => distribution.emptyAddrCount[t].count,
},
{
key: "totalAddrCount",
name: "Total",
title: "Total Address Count by Type",
/** @param {AddressableType} t */
getMetric: (t) => distribution.totalAddrCount[t],
},
]);
@@ -151,7 +160,7 @@ export function createNetworkSection() {
title: `${titlePrefix}Address Count`,
bottom: [
line({
metric: distribution.addrCount[key],
metric: distribution.addrCount[key].count,
name: "Funded",
unit: Unit.count,
}),
@@ -163,7 +172,7 @@ export function createNetworkSection() {
defaultActive: false,
}),
line({
metric: distribution.emptyAddrCount[key],
metric: distribution.emptyAddrCount[key].count,
name: "Empty",
color: colors.gray,
unit: Unit.count,
@@ -172,28 +181,44 @@ export function createNetworkSection() {
],
},
{
name: "New",
tree: chartsFromFull({
pattern: distribution.newAddrCount[key],
title: `${titlePrefix}New Address Count`,
unit: Unit.count,
}),
},
{
name: "Reactivated",
title: `${titlePrefix}Reactivated Address Count`,
bottom: fromBaseStatsPattern({
pattern: distribution.addressActivity[key].reactivated,
unit: Unit.count,
}),
},
{
name: "Growth Rate",
title: `${titlePrefix}Address Growth Rate`,
bottom: fromBaseStatsPattern({
pattern: distribution.growthRate[key],
unit: Unit.ratio,
}),
name: "Trends",
tree: [
{
name: "30d Change",
title: `${titlePrefix}Address Count 30d Change`,
bottom: [
baseline({
metric: distribution.addrCount[key]._30dChange,
name: "30d Change",
unit: Unit.count,
}),
],
},
{
name: "New",
tree: chartsFromFull({
pattern: distribution.newAddrCount[key],
title: `${titlePrefix}New Address Count`,
unit: Unit.count,
}),
},
{
name: "Reactivated",
title: `${titlePrefix}Reactivated Address Count`,
bottom: fromBaseStatsPattern({
pattern: distribution.addressActivity[key].reactivated,
unit: Unit.count,
}),
},
{
name: "Growth Rate",
title: `${titlePrefix}Address Growth Rate`,
bottom: fromBaseStatsPattern({
pattern: distribution.growthRate[key],
unit: Unit.ratio,
}),
},
],
},
{
name: "Transacting",
@@ -235,7 +260,7 @@ export function createNetworkSection() {
title: `${groupName} ${c.title}`,
bottom: types.map((t) =>
line({
metric: distribution[c.key][t.key],
metric: c.getMetric(t.key),
name: t.name,
color: t.color,
unit: Unit.count,
@@ -353,7 +378,7 @@ export function createNetworkSection() {
{ key: "p2ms", name: "P2MS", color: st.p2ms },
]);
const segwitScripts = /** @type {const} */ ([
{ key: "segwit", name: "All SegWit", color: colors.cyan },
{ key: "segwit", name: "All SegWit", color: colors.segwit },
{ key: "p2wsh", name: "P2WSH", color: st.p2wsh },
{ key: "p2wpkh", name: "P2WPKH", color: st.p2wpkh },
]);
@@ -492,7 +517,7 @@ export function createNetworkSection() {
...satsBtcUsd({
pattern: transactions.volume.receivedSum,
name: "Received",
color: colors.cyan,
color: colors.entity.output,
}),
],
},
@@ -530,19 +555,19 @@ export function createNetworkSection() {
line({
metric: transactions.versions.v1.sum,
name: "v1",
color: colors.orange,
color: colors.txVersion.v1,
unit: Unit.count,
}),
line({
metric: transactions.versions.v2.sum,
name: "v2",
color: colors.cyan,
color: colors.txVersion.v2,
unit: Unit.count,
}),
line({
metric: transactions.versions.v3.sum,
name: "v3",
color: colors.lime,
color: colors.txVersion.v3,
unit: Unit.count,
}),
],
@@ -554,19 +579,19 @@ export function createNetworkSection() {
line({
metric: transactions.versions.v1.cumulative,
name: "v1",
color: colors.orange,
color: colors.txVersion.v1,
unit: Unit.count,
}),
line({
metric: transactions.versions.v2.cumulative,
name: "v2",
color: colors.cyan,
color: colors.txVersion.v2,
unit: Unit.count,
}),
line({
metric: transactions.versions.v3.cumulative,
name: "v3",
color: colors.lime,
color: colors.txVersion.v3,
unit: Unit.count,
}),
],
@@ -585,7 +610,7 @@ export function createNetworkSection() {
line({
metric: supply.velocity.usd,
name: "USD",
color: colors.emerald,
color: colors.usd,
unit: Unit.ratio,
}),
],
@@ -625,25 +650,25 @@ export function createNetworkSection() {
line({
metric: blocks.count._24hBlockCount,
name: "24h",
color: colors.pink,
color: colors.time._24h,
unit: Unit.count,
}),
line({
metric: blocks.count._1wBlockCount,
name: "1w",
color: colors.red,
color: colors.time._1w,
unit: Unit.count,
}),
line({
metric: blocks.count._1mBlockCount,
name: "1m",
color: colors.orange,
color: colors.time._1m,
unit: Unit.count,
}),
line({
metric: blocks.count._1yBlockCount,
name: "1y",
color: colors.purple,
color: colors.time._1y,
unit: Unit.count,
}),
],
@@ -839,6 +864,17 @@ export function createNetworkSection() {
}),
],
},
{
name: "30d Change",
title: "UTXO Count 30d Change",
bottom: [
baseline({
metric: distribution.utxoCohorts.all.outputs.utxoCount30dChange,
name: "30d Change",
unit: Unit.count,
}),
],
},
{
name: "Flow",
title: "UTXO Flow",
@@ -846,13 +882,13 @@ export function createNetworkSection() {
line({
metric: outputs.count.totalCount.sum,
name: "Created",
color: colors.lime,
color: colors.entity.output,
unit: Unit.count,
}),
line({
metric: inputs.count.sum,
name: "Spent",
color: colors.red,
color: colors.entity.input,
unit: Unit.count,
}),
],
@@ -882,18 +918,19 @@ export function createNetworkSection() {
dots({
metric: transactions.volume.txPerSec,
name: "TX/sec",
color: colors.red,
color: colors.entity.tx,
unit: Unit.perSec,
}),
dots({
metric: transactions.volume.inputsPerSec,
name: "Inputs/sec",
color: colors.entity.input,
unit: Unit.perSec,
}),
dots({
metric: transactions.volume.outputsPerSec,
name: "Outputs/sec",
color: colors.cyan,
color: colors.entity.output,
unit: Unit.perSec,
}),
],
@@ -917,7 +954,7 @@ export function createNetworkSection() {
title: c.title,
bottom: addressTypes.map((t) =>
line({
metric: distribution[c.key][t.key],
metric: c.getMetric(t.key),
name: t.name,
color: t.color,
unit: Unit.count,
@@ -1198,13 +1235,13 @@ export function createNetworkSection() {
line({
metric: scripts.count.segwitAdoption.cumulative,
name: "SegWit",
color: colors.cyan,
color: colors.segwit,
unit: Unit.percentage,
}),
line({
metric: scripts.count.taprootAdoption.cumulative,
name: "Taproot",
color: colors.orange,
color: colors.scriptType.p2tr,
unit: Unit.percentage,
}),
],
@@ -1226,7 +1263,7 @@ export function createNetworkSection() {
line({
metric: scripts.count.segwitAdoption.cumulative,
name: "All-Time",
color: colors.red,
color: colors.time.all,
unit: Unit.percentage,
}),
],
@@ -1248,7 +1285,7 @@ export function createNetworkSection() {
line({
metric: scripts.count.taprootAdoption.cumulative,
name: "All-Time",
color: colors.red,
color: colors.time.all,
unit: Unit.percentage,
}),
],
+10 -10
View File
@@ -80,22 +80,22 @@ export function createPartialOptions() {
// Overview - All UTXOs (adjustedSopr + percentiles but no RelToMarketCap)
createCohortFolderAll({ ...cohortAll, name: "Overview" }),
// STH - Short term holder cohort (Full capability)
createCohortFolderFull(termShort),
// LTH - Long term holder cohort (nupl)
createCohortFolderWithNupl(termLong),
// STH vs LTH - Direct comparison
// STH vs LTH - Direct comparison (before individual cohorts)
createCohortFolderWithNupl({
name: "STH vs LTH",
title: "STH vs LTH",
list: [termShort, termLong],
}),
// STH - Short term holder cohort (Full capability)
createCohortFolderFull(termShort),
// LTH - Long term holder cohort (nupl)
createCohortFolderWithNupl(termLong),
// Ages cohorts
{
name: "Ages",
name: "UTXO Ages",
tree: [
// Younger Than (< X old)
{
@@ -138,7 +138,7 @@ export function createPartialOptions() {
// Sizes cohorts (UTXO size)
{
name: "Sizes",
name: "UTXO Sizes",
tree: [
// Less Than (< X sats)
{
@@ -187,7 +187,7 @@ export function createPartialOptions() {
// Balances cohorts (Address balance)
{
name: "Balances",
name: "Address Balances",
tree: [
// Less Than (< X sats)
{
+90 -65
View File
@@ -42,6 +42,35 @@ export function satsBtcUsd({ pattern, name, color, defaultActive }) {
];
}
/**
* Create sats/btc/usd baseline series from a value pattern
* @param {Object} args
* @param {{ bitcoin: AnyMetricPattern, sats: AnyMetricPattern, dollars: AnyMetricPattern }} args.pattern
* @param {string} args.name
* @param {Color} [args.color]
* @param {boolean} [args.defaultActive]
* @returns {FetchedBaselineSeriesBlueprint[]}
*/
export function satsBtcUsdBaseline({ pattern, name, color, defaultActive }) {
return [
baseline({
metric: pattern.bitcoin,
name,
color,
unit: Unit.btc,
defaultActive,
}),
baseline({ metric: pattern.sats, name, color, unit: Unit.sats, defaultActive }),
baseline({
metric: pattern.dollars,
name,
color,
unit: Unit.usd,
defaultActive,
}),
];
}
/**
* Create sats/btc/usd series from any value pattern using sum or cumulative key
* @param {Object} args
@@ -109,15 +138,15 @@ export function revenueBtcSatsUsd({ coinbase, subsidy, fee, key }) {
source: coinbase,
key,
name: "Coinbase",
color: colors.orange,
color: colors.mining.coinbase,
}),
...satsBtcUsdFrom({
source: subsidy,
key,
name: "Subsidy",
color: colors.lime,
color: colors.mining.subsidy,
}),
...satsBtcUsdFrom({ source: fee, key, name: "Fees", color: colors.cyan }),
...satsBtcUsdFrom({ source: fee, key, name: "Fees", color: colors.mining.fee }),
];
}
@@ -127,12 +156,12 @@ export function revenueBtcSatsUsd({ coinbase, subsidy, fee, key }) {
*/
export function percentileUsdMap(ratio) {
return /** @type {const} */ ([
{ name: "pct95", prop: ratio.ratioPct95Usd, color: colors.fuchsia },
{ name: "pct5", prop: ratio.ratioPct5Usd, color: colors.cyan },
{ name: "pct98", prop: ratio.ratioPct98Usd, color: colors.pink },
{ name: "pct2", prop: ratio.ratioPct2Usd, color: colors.sky },
{ name: "pct99", prop: ratio.ratioPct99Usd, color: colors.rose },
{ name: "pct1", prop: ratio.ratioPct1Usd, color: colors.blue },
{ name: "pct95", prop: ratio.ratioPct95Usd, color: colors.ratioPct._95 },
{ name: "pct5", prop: ratio.ratioPct5Usd, color: colors.ratioPct._5 },
{ name: "pct98", prop: ratio.ratioPct98Usd, color: colors.ratioPct._98 },
{ name: "pct2", prop: ratio.ratioPct2Usd, color: colors.ratioPct._2 },
{ name: "pct99", prop: ratio.ratioPct99Usd, color: colors.ratioPct._99 },
{ name: "pct1", prop: ratio.ratioPct1Usd, color: colors.ratioPct._1 },
]);
}
@@ -142,12 +171,12 @@ export function percentileUsdMap(ratio) {
*/
export function percentileMap(ratio) {
return /** @type {const} */ ([
{ name: "pct95", prop: ratio.ratioPct95, color: colors.fuchsia },
{ name: "pct5", prop: ratio.ratioPct5, color: colors.cyan },
{ name: "pct98", prop: ratio.ratioPct98, color: colors.pink },
{ name: "pct2", prop: ratio.ratioPct2, color: colors.sky },
{ name: "pct99", prop: ratio.ratioPct99, color: colors.rose },
{ name: "pct1", prop: ratio.ratioPct1, color: colors.blue },
{ name: "pct95", prop: ratio.ratioPct95, color: colors.ratioPct._95 },
{ name: "pct5", prop: ratio.ratioPct5, color: colors.ratioPct._5 },
{ name: "pct98", prop: ratio.ratioPct98, color: colors.ratioPct._98 },
{ name: "pct2", prop: ratio.ratioPct2, color: colors.ratioPct._2 },
{ name: "pct99", prop: ratio.ratioPct99, color: colors.ratioPct._99 },
{ name: "pct1", prop: ratio.ratioPct1, color: colors.ratioPct._1 },
]);
}
@@ -170,19 +199,19 @@ export function sdPatterns(ratio) {
*/
export function sdBandsUsd(sd) {
return /** @type {const} */ ([
{ name: "0σ", prop: sd._0sdUsd, color: colors.lime },
{ name: "+0.5σ", prop: sd.p05sdUsd, color: colors.yellow },
{ name: "0.5σ", prop: sd.m05sdUsd, color: colors.teal },
{ name: "+1σ", prop: sd.p1sdUsd, color: colors.amber },
{ name: "1σ", prop: sd.m1sdUsd, color: colors.cyan },
{ name: "+1.5σ", prop: sd.p15sdUsd, color: colors.orange },
{ name: "1.5σ", prop: sd.m15sdUsd, color: colors.sky },
{ name: "+2σ", prop: sd.p2sdUsd, color: colors.red },
{ 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 },
{ name: "0σ", prop: sd._0sdUsd, color: colors.sd._0 },
{ name: "+0.5σ", prop: sd.p05sdUsd, color: colors.sd.p05 },
{ name: "0.5σ", prop: sd.m05sdUsd, color: colors.sd.m05 },
{ name: "+1σ", prop: sd.p1sdUsd, color: colors.sd.p1 },
{ name: "1σ", prop: sd.m1sdUsd, color: colors.sd.m1 },
{ name: "+1.5σ", prop: sd.p15sdUsd, color: colors.sd.p15 },
{ name: "1.5σ", prop: sd.m15sdUsd, color: colors.sd.m15 },
{ name: "+2σ", prop: sd.p2sdUsd, color: colors.sd.p2 },
{ name: "2σ", prop: sd.m2sdUsd, color: colors.sd.m2 },
{ name: "+2.5σ", prop: sd.p25sdUsd, color: colors.sd.p25 },
{ name: "2.5σ", prop: sd.m25sdUsd, color: colors.sd.m25 },
{ name: "+3σ", prop: sd.p3sdUsd, color: colors.sd.p3 },
{ name: "3σ", prop: sd.m3sdUsd, color: colors.sd.m3 },
]);
}
@@ -192,19 +221,19 @@ export function sdBandsUsd(sd) {
*/
export function sdBandsRatio(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 },
{ name: "0σ", prop: sd.sma, color: colors.sd._0 },
{ name: "+0.5σ", prop: sd.p05sd, color: colors.sd.p05 },
{ name: "0.5σ", prop: sd.m05sd, color: colors.sd.m05 },
{ name: "+1σ", prop: sd.p1sd, color: colors.sd.p1 },
{ name: "1σ", prop: sd.m1sd, color: colors.sd.m1 },
{ name: "+1.5σ", prop: sd.p15sd, color: colors.sd.p15 },
{ name: "1.5σ", prop: sd.m15sd, color: colors.sd.m15 },
{ name: "+2σ", prop: sd.p2sd, color: colors.sd.p2 },
{ name: "2σ", prop: sd.m2sd, color: colors.sd.m2 },
{ name: "+2.5σ", prop: sd.p25sd, color: colors.sd.p25 },
{ name: "2.5σ", prop: sd.m25sd, color: colors.sd.m25 },
{ name: "+3σ", prop: sd.p3sd, color: colors.sd.p3 },
{ name: "3σ", prop: sd.m3sd, color: colors.sd.m3 },
]);
}
@@ -214,12 +243,12 @@ export function sdBandsRatio(sd) {
*/
export function ratioSmas(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 },
{ name: "1w SMA", metric: ratio.ratio1wSma, color: colors.ma._1w },
{ name: "1m SMA", metric: ratio.ratio1mSma, color: colors.ma._1m },
{ name: "1y SMA", metric: ratio.ratio1ySd.sma, color: colors.ma._1y },
{ name: "2y SMA", metric: ratio.ratio2ySd.sma, color: colors.ma._2y },
{ name: "4y SMA", metric: ratio.ratio4ySd.sma, color: colors.ma._4y },
{ name: "All SMA", metric: ratio.ratioSd.sma, color: colors.time.all },
]);
}
@@ -303,25 +332,25 @@ export function createZScoresFolder({
price({
metric: ratio.ratio1ySd._0sdUsd,
name: "1y 0σ",
color: colors.orange,
color: colors.ma._1y,
defaultActive: false,
}),
price({
metric: ratio.ratio2ySd._0sdUsd,
name: "2y 0σ",
color: colors.yellow,
color: colors.ma._2y,
defaultActive: false,
}),
price({
metric: ratio.ratio4ySd._0sdUsd,
name: "4y 0σ",
color: colors.lime,
color: colors.ma._4y,
defaultActive: false,
}),
price({
metric: ratio.ratioSd._0sdUsd,
name: "all 0σ",
color: colors.blue,
color: colors.time.all,
defaultActive: false,
}),
],
@@ -329,25 +358,25 @@ export function createZScoresFolder({
line({
metric: ratio.ratioSd.zscore,
name: "All",
color: colors.blue,
color: colors.time.all,
unit: Unit.sd,
}),
line({
metric: ratio.ratio4ySd.zscore,
name: "4y",
color: colors.lime,
color: colors.ma._4y,
unit: Unit.sd,
}),
line({
metric: ratio.ratio2ySd.zscore,
name: "2y",
color: colors.yellow,
color: colors.ma._2y,
unit: Unit.sd,
}),
line({
metric: ratio.ratio1ySd.zscore,
name: "1y",
color: colors.orange,
color: colors.ma._1y,
unit: Unit.sd,
}),
...priceLines({
@@ -422,9 +451,8 @@ export function createZScoresFolder({
* @param {AnyPricePattern} args.pricePattern - The price pattern
* @param {AnyRatioPattern} args.ratio - The ratio pattern
* @param {Color} args.color
* @param {string} [args.ratioName] - Optional custom name for ratio chart (default: "ratio")
* @param {string} [args.priceTitle] - Optional override for price chart title (default: context)
* @param {string} [args.zScoresSuffix] - Optional suffix appended to context for z-scores (e.g., "MVRV" gives "2y Z-Score: STH MVRV")
* @param {string} [args.titlePrefix] - Optional prefix for ratio/z-scores titles (e.g., "Realized Price" gives "Realized Price Ratio: STH")
* @param {FetchedPriceSeriesBlueprint[]} [args.priceReferences] - Optional additional price series to show in Price chart
* @returns {PartialOptionsTree}
*/
@@ -434,15 +462,11 @@ export function createPriceRatioCharts({
pricePattern,
ratio,
color,
ratioName,
priceTitle,
zScoresSuffix,
titlePrefix,
priceReferences,
}) {
const titleFn = formatCohortTitle(context);
const zScoresTitleFn = zScoresSuffix
? formatCohortTitle(`${context} ${zScoresSuffix}`)
: titleFn;
return [
{
name: "Price",
@@ -453,14 +477,15 @@ export function createPriceRatioCharts({
],
},
createRatioChart({
title: titleFn,
title: (name) =>
titleFn(titlePrefix ? `${titlePrefix} ${name}` : name),
pricePattern,
ratio,
color,
name: ratioName,
}),
createZScoresFolder({
formatTitle: zScoresTitleFn,
formatTitle: (name) =>
titleFn(titlePrefix ? `${titlePrefix} ${name}` : name),
legend,
pricePattern,
ratio,
+2 -2
View File
@@ -180,7 +180,7 @@
* @property {string} title
* @property {Color} color
* @property {PatternAll} tree
* @property {Brk.MetricPattern1<Brk.StoredU64>} addrCount
* @property {Brk._30dCountPattern} addrCount
*
* Full cohort: adjustedSopr + percentiles + RelToMarketCap (term.short)
* @typedef {Object} CohortFull
@@ -253,7 +253,7 @@
* ============================================================================
*
* Addressable cohort with address count (for "type" cohorts - no RelToMarketCap)
* @typedef {CohortBasicWithoutMarketCap & { addrCount: Brk.MetricPattern1<Brk.StoredU64> }} CohortAddress
* @typedef {CohortBasicWithoutMarketCap & { addrCount: Brk._30dCountPattern }} CohortAddress
*
* ============================================================================
* Cohort Group Types (by capability)
+1 -1
View File
@@ -61,7 +61,7 @@ export function init() {
type: "Candlestick",
title: "Price",
metric: brk.metrics.price.sats.ohlc,
colors: [colors.red, colors.green],
colors: colors.bi.profitLoss,
};
result.set(Unit.sats, [satsPrice, ...(optionTop.get(Unit.sats) ?? [])]);
+13 -9
View File
@@ -10,7 +10,7 @@
*
* @import { SingleValueData, CandlestickData, Series, AnySeries, ISeries, HistogramData, LineData, BaselineData, LineSeriesPartialOptions, BaselineSeriesPartialOptions, HistogramSeriesPartialOptions, CandlestickSeriesPartialOptions, Chart, Legend } from "./chart/index.js"
*
* @import { Color, ColorName, Colors } from "./utils/colors.js"
* @import { Color } from "./utils/colors.js"
*
* @import { WebSockets } from "./utils/ws.js"
*
@@ -77,20 +77,22 @@
* Relative patterns by capability:
* - BasicRelativePattern: minimal relative (investedCapitalIn*Pct, supplyIn*RelToOwnSupply only)
* - GlobalRelativePattern: has RelToMarketCap metrics (netUnrealizedPnlRelToMarketCap, etc)
* - GlobalPeakRelativePattern: GlobalRelativePattern + unrealizedPeakRegretRelToMarketCap
* - OwnRelativePattern: has RelToOwnMarketCap metrics (netUnrealizedPnlRelToOwnMarketCap, etc)
* - FullRelativePattern: has BOTH RelToMarketCap AND RelToOwnMarketCap
* - FullRelativePattern: has BOTH RelToMarketCap AND RelToOwnMarketCap + unrealizedPeakRegretRelToMarketCap
* @typedef {Brk.InvestedSupplyPattern} BasicRelativePattern
* @typedef {Brk.InvestedNegNetNuplSupplyUnrealizedPattern} GlobalRelativePattern
* @typedef {Brk.InvestedNegNetNuplSupplyUnrealizedPattern3} GlobalPeakRelativePattern
* @typedef {Brk.InvestedNegNetSupplyUnrealizedPattern} OwnRelativePattern
* @typedef {Brk.InvestedNegNetNuplSupplyUnrealizedPattern2} FullRelativePattern
* @typedef {Brk.InvestedNegNetNuplSupplyUnrealizedPattern4} FullRelativePattern
* @typedef {Brk.GreedInvestedInvestorNegNetPainSupplyTotalUnrealizedPattern} UnrealizedPattern
* @typedef {Brk.GreedInvestedInvestorNegNetPainPeakSupplyTotalUnrealizedPattern} UnrealizedFullPattern
*
* Realized patterns
* @typedef {Brk.CapCapitulationInvestorLossMvrvNegNetPeakProfitRealizedSellSoprTotalValuePattern} RealizedPattern
* @typedef {Brk.CapCapitulationInvestorLossMvrvNegNetPeakProfitRealizedSellSoprTotalValuePattern2} RealizedPattern2
* @typedef {Brk.AdjustedCapCapitulationInvestorLossMvrvNegNetPeakProfitRealizedSellSoprTotalValuePattern} RealizedPattern3
* @typedef {Brk.AdjustedCapCapitulationInvestorLossMvrvNegNetPeakProfitRealizedSellSoprTotalValuePattern2} RealizedPattern4
* @typedef {Brk.CapCapitulationInvestorLossMvrvNegNetPeakProfitRealizedSellSentSoprTotalValuePattern} RealizedPattern
* @typedef {Brk.CapCapitulationInvestorLossMvrvNegNetPeakProfitRealizedSellSentSoprTotalValuePattern2} RealizedPattern2
* @typedef {Brk.AdjustedCapCapitulationInvestorLossMvrvNegNetPeakProfitRealizedSellSentSoprTotalValuePattern} RealizedPattern3
* @typedef {Brk.AdjustedCapCapitulationInvestorLossMvrvNegNetPeakProfitRealizedSellSentSoprTotalValuePattern2} RealizedPattern4
*/
/**
@@ -153,10 +155,11 @@
* @typedef {UtxoCohortPattern | AddressCohortPattern} CohortPattern
*
* Relative pattern capability types
* @typedef {GlobalRelativePattern | FullRelativePattern} RelativeWithMarketCap
* @typedef {GlobalRelativePattern | FullRelativePattern | AllRelativePattern} RelativeWithMarketCap
* @typedef {OwnRelativePattern | FullRelativePattern} RelativeWithOwnMarketCap
* @typedef {OwnRelativePattern | FullRelativePattern | AllRelativePattern} RelativeWithOwnPnl
* @typedef {GlobalRelativePattern | FullRelativePattern} RelativeWithNupl
* @typedef {GlobalRelativePattern | FullRelativePattern | AllRelativePattern} RelativeWithNupl
* @typedef {GlobalPeakRelativePattern | FullRelativePattern | AllRelativePattern} RelativeWithPeakRegret
* @typedef {BasicRelativePattern | GlobalRelativePattern | OwnRelativePattern | FullRelativePattern | AllRelativePattern} RelativeWithInvestedCapitalPct
*
* Realized pattern capability types
@@ -173,6 +176,7 @@
* @typedef {AllUtxoPattern | AgeRangePattern | UtxoAmountPattern} PatternWithCostBasis
* @typedef {AllUtxoPattern | AgeRangePattern | UtxoAmountPattern} PatternWithActivity
* @typedef {AllUtxoPattern | AgeRangePattern} PatternWithCostBasisPercentiles
* @typedef {Brk.Pct05Pct10Pct15Pct20Pct25Pct30Pct35Pct40Pct45Pct50Pct55Pct60Pct65Pct70Pct75Pct80Pct85Pct90Pct95Pattern} PercentilesPattern
*
* Cohort objects with specific pattern capabilities
* @typedef {{ name: string, title: string, color: Color, tree: PatternWithRealizedPrice }} CohortWithRealizedPrice
+335 -224
View File
@@ -67,270 +67,381 @@ 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"));
const spectrumColors = {
red,
orange,
amber,
yellow,
avocado,
lime,
green,
emerald,
teal,
cyan,
sky,
blue,
indigo,
violet,
purple,
fuchsia,
pink,
rose,
};
const baseColors = {
default: createColor(() => getLightDarkValue("--color")),
gray: createColor(() => getColor("gray")),
border: createColor(() => getLightDarkValue("--border-color")),
...spectrumColors,
const palette = {
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")),
};
export const colors = {
...baseColors,
default: createColor(() => getLightDarkValue("--color")),
gray: createColor(() => getColor("gray")),
border: createColor(() => getLightDarkValue("--border-color")),
// Directional
profit: palette.green,
loss: palette.red,
bitcoin: palette.orange,
usd: palette.green,
// Bi-color pairs for baselines
bi: {
/** @type {[Color, Color]} */
profitLoss: [palette.green, palette.red],
/** @type {[Color, Color]} */
sopr7d: [palette.lime, palette.rose],
/** @type {[Color, Color]} */
sopr30d: [palette.avocado, palette.pink],
/** @type {[Color, Color]} */
adjustedSopr: [palette.yellow, palette.fuchsia],
/** @type {[Color, Color]} */
adjustedSopr7d: [palette.amber, palette.purple],
/** @type {[Color, Color]} */
adjustedSopr30d: [palette.orange, palette.violet],
/** @type {[Color, Color]} */
lumpSum: [palette.cyan, palette.orange],
},
// Cointime economics
liveliness: palette.pink,
vaulted: palette.lime,
active: palette.rose,
activity: palette.purple,
cointime: palette.yellow,
destroyed: palette.red,
created: palette.orange,
stored: palette.green,
// Valuations
realized: palette.orange,
investor: palette.fuchsia,
thermo: palette.emerald,
trueMarketMean: palette.blue,
vocdd: palette.purple,
hodlBank: palette.blue,
reserveRisk: palette.orange,
// Comparisons (base vs adjusted)
base: palette.orange,
adjusted: palette.purple,
adjustedCreated: palette.lime,
adjustedDestroyed: palette.pink,
// Ratios
plRatio: palette.yellow,
// Mining
mining: {
coinbase: palette.orange,
subsidy: palette.lime,
fee: palette.cyan,
},
// Network
segwit: palette.cyan,
// Entity (transactions, inputs, outputs)
entity: {
tx: palette.orange,
input: palette.red,
output: palette.cyan,
},
// Technical indicators
indicator: {
main: palette.indigo,
fast: palette.blue,
slow: palette.orange,
upper: palette.green,
lower: palette.red,
mid: palette.yellow,
},
stat: {
sum: blue,
cumulative: indigo,
avg: orange,
max: green,
pct90: cyan,
pct75: blue,
median: yellow,
pct25: violet,
pct10: fuchsia,
min: red,
sum: palette.blue,
cumulative: palette.indigo,
avg: palette.orange,
max: palette.green,
pct90: palette.cyan,
pct75: palette.blue,
median: palette.yellow,
pct25: palette.violet,
pct10: palette.fuchsia,
min: palette.red,
},
// Ratio percentile bands (extreme values)
ratioPct: {
_99: palette.rose,
_98: palette.pink,
_95: palette.fuchsia,
_5: palette.cyan,
_2: palette.sky,
_1: palette.blue,
},
// Standard deviation bands (warm = positive, cool = negative)
sd: {
_0: palette.lime,
p05: palette.yellow,
m05: palette.teal,
p1: palette.amber,
m1: palette.cyan,
p15: palette.orange,
m15: palette.sky,
p2: palette.red,
m2: palette.blue,
p25: palette.rose,
m25: palette.indigo,
p3: palette.pink,
m3: palette.violet,
},
// Transaction versions
txVersion: {
v1: palette.orange,
v2: palette.cyan,
v3: palette.lime,
},
pct: {
_100: palette.red,
_95: palette.orange,
_90: palette.amber,
_85: palette.yellow,
_80: palette.avocado,
_75: palette.lime,
_70: palette.green,
_65: palette.emerald,
_60: palette.teal,
_55: palette.cyan,
_50: palette.sky,
_45: palette.blue,
_40: palette.indigo,
_35: palette.violet,
_30: palette.purple,
_25: palette.fuchsia,
_20: palette.pink,
_15: palette.rose,
_10: palette.pink,
_05: palette.fuchsia,
_0: palette.purple,
},
time: {
_24h: pink,
_1w: red,
_1m: yellow,
_1y: lime,
all: teal,
_24h: palette.pink,
_1w: palette.red,
_1m: palette.yellow,
_1y: palette.lime,
all: palette.teal,
},
term: {
short: yellow,
long: fuchsia,
short: palette.yellow,
long: palette.fuchsia,
},
age: {
_1d: red,
_1w: orange,
_1m: yellow,
_2m: lime,
_3m: green,
_4m: teal,
_5m: cyan,
_6m: blue,
_1y: indigo,
_2y: violet,
_3y: purple,
_4y: fuchsia,
_5y: pink,
_6y: rose,
_7y: red,
_8y: orange,
_10y: yellow,
_12y: lime,
_15y: green,
_1d: palette.red,
_1w: palette.orange,
_1m: palette.yellow,
_2m: palette.lime,
_3m: palette.green,
_4m: palette.teal,
_5m: palette.cyan,
_6m: palette.blue,
_1y: palette.indigo,
_2y: palette.violet,
_3y: palette.purple,
_4y: palette.fuchsia,
_5y: palette.pink,
_6y: palette.rose,
_7y: palette.red,
_8y: palette.orange,
_10y: palette.yellow,
_12y: palette.lime,
_15y: palette.green,
},
ageRange: {
upTo1h: rose,
_1hTo1d: pink,
_1dTo1w: red,
_1wTo1m: orange,
_1mTo2m: yellow,
_2mTo3m: yellow,
_3mTo4m: lime,
_4mTo5m: lime,
_5mTo6m: lime,
_6mTo1y: green,
_1yTo2y: cyan,
_2yTo3y: blue,
_3yTo4y: indigo,
_4yTo5y: violet,
_5yTo6y: purple,
_6yTo7y: purple,
_7yTo8y: fuchsia,
_8yTo10y: fuchsia,
_10yTo12y: pink,
_12yTo15y: red,
from15y: orange,
upTo1h: palette.rose,
_1hTo1d: palette.pink,
_1dTo1w: palette.red,
_1wTo1m: palette.orange,
_1mTo2m: palette.yellow,
_2mTo3m: palette.yellow,
_3mTo4m: palette.lime,
_4mTo5m: palette.lime,
_5mTo6m: palette.lime,
_6mTo1y: palette.green,
_1yTo2y: palette.cyan,
_2yTo3y: palette.blue,
_3yTo4y: palette.indigo,
_4yTo5y: palette.violet,
_5yTo6y: palette.purple,
_6yTo7y: palette.purple,
_7yTo8y: palette.fuchsia,
_8yTo10y: palette.fuchsia,
_10yTo12y: palette.pink,
_12yTo15y: palette.red,
from15y: palette.orange,
},
amount: {
_1sat: orange,
_10sats: orange,
_100sats: yellow,
_1kSats: lime,
_10kSats: green,
_100kSats: cyan,
_1mSats: blue,
_10mSats: indigo,
_1btc: purple,
_10btc: violet,
_100btc: fuchsia,
_1kBtc: pink,
_10kBtc: red,
_100kBtc: orange,
_1sat: palette.orange,
_10sats: palette.orange,
_100sats: palette.yellow,
_1kSats: palette.lime,
_10kSats: palette.green,
_100kSats: palette.cyan,
_1mSats: palette.blue,
_10mSats: palette.indigo,
_1btc: palette.purple,
_10btc: palette.violet,
_100btc: palette.fuchsia,
_1kBtc: palette.pink,
_10kBtc: palette.red,
_100kBtc: palette.orange,
},
amountRange: {
_0sats: red,
_1satTo10sats: orange,
_10satsTo100sats: yellow,
_100satsTo1kSats: lime,
_1kSatsTo10kSats: green,
_10kSatsTo100kSats: cyan,
_100kSatsTo1mSats: blue,
_1mSatsTo10mSats: indigo,
_10mSatsTo1btc: purple,
_1btcTo10btc: violet,
_10btcTo100btc: fuchsia,
_100btcTo1kBtc: pink,
_1kBtcTo10kBtc: red,
_10kBtcTo100kBtc: orange,
_100kBtcOrMore: yellow,
_0sats: palette.red,
_1satTo10sats: palette.orange,
_10satsTo100sats: palette.yellow,
_100satsTo1kSats: palette.lime,
_1kSatsTo10kSats: palette.green,
_10kSatsTo100kSats: palette.cyan,
_100kSatsTo1mSats: palette.blue,
_1mSatsTo10mSats: palette.indigo,
_10mSatsTo1btc: palette.purple,
_1btcTo10btc: palette.violet,
_10btcTo100btc: palette.fuchsia,
_100btcTo1kBtc: palette.pink,
_1kBtcTo10kBtc: palette.red,
_10kBtcTo100kBtc: palette.orange,
_100kBtcOrMore: palette.yellow,
},
epoch: {
_0: red,
_1: yellow,
_2: orange,
_3: lime,
_4: green,
_0: palette.red,
_1: palette.yellow,
_2: palette.orange,
_3: palette.lime,
_4: palette.green,
},
year: {
_2009: red,
_2010: orange,
_2011: amber,
_2012: yellow,
_2013: lime,
_2014: green,
_2015: teal,
_2016: cyan,
_2017: sky,
_2018: blue,
_2019: indigo,
_2020: violet,
_2021: purple,
_2022: fuchsia,
_2023: pink,
_2024: rose,
_2025: red,
_2026: orange,
_2009: palette.red,
_2010: palette.orange,
_2011: palette.amber,
_2012: palette.yellow,
_2013: palette.lime,
_2014: palette.green,
_2015: palette.teal,
_2016: palette.cyan,
_2017: palette.sky,
_2018: palette.blue,
_2019: palette.indigo,
_2020: palette.violet,
_2021: palette.purple,
_2022: palette.fuchsia,
_2023: palette.pink,
_2024: palette.rose,
_2025: palette.red,
_2026: palette.orange,
},
returns: {
_1d: red,
_1w: orange,
_1m: yellow,
_3m: lime,
_6m: green,
_1y: teal,
_2y: cyan,
_3y: sky,
_4y: blue,
_5y: indigo,
_6y: violet,
_8y: purple,
_10y: fuchsia,
_1d: palette.red,
_1w: palette.orange,
_1m: palette.yellow,
_3m: palette.lime,
_6m: palette.green,
_1y: palette.teal,
_2y: palette.cyan,
_3y: palette.sky,
_4y: palette.blue,
_5y: palette.indigo,
_6y: palette.violet,
_8y: palette.purple,
_10y: palette.fuchsia,
},
ma: {
_1w: red,
_8d: orange,
_12d: amber,
_13d: yellow,
_21d: avocado,
_26d: lime,
_1m: green,
_34d: emerald,
_55d: teal,
_89d: cyan,
_111d: sky,
_144d: blue,
_200d: indigo,
_350d: violet,
_1y: purple,
_2y: fuchsia,
_200w: pink,
_4y: rose,
_1w: palette.red,
_8d: palette.orange,
_12d: palette.amber,
_13d: palette.yellow,
_21d: palette.avocado,
_26d: palette.lime,
_1m: palette.green,
_34d: palette.emerald,
_55d: palette.teal,
_2m: palette.cyan,
_89d: palette.sky,
_111d: palette.blue,
_144d: palette.indigo,
_200d: palette.violet,
_350d: palette.purple,
_1y: palette.fuchsia,
_2y: palette.pink,
_200w: palette.rose,
_4y: palette.red,
},
dca: {
_1w: red,
_1m: orange,
_3m: yellow,
_6m: lime,
_1y: green,
_2y: teal,
_3y: cyan,
_4y: sky,
_5y: blue,
_6y: indigo,
_8y: violet,
_10y: purple,
_1w: palette.red,
_1m: palette.orange,
_3m: palette.yellow,
_6m: palette.lime,
_1y: palette.green,
_2y: palette.teal,
_3y: palette.cyan,
_4y: palette.sky,
_5y: palette.blue,
_6y: palette.indigo,
_8y: palette.violet,
_10y: palette.purple,
},
scriptType: {
p2pk65: red,
p2pk33: orange,
p2pkh: yellow,
p2ms: lime,
p2sh: green,
p2wpkh: teal,
p2wsh: blue,
p2tr: indigo,
p2a: purple,
opreturn: pink,
unknown: violet,
empty: fuchsia,
p2pk65: palette.red,
p2pk33: palette.orange,
p2pkh: palette.yellow,
p2ms: palette.lime,
p2sh: palette.green,
p2wpkh: palette.teal,
p2wsh: palette.blue,
p2tr: palette.indigo,
p2a: palette.purple,
opreturn: palette.pink,
unknown: palette.violet,
empty: palette.fuchsia,
},
arr: Object.values(palette),
/**
* Get a color by index (cycles through palette)
* @param {number} index
*/
at(index) {
return this.arr[index % this.arr.length];
},
};
/**
* @typedef {typeof colors} Colors
* @typedef {keyof typeof baseColors} ColorName
*/
/** Palette for indexed series */
const palette = Object.values(spectrumColors);
/**
* Get a color by index (cycles through palette)
* @param {number} index
*/
export const colorAt = (index) => palette[index % palette.length];
+11 -1
View File
@@ -45,12 +45,13 @@ export function throttle(callback, wait = 1000) {
* @template {(...args: any[]) => any} F
* @param {F} callback
* @param {number} [wait]
* @returns {((...args: Parameters<F>) => void) & { cancel: () => void }}
*/
export function debounce(callback, wait = 1000) {
/** @type {number | null} */
let timeoutId = null;
return (/** @type {Parameters<F>} */ ...args) => {
const fn = (/** @type {Parameters<F>} */ ...args) => {
if (timeoutId) {
clearTimeout(timeoutId);
}
@@ -59,4 +60,13 @@ export function debounce(callback, wait = 1000) {
timeoutId = null;
}, wait);
};
fn.cancel = () => {
if (timeoutId) {
clearTimeout(timeoutId);
timeoutId = null;
}
};
return fn;
}