mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-06-18 02:39:43 -07:00
website: snapshot
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
}),
|
||||
],
|
||||
|
||||
@@ -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 }),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -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,
|
||||
}),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -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,
|
||||
}),
|
||||
),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
}),
|
||||
],
|
||||
|
||||
@@ -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),
|
||||
}),
|
||||
),
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
}),
|
||||
],
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) ?? [])]);
|
||||
|
||||
|
||||
@@ -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
@@ -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];
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user