global: MASSIVE snapshot

This commit is contained in:
nym21
2026-01-07 01:16:37 +01:00
parent e832ffbe23
commit cb0abc324e
487 changed files with 21155 additions and 13627 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -4,6 +4,7 @@
* Address cohorts use _0satsPattern which has CostBasisPattern (no percentiles)
*/
import { Unit } from "../../utils/units.js";
import {
createSingleSupplySeries,
createGroupedSupplyTotalSeries,
@@ -37,7 +38,10 @@ export function createAddressCohortFolder(ctx, args) {
? {
name: "supply",
title: `Supply ${title}`,
bottom: createSingleSupplySeries(ctx, /** @type {AddressCohortObject} */ (args), title),
bottom: createSingleSupplySeries(
ctx,
/** @type {AddressCohortObject} */ (args),
),
}
: {
name: "supply",
@@ -91,13 +95,23 @@ export function createAddressCohortFolder(ctx, args) {
bottom: createRealizedPriceRatioSeries(ctx, list),
},
]
: createRealizedPriceOptions(ctx, /** @type {AddressCohortObject} */ (args), title)),
: createRealizedPriceOptions(
ctx,
/** @type {AddressCohortObject} */ (args),
title,
)),
{
name: "capitalization",
title: `Realized Capitalization ${title}`,
bottom: createRealizedCapWithExtras(ctx, list, args, useGroupName, title),
bottom: createRealizedCapWithExtras(ctx, list, args, useGroupName),
},
...(!useGroupName ? createRealizedPnlSection(ctx, /** @type {AddressCohortObject} */ (args), title) : []),
...(!useGroupName
? createRealizedPnlSection(
ctx,
/** @type {AddressCohortObject} */ (args),
title,
)
: []),
],
},
@@ -128,7 +142,14 @@ function createRealizedPriceOptions(ctx, args, title) {
{
name: "price",
title: `Realized Price ${title}`,
top: [s({ metric: tree.realized.realizedPrice, name: "realized", color })],
top: [
s({
metric: tree.realized.realizedPrice,
name: "Realized",
color,
unit: Unit.usd,
}),
],
},
];
}
@@ -139,24 +160,29 @@ function createRealizedPriceOptions(ctx, args, title) {
* @param {readonly AddressCohortObject[]} list
* @param {AddressCohortObject | AddressCohortGroupObject} args
* @param {boolean} useGroupName
* @param {string} title
* @returns {AnyFetchedSeriesBlueprint[]}
*/
function createRealizedCapWithExtras(ctx, list, args, useGroupName, title) {
const { colors, s, createPriceLine } = ctx;
function createRealizedCapWithExtras(ctx, list, args, useGroupName) {
const { s, createPriceLine } = ctx;
const isSingle = !("list" in args);
return list.flatMap(({ color, name, tree }) => [
s({ metric: tree.realized.realizedCap, name: useGroupName ? name : "Capitalization", color }),
s({
metric: tree.realized.realizedCap,
name: useGroupName ? name : "Capitalization",
color,
unit: Unit.usd,
}),
...(isSingle
? [
/** @type {AnyFetchedSeriesBlueprint} */ ({
type: "Baseline",
metric: tree.realized.realizedCap30dDelta,
title: "30d change",
title: "30d Change",
unit: Unit.usd,
defaultActive: false,
}),
createPriceLine({ unit: "usd", defaultActive: false }),
createPriceLine({ unit: Unit.usd, defaultActive: false }),
]
: []),
// RealizedPattern (address cohorts) doesn't have realizedCapRelToOwnMarketCap
@@ -172,18 +198,50 @@ function createRealizedCapWithExtras(ctx, list, args, useGroupName, title) {
*/
function createRealizedPnlSection(ctx, args, title) {
const { colors, s } = ctx;
const { tree } = args;
const { mergeMetricPatterns } = ctx.brk;
const { realized } = args.tree;
return [
{
name: "pnl",
title: `Realized Profit And Loss ${title}`,
bottom: [
s({ metric: tree.realized.realizedProfit.base, name: "Profit", color: colors.green }),
s({ metric: tree.realized.realizedLoss.base, name: "Loss", color: colors.red, defaultActive: false }),
s({
metric: mergeMetricPatterns(
realized.realizedProfit.base,
realized.realizedProfit.sum,
),
name: "Profit",
color: colors.green,
unit: Unit.usd,
}),
s({
metric: mergeMetricPatterns(
realized.realizedLoss.base,
realized.realizedLoss.sum,
),
name: "Loss",
color: colors.red,
unit: Unit.usd,
defaultActive: false,
}),
// RealizedPattern (address cohorts) doesn't have realizedProfitToLossRatio
s({ metric: tree.realized.totalRealizedPnl.base, name: "Total", color: colors.default, defaultActive: false }),
s({ metric: tree.realized.negRealizedLoss.base, name: "Negative Loss", color: colors.red }),
s({
metric: realized.totalRealizedPnl,
name: "Total",
color: colors.default,
defaultActive: false,
unit: Unit.usd,
}),
s({
metric: mergeMetricPatterns(
realized.negRealizedLoss.base,
realized.negRealizedLoss.sum,
),
name: "Negative Loss",
color: colors.red,
unit: Unit.usd,
}),
],
},
];
@@ -212,7 +270,9 @@ function createUnrealizedSection(ctx, list, useGroupName, title) {
type: "Baseline",
metric: tree.unrealized.netUnrealizedPnl,
title: useGroupName ? name : "NUPL",
colors: [colors.red, colors.green],
color: useGroupName ? color : undefined,
colors: useGroupName ? undefined : [colors.red, colors.green],
unit: Unit.ratio,
options: { baseValue: { price: 0 } },
}),
]),
@@ -221,14 +281,24 @@ function createUnrealizedSection(ctx, list, useGroupName, title) {
name: "profit",
title: `Unrealized Profit ${title}`,
bottom: list.flatMap(({ color, name, tree }) => [
s({ metric: tree.unrealized.unrealizedProfit, name: useGroupName ? name : "Profit", color }),
s({
metric: tree.unrealized.unrealizedProfit,
name: useGroupName ? name : "Profit",
color,
unit: Unit.usd,
}),
]),
},
{
name: "loss",
title: `Unrealized Loss ${title}`,
bottom: list.flatMap(({ color, name, tree }) => [
s({ metric: tree.unrealized.unrealizedLoss, name: useGroupName ? name : "Loss", color }),
s({
metric: tree.unrealized.unrealizedLoss,
name: useGroupName ? name : "Loss",
color,
unit: Unit.usd,
}),
]),
},
],
@@ -255,14 +325,24 @@ function createCostBasisSection(ctx, list, useGroupName, title) {
name: "min",
title: `Min Cost Basis ${title}`,
top: list.map(({ color, name, tree }) =>
s({ metric: tree.costBasis.minCostBasis, name: useGroupName ? name : "Min", color }),
s({
metric: tree.costBasis.minCostBasis,
name: useGroupName ? name : "Min",
color,
unit: Unit.usd,
}),
),
},
{
name: "max",
title: `Max Cost Basis ${title}`,
top: list.map(({ color, name, tree }) =>
s({ metric: tree.costBasis.maxCostBasis, name: useGroupName ? name : "Max", color }),
s({
metric: tree.costBasis.maxCostBasis,
name: useGroupName ? name : "Max",
color,
unit: Unit.usd,
}),
),
},
],
@@ -279,7 +359,8 @@ function createCostBasisSection(ctx, list, useGroupName, title) {
* @returns {PartialOptionsTree}
*/
function createActivitySection(ctx, list, useGroupName, title) {
const { s } = ctx;
const { s, brk } = ctx;
const { mergeMetricPatterns } = brk;
return [
{
@@ -290,9 +371,13 @@ function createActivitySection(ctx, list, useGroupName, title) {
title: `Coinblocks Destroyed ${title}`,
bottom: list.flatMap(({ color, name, tree }) => [
s({
metric: tree.activity.coinblocksDestroyed.base,
metric: mergeMetricPatterns(
tree.activity.coinblocksDestroyed.base,
tree.activity.coinblocksDestroyed.sum,
),
name: useGroupName ? name : "Coinblocks",
color,
unit: Unit.coinblocks,
}),
]),
},
@@ -301,9 +386,13 @@ function createActivitySection(ctx, list, useGroupName, title) {
title: `Coindays Destroyed ${title}`,
bottom: list.flatMap(({ color, name, tree }) => [
s({
metric: tree.activity.coindaysDestroyed.base,
metric: mergeMetricPatterns(
tree.activity.coindaysDestroyed.base,
tree.activity.coindaysDestroyed.sum,
),
name: useGroupName ? name : "Coindays",
color,
unit: Unit.coindays,
}),
]),
},

View File

@@ -1,42 +1,43 @@
/** Shared cohort chart section builders */
import { Unit } from "../../utils/units.js";
/**
* Create supply section for a single cohort
* @param {PartialContext} ctx
* @param {CohortObject} cohort
* @param {string} title
* @returns {AnyFetchedSeriesBlueprint[]}
*/
export function createSingleSupplySeries(ctx, cohort, title) {
export function createSingleSupplySeries(ctx, cohort) {
const { colors, s, createPriceLine } = ctx;
const { tree, color, name } = cohort;
const { tree } = cohort;
return [
s({ metric: tree.supply.supply.sats, name: "Supply", color: colors.default }),
s({ metric: tree.supply.supply.bitcoin, name: "Supply", color: colors.default }),
s({ metric: tree.supply.supply.dollars, name: "Supply", color: colors.default }),
s({ metric: tree.supply.supply.sats, name: "Supply", color: colors.default, unit: Unit.sats }),
s({ metric: tree.supply.supply.bitcoin, name: "Supply", color: colors.default, unit: Unit.btc }),
s({ metric: tree.supply.supply.dollars, name: "Supply", color: colors.default, unit: Unit.usd }),
...("supplyRelToCirculatingSupply" in tree.relative
? [s({ metric: tree.relative.supplyRelToCirculatingSupply, name: "Supply", color: colors.default })]
? [s({ metric: tree.relative.supplyRelToCirculatingSupply, name: "Supply", color: colors.default, unit: Unit.pctSupply })]
: []),
s({ metric: tree.unrealized.supplyInProfit.sats, name: "In Profit", color: colors.green }),
s({ metric: tree.unrealized.supplyInProfit.bitcoin, name: "In Profit", color: colors.green }),
s({ metric: tree.unrealized.supplyInProfit.dollars, name: "In Profit", color: colors.green }),
s({ metric: tree.unrealized.supplyInLoss.sats, name: "In Loss", color: colors.red }),
s({ metric: tree.unrealized.supplyInLoss.bitcoin, name: "In Loss", color: colors.red }),
s({ metric: tree.unrealized.supplyInLoss.dollars, name: "In Loss", color: colors.red }),
s({ metric: tree.supply.supplyHalf.sats, name: "half", color: colors.gray, options: { lineStyle: 4 } }),
s({ metric: tree.supply.supplyHalf.bitcoin, name: "half", color: colors.gray, options: { lineStyle: 4 } }),
s({ metric: tree.supply.supplyHalf.dollars, name: "half", color: colors.gray, options: { lineStyle: 4 } }),
s({ metric: tree.unrealized.supplyInProfit.sats, name: "In Profit", color: colors.green, unit: Unit.sats }),
s({ metric: tree.unrealized.supplyInProfit.bitcoin, name: "In Profit", color: colors.green, unit: Unit.btc }),
s({ metric: tree.unrealized.supplyInProfit.dollars, name: "In Profit", color: colors.green, unit: Unit.usd }),
s({ metric: tree.unrealized.supplyInLoss.sats, name: "In Loss", color: colors.red, unit: Unit.sats }),
s({ metric: tree.unrealized.supplyInLoss.bitcoin, name: "In Loss", color: colors.red, unit: Unit.btc }),
s({ metric: tree.unrealized.supplyInLoss.dollars, name: "In Loss", color: colors.red, unit: Unit.usd }),
s({ metric: tree.supply.supplyHalf.sats, name: "half", color: colors.gray, unit: Unit.sats, options: { lineStyle: 4 } }),
s({ metric: tree.supply.supplyHalf.bitcoin, name: "half", color: colors.gray, unit: Unit.btc, options: { lineStyle: 4 } }),
s({ metric: tree.supply.supplyHalf.dollars, name: "half", color: colors.gray, unit: Unit.usd, options: { lineStyle: 4 } }),
...("supplyInProfitRelToCirculatingSupply" in tree.relative
? [
s({ metric: tree.relative.supplyInProfitRelToCirculatingSupply, name: "In Profit", color: colors.green }),
s({ metric: tree.relative.supplyInLossRelToCirculatingSupply, name: "In Loss", color: colors.red }),
s({ metric: tree.relative.supplyInProfitRelToCirculatingSupply, name: "In Profit", color: colors.green, unit: Unit.pctSupply }),
s({ metric: tree.relative.supplyInLossRelToCirculatingSupply, name: "In Loss", color: colors.red, unit: Unit.pctSupply }),
]
: []),
s({ metric: tree.relative.supplyInProfitRelToOwnSupply, name: "In Profit", color: colors.green }),
s({ metric: tree.relative.supplyInLossRelToOwnSupply, name: "In Loss", color: colors.red }),
createPriceLine({ unit: "%self", number: 100, lineStyle: 0, color: colors.default }),
createPriceLine({ unit: "%self", number: 50 }),
s({ metric: tree.relative.supplyInProfitRelToOwnSupply, name: "In Profit", color: colors.green, unit: Unit.pctOwn }),
s({ metric: tree.relative.supplyInLossRelToOwnSupply, name: "In Loss", color: colors.red, unit: Unit.pctOwn }),
createPriceLine({ unit: Unit.pctOwn, number: 100, lineStyle: 0, color: colors.default }),
createPriceLine({ unit: Unit.pctOwn, number: 50 }),
];
}
@@ -51,12 +52,12 @@ export function createGroupedSupplyTotalSeries(ctx, list) {
const constant100 = brk.tree.computed.constants.constant100;
return list.flatMap(({ color, name, tree }) => [
s({ metric: tree.supply.supply.sats, name, color }),
s({ metric: tree.supply.supply.bitcoin, name, color }),
s({ metric: tree.supply.supply.dollars, name, color }),
s({ metric: tree.supply.supply.sats, name, color, unit: Unit.sats }),
s({ metric: tree.supply.supply.bitcoin, name, color, unit: Unit.btc }),
s({ metric: tree.supply.supply.dollars, name, color, unit: Unit.usd }),
"supplyRelToCirculatingSupply" in tree.relative
? s({ metric: tree.relative.supplyRelToCirculatingSupply, name, color })
: s({ unit: "%all", metric: constant100, name, color }),
? s({ metric: tree.relative.supplyRelToCirculatingSupply, name, color, unit: Unit.pctSupply })
: s({ metric: constant100, name, color, unit: Unit.pctSupply }),
]);
}
@@ -70,11 +71,11 @@ export function createGroupedSupplyInProfitSeries(ctx, list) {
const { s } = ctx;
return list.flatMap(({ color, name, tree }) => [
s({ metric: tree.unrealized.supplyInProfit.sats, name, color }),
s({ metric: tree.unrealized.supplyInProfit.bitcoin, name, color }),
s({ metric: tree.unrealized.supplyInProfit.dollars, name, color }),
s({ metric: tree.unrealized.supplyInProfit.sats, name, color, unit: Unit.sats }),
s({ metric: tree.unrealized.supplyInProfit.bitcoin, name, color, unit: Unit.btc }),
s({ metric: tree.unrealized.supplyInProfit.dollars, name, color, unit: Unit.usd }),
...("supplyInProfitRelToCirculatingSupply" in tree.relative
? [s({ metric: tree.relative.supplyInProfitRelToCirculatingSupply, name, color })]
? [s({ metric: tree.relative.supplyInProfitRelToCirculatingSupply, name, color, unit: Unit.pctSupply })]
: []),
]);
}
@@ -89,11 +90,11 @@ export function createGroupedSupplyInLossSeries(ctx, list) {
const { s } = ctx;
return list.flatMap(({ color, name, tree }) => [
s({ metric: tree.unrealized.supplyInLoss.sats, name, color }),
s({ metric: tree.unrealized.supplyInLoss.bitcoin, name, color }),
s({ metric: tree.unrealized.supplyInLoss.dollars, name, color }),
s({ metric: tree.unrealized.supplyInLoss.sats, name, color, unit: Unit.sats }),
s({ metric: tree.unrealized.supplyInLoss.bitcoin, name, color, unit: Unit.btc }),
s({ metric: tree.unrealized.supplyInLoss.dollars, name, color, unit: Unit.usd }),
...("supplyInLossRelToCirculatingSupply" in tree.relative
? [s({ metric: tree.relative.supplyInLossRelToCirculatingSupply, name, color })]
? [s({ metric: tree.relative.supplyInLossRelToCirculatingSupply, name, color, unit: Unit.pctSupply })]
: []),
]);
}
@@ -109,7 +110,7 @@ export function createUtxoCountSeries(ctx, list, useGroupName) {
const { s } = ctx;
return list.flatMap(({ color, name, tree }) => [
s({ metric: tree.supply.utxoCount, name: useGroupName ? name : "Count", color }),
s({ metric: tree.supply.utxoCount, name: useGroupName ? name : "Count", color, unit: Unit.count }),
]);
}
@@ -128,6 +129,7 @@ export function createAddressCountSeries(ctx, list, useGroupName) {
metric: tree.addrCount,
name: useGroupName ? name : "Count",
color: useGroupName ? color : colors.orange,
unit: Unit.count,
}),
]);
}
@@ -142,7 +144,7 @@ export function createRealizedPriceSeries(ctx, list) {
const { s } = ctx;
return list.map(({ color, name, tree }) =>
s({ metric: tree.realized.realizedPrice, name, color }),
s({ metric: tree.realized.realizedPrice, name, color, unit: Unit.usd }),
);
}
@@ -157,9 +159,9 @@ export function createRealizedPriceRatioSeries(ctx, list) {
return [
...list.map(({ color, name, tree }) =>
s({ metric: tree.realized.realizedPriceExtra.ratio, name, color }),
s({ metric: tree.realized.realizedPriceExtra.ratio, name, color, unit: Unit.ratio }),
),
createPriceLine({ unit: "ratio", number: 1 }),
createPriceLine({ unit: Unit.ratio, number: 1 }),
];
}
@@ -174,7 +176,7 @@ export function createRealizedCapSeries(ctx, list, useGroupName) {
const { s } = ctx;
return list.flatMap(({ color, name, tree }) => [
s({ metric: tree.realized.realizedCap, name: useGroupName ? name : "Capitalization", color }),
s({ metric: tree.realized.realizedCap, name: useGroupName ? name : "Capitalization", color, unit: Unit.usd }),
]);
}
@@ -189,8 +191,8 @@ export function createCostBasisMinMaxSeries(ctx, list, useGroupName) {
const { s } = ctx;
return list.flatMap(({ color, name, tree }) => [
s({ metric: tree.costBasis.minCostBasis, name: useGroupName ? `${name} min` : "Min", color }),
s({ metric: tree.costBasis.maxCostBasis, name: useGroupName ? `${name} max` : "Max", color }),
s({ metric: tree.costBasis.minCostBasis, name: useGroupName ? `${name} min` : "Min", color, unit: Unit.usd }),
s({ metric: tree.costBasis.maxCostBasis, name: useGroupName ? `${name} max` : "Max", color, unit: Unit.usd }),
]);
}
@@ -202,16 +204,16 @@ export function createCostBasisMinMaxSeries(ctx, list, useGroupName) {
* @returns {AnyFetchedSeriesBlueprint[]}
*/
export function createCostBasisPercentilesSeries(ctx, list, useGroupName) {
const { s, colors } = ctx;
const { s } = ctx;
return list.flatMap(({ color, name, tree }) => {
const percentiles = tree.costBasis.percentiles;
return [
s({ metric: percentiles.costBasisPct10, name: useGroupName ? `${name} p10` : "p10", color, defaultActive: false }),
s({ metric: percentiles.costBasisPct25, name: useGroupName ? `${name} p25` : "p25", color, defaultActive: false }),
s({ metric: percentiles.costBasisPct50, name: useGroupName ? `${name} p50` : "p50", color }),
s({ metric: percentiles.costBasisPct75, name: useGroupName ? `${name} p75` : "p75", color, defaultActive: false }),
s({ metric: percentiles.costBasisPct90, name: useGroupName ? `${name} p90` : "p90", color, defaultActive: false }),
s({ metric: percentiles.costBasisPct10, name: useGroupName ? `${name} p10` : "p10", color, unit: Unit.usd, defaultActive: false }),
s({ metric: percentiles.costBasisPct25, name: useGroupName ? `${name} p25` : "p25", color, unit: Unit.usd, defaultActive: false }),
s({ metric: percentiles.costBasisPct50, name: useGroupName ? `${name} p50` : "p50", color, unit: Unit.usd }),
s({ metric: percentiles.costBasisPct75, name: useGroupName ? `${name} p75` : "p75", color, unit: Unit.usd, defaultActive: false }),
s({ metric: percentiles.costBasisPct90, name: useGroupName ? `${name} p90` : "p90", color, unit: Unit.usd, defaultActive: false }),
];
});
}

View File

@@ -17,6 +17,7 @@ import {
createRealizedPriceRatioSeries,
createCostBasisPercentilesSeries,
} from "./shared.js";
import { Unit } from "../../utils/units.js";
/**
* Create a cohort folder for age-based UTXO cohorts (term, maxAge, minAge, ageRange, epoch)
@@ -28,15 +29,14 @@ import {
export function createAgeCohortFolder(ctx, args) {
const list = "list" in args ? args.list : [args];
const useGroupName = "list" in args;
const isSingle = !("list" in args);
const title = args.title ? `${useGroupName ? "by" : "of"} ${args.title}` : "";
return {
name: args.name || "all",
tree: [
...createSupplySection(ctx, list, args, useGroupName, isSingle, title),
...createSupplySection(ctx, list, args, useGroupName, title),
createUtxoCountSection(ctx, list, useGroupName, title),
createRealizedSection(ctx, list, args, useGroupName, isSingle, title),
createRealizedSection(ctx, list, args, useGroupName, title),
...createUnrealizedSection(ctx, list, useGroupName, title),
...createCostBasisSectionWithPercentiles(ctx, list, useGroupName, title),
...createActivitySection(ctx, list, useGroupName, title),
@@ -54,15 +54,14 @@ export function createAgeCohortFolder(ctx, args) {
export function createAmountCohortFolder(ctx, args) {
const list = "list" in args ? args.list : [args];
const useGroupName = "list" in args;
const isSingle = !("list" in args);
const title = args.title ? `${useGroupName ? "by" : "of"} ${args.title}` : "";
return {
name: args.name || "all",
tree: [
...createSupplySection(ctx, list, args, useGroupName, isSingle, title),
...createSupplySection(ctx, list, args, useGroupName, title),
createUtxoCountSection(ctx, list, useGroupName, title),
createRealizedSection(ctx, list, args, useGroupName, isSingle, title),
createRealizedSection(ctx, list, args, useGroupName, title),
...createUnrealizedSection(ctx, list, useGroupName, title),
...createCostBasisSectionBasic(ctx, list, useGroupName, title),
...createActivitySection(ctx, list, useGroupName, title),
@@ -81,7 +80,6 @@ export function createAmountCohortFolder(ctx, args) {
export function createUtxoCohortFolder(ctx, args) {
const list = "list" in args ? args.list : [args];
const useGroupName = "list" in args;
const isSingle = !("list" in args);
const title = args.title ? `${useGroupName ? "by" : "of"} ${args.title}` : "";
// Runtime check for percentiles
@@ -90,9 +88,9 @@ export function createUtxoCohortFolder(ctx, args) {
return {
name: args.name || "all",
tree: [
...createSupplySection(ctx, list, args, useGroupName, isSingle, title),
...createSupplySection(ctx, list, args, useGroupName, title),
createUtxoCountSection(ctx, list, useGroupName, title),
createRealizedSection(ctx, list, args, useGroupName, isSingle, title),
createRealizedSection(ctx, list, args, useGroupName, title),
...createUnrealizedSection(ctx, list, useGroupName, title),
...(hasPercentiles
? createCostBasisSectionWithPercentiles(
@@ -113,11 +111,11 @@ export function createUtxoCohortFolder(ctx, args) {
* @param {readonly UtxoCohortObject[]} list
* @param {UtxoCohortObject | UtxoCohortGroupObject} args
* @param {boolean} useGroupName
* @param {boolean} isSingle
* @param {string} title
* @returns {PartialOptionsTree}
*/
function createSupplySection(ctx, list, args, useGroupName, isSingle, title) {
function createSupplySection(ctx, list, args, useGroupName, title) {
const isSingle = !useGroupName;
return [
isSingle
? {
@@ -126,7 +124,6 @@ function createSupplySection(ctx, list, args, useGroupName, isSingle, title) {
bottom: createSingleSupplySeries(
ctx,
/** @type {UtxoCohortObject} */ (args),
title,
),
}
: {
@@ -174,11 +171,10 @@ function createUtxoCountSection(ctx, list, useGroupName, title) {
* @param {readonly UtxoCohortObject[]} list
* @param {UtxoCohortObject | UtxoCohortGroupObject} args
* @param {boolean} useGroupName
* @param {boolean} isSingle
* @param {string} title
* @returns {PartialOptionsGroup}
*/
function createRealizedSection(ctx, list, args, useGroupName, isSingle, title) {
function createRealizedSection(ctx, list, args, useGroupName, title) {
return {
name: "Realized",
tree: [
@@ -203,13 +199,7 @@ function createRealizedSection(ctx, list, args, useGroupName, isSingle, title) {
{
name: "capitalization",
title: `Realized Capitalization ${title}`,
bottom: createRealizedCapWithExtras(
ctx,
list,
args,
useGroupName,
title,
),
bottom: createRealizedCapWithExtras(ctx, list, args, useGroupName),
},
...(!useGroupName
? createRealizedPnlSection(
@@ -238,7 +228,7 @@ function createRealizedPriceOptions(ctx, args, title) {
name: "price",
title: `Realized Price ${title}`,
top: [
s({ metric: tree.realized.realizedPrice, name: "realized", color }),
s({ metric: tree.realized.realizedPrice, name: "realized", color, unit: Unit.usd }),
],
},
];
@@ -250,10 +240,9 @@ function createRealizedPriceOptions(ctx, args, title) {
* @param {readonly UtxoCohortObject[]} list
* @param {UtxoCohortObject | UtxoCohortGroupObject} args
* @param {boolean} useGroupName
* @param {string} title
* @returns {AnyFetchedSeriesBlueprint[]}
*/
function createRealizedCapWithExtras(ctx, list, args, useGroupName, title) {
function createRealizedCapWithExtras(ctx, list, args, useGroupName) {
const { colors, s, createPriceLine } = ctx;
const isSingle = !("list" in args);
@@ -262,6 +251,7 @@ function createRealizedCapWithExtras(ctx, list, args, useGroupName, title) {
metric: tree.realized.realizedCap,
name: useGroupName ? name : "Capitalization",
color,
unit: Unit.usd,
}),
...(isSingle
? [
@@ -269,9 +259,10 @@ function createRealizedCapWithExtras(ctx, list, args, useGroupName, title) {
type: "Baseline",
metric: tree.realized.realizedCap30dDelta,
title: "30d change",
unit: Unit.usd,
defaultActive: false,
}),
createPriceLine({ unit: "usd", defaultActive: false }),
createPriceLine({ unit: Unit.usd, defaultActive: false }),
]
: []),
...(isSingle && "realizedCapRelToOwnMarketCap" in tree.realized
@@ -280,10 +271,11 @@ function createRealizedCapWithExtras(ctx, list, args, useGroupName, title) {
type: "Baseline",
metric: tree.realized.realizedCapRelToOwnMarketCap,
title: "ratio",
unit: Unit.pctOwnMcap,
options: { baseValue: { price: 100 } },
colors: [colors.red, colors.green],
}),
createPriceLine({ unit: "%cmcap", defaultActive: true, number: 100 }),
createPriceLine({ unit: Unit.pctOwnMcap, defaultActive: true, number: 100 }),
]
: []),
]);
@@ -297,7 +289,8 @@ function createRealizedCapWithExtras(ctx, list, args, useGroupName, title) {
* @returns {PartialOptionsTree}
*/
function createRealizedPnlSection(ctx, args, title) {
const { colors, s } = ctx;
const { colors, s, brk } = ctx;
const { mergeMetricPatterns } = brk;
const { tree } = args;
return [
@@ -306,35 +299,49 @@ function createRealizedPnlSection(ctx, args, title) {
title: `Realized Profit And Loss ${title}`,
bottom: [
s({
metric: tree.realized.realizedProfit.base,
metric: mergeMetricPatterns(
tree.realized.realizedProfit.base,
tree.realized.realizedProfit.sum,
),
name: "Profit",
color: colors.green,
unit: Unit.usd,
}),
s({
metric: tree.realized.realizedLoss.base,
metric: mergeMetricPatterns(
tree.realized.realizedLoss.base,
tree.realized.realizedLoss.sum,
),
name: "Loss",
color: colors.red,
defaultActive: false,
unit: Unit.usd,
}),
...("realizedProfitToLossRatio" in tree.realized
? [
s({
metric: tree.realized.realizedProfitToLossRatio,
name: "profit / loss",
name: "Profit / Loss",
color: colors.yellow,
unit: Unit.ratio,
}),
]
: []),
s({
metric: tree.realized.totalRealizedPnl.base,
metric: tree.realized.totalRealizedPnl,
name: "Total",
color: colors.default,
defaultActive: false,
unit: Unit.usd,
}),
s({
metric: tree.realized.negRealizedLoss.base,
metric: mergeMetricPatterns(
tree.realized.negRealizedLoss.base,
tree.realized.negRealizedLoss.sum,
),
name: "Negative Loss",
color: colors.red,
unit: Unit.usd,
}),
],
},
@@ -350,7 +357,7 @@ function createRealizedPnlSection(ctx, args, title) {
* @returns {PartialOptionsTree}
*/
function createUnrealizedSection(ctx, list, useGroupName, title) {
const { colors, s, createPriceLine } = ctx;
const { colors, s } = ctx;
return [
{
@@ -359,12 +366,13 @@ function createUnrealizedSection(ctx, list, useGroupName, title) {
{
name: "nupl",
title: `Net Unrealized Profit/Loss ${title}`,
bottom: list.flatMap(({ color, name, tree }) => [
bottom: list.flatMap(({ name, tree }) => [
/** @type {AnyFetchedSeriesBlueprint} */ ({
type: "Baseline",
metric: tree.unrealized.netUnrealizedPnl,
title: useGroupName ? name : "NUPL",
colors: [colors.red, colors.green],
unit: Unit.ratio,
options: { baseValue: { price: 0 } },
}),
]),
@@ -377,6 +385,7 @@ function createUnrealizedSection(ctx, list, useGroupName, title) {
metric: tree.unrealized.unrealizedProfit,
name: useGroupName ? name : "Profit",
color,
unit: Unit.usd,
}),
]),
},
@@ -388,6 +397,7 @@ function createUnrealizedSection(ctx, list, useGroupName, title) {
metric: tree.unrealized.unrealizedLoss,
name: useGroupName ? name : "Loss",
color,
unit: Unit.usd,
}),
]),
},
@@ -419,6 +429,7 @@ function createCostBasisSectionWithPercentiles(ctx, list, useGroupName, title) {
metric: tree.costBasis.minCostBasis,
name: useGroupName ? name : "Min",
color,
unit: Unit.usd,
}),
),
},
@@ -430,6 +441,7 @@ function createCostBasisSectionWithPercentiles(ctx, list, useGroupName, title) {
metric: tree.costBasis.maxCostBasis,
name: useGroupName ? name : "Max",
color,
unit: Unit.usd,
}),
),
},
@@ -466,6 +478,7 @@ function createCostBasisSectionBasic(ctx, list, useGroupName, title) {
metric: tree.costBasis.minCostBasis,
name: useGroupName ? name : "Min",
color,
unit: Unit.usd,
}),
),
},
@@ -477,6 +490,7 @@ function createCostBasisSectionBasic(ctx, list, useGroupName, title) {
metric: tree.costBasis.maxCostBasis,
name: useGroupName ? name : "Max",
color,
unit: Unit.usd,
}),
),
},
@@ -494,7 +508,8 @@ function createCostBasisSectionBasic(ctx, list, useGroupName, title) {
* @returns {PartialOptionsTree}
*/
function createActivitySection(ctx, list, useGroupName, title) {
const { s } = ctx;
const { s, brk } = ctx;
const { mergeMetricPatterns } = brk;
return [
{
@@ -505,9 +520,13 @@ function createActivitySection(ctx, list, useGroupName, title) {
title: `Coinblocks Destroyed ${title}`,
bottom: list.flatMap(({ color, name, tree }) => [
s({
metric: tree.activity.coinblocksDestroyed.base,
metric: mergeMetricPatterns(
tree.activity.coinblocksDestroyed.base,
tree.activity.coinblocksDestroyed.sum,
),
name: useGroupName ? name : "Coinblocks",
color,
unit: Unit.coinblocks,
}),
]),
},
@@ -516,9 +535,13 @@ function createActivitySection(ctx, list, useGroupName, title) {
title: `Coindays Destroyed ${title}`,
bottom: list.flatMap(({ color, name, tree }) => [
s({
metric: tree.activity.coindaysDestroyed.base,
metric: mergeMetricPatterns(
tree.activity.coindaysDestroyed.base,
tree.activity.coindaysDestroyed.sum,
),
name: useGroupName ? name : "Coindays",
color,
unit: Unit.coindays,
}),
]),
},

View File

@@ -0,0 +1,502 @@
/** Cointime section builder - typed tree-based patterns */
import { Unit } from "../utils/units.js";
/**
* Create price with ratio options for cointime prices
* @param {PartialContext} ctx
* @param {Object} args
* @param {string} args.title
* @param {string} args.legend
* @param {AnyMetricPattern} args.price
* @param {ActivePriceRatioPattern} args.ratio
* @param {Color} [args.color]
* @returns {PartialOptionsTree}
*/
function createCointimePriceWithRatioOptions(
ctx,
{ title, legend, price, ratio, color },
) {
const { s, colors, createPriceLine } = ctx;
// Percentile USD mappings
const percentileUsdMap = [
{ name: "pct99", prop: ratio.ratioPct99Usd, color: colors.rose },
{ name: "pct98", prop: ratio.ratioPct98Usd, color: colors.pink },
{ name: "pct95", prop: ratio.ratioPct95Usd, color: colors.fuchsia },
{ name: "pct5", prop: ratio.ratioPct5Usd, color: colors.cyan },
{ name: "pct2", prop: ratio.ratioPct2Usd, color: colors.sky },
{ name: "pct1", prop: ratio.ratioPct1Usd, color: colors.blue },
];
// Percentile ratio mappings
const percentileMap = [
{ name: "pct99", prop: ratio.ratioPct99, color: colors.rose },
{ name: "pct98", prop: ratio.ratioPct98, color: colors.pink },
{ name: "pct95", prop: ratio.ratioPct95, color: colors.fuchsia },
{ name: "pct5", prop: ratio.ratioPct5, color: colors.cyan },
{ name: "pct2", prop: ratio.ratioPct2, color: colors.sky },
{ name: "pct1", prop: ratio.ratioPct1, color: colors.blue },
];
// SD patterns by window
const sdPatterns = [
{ nameAddon: "all", titleAddon: "", sd: ratio.ratioSd },
{ nameAddon: "4y", titleAddon: "4y", sd: ratio.ratio4ySd },
{ nameAddon: "2y", titleAddon: "2y", sd: ratio.ratio2ySd },
{ nameAddon: "1y", titleAddon: "1y", sd: ratio.ratio1ySd },
];
/** @param {Ratio1ySdPattern} sd */
const getSdBands = (sd) => [
{ name: "0σ", prop: sd._0sdUsd, color: colors.lime },
{ name: "+0.5σ", prop: sd.p05sdUsd, color: colors.yellow },
{ name: "+1σ", prop: sd.p1sdUsd, color: colors.amber },
{ name: "+1.5σ", prop: sd.p15sdUsd, color: colors.orange },
{ name: "+2σ", prop: sd.p2sdUsd, color: colors.red },
{ name: "+2.5σ", prop: sd.p25sdUsd, color: colors.rose },
{ name: "+3σ", prop: sd.p3sd, color: colors.pink },
{ name: "0.5σ", prop: sd.m05sdUsd, color: colors.teal },
{ name: "1σ", prop: sd.m1sdUsd, color: colors.cyan },
{ name: "1.5σ", prop: sd.m15sdUsd, color: colors.sky },
{ name: "2σ", prop: sd.m2sdUsd, color: colors.blue },
{ name: "2.5σ", prop: sd.m25sdUsd, color: colors.indigo },
{ name: "3σ", prop: sd.m3sd, color: colors.violet },
];
return [
{
name: "price",
title,
top: [s({ metric: price, name: legend, color, unit: Unit.usd })],
},
{
name: "Ratio",
title: `${title} Ratio`,
top: [
s({ metric: price, name: legend, color, unit: Unit.usd }),
...percentileUsdMap.map(({ name: pctName, prop, color: pctColor }) =>
s({
metric: prop,
name: pctName,
color: pctColor,
defaultActive: false,
unit: Unit.usd,
options: { lineStyle: 1 },
}),
),
],
bottom: [
s({ metric: ratio.ratio, name: "Ratio", color, unit: Unit.ratio }),
s({
metric: ratio.ratio1wSma,
name: "1w SMA",
color: colors.lime,
unit: Unit.ratio,
}),
s({
metric: ratio.ratio1mSma,
name: "1m SMA",
color: colors.teal,
unit: Unit.ratio,
}),
s({
metric: ratio.ratio1ySd.sma,
name: "1y SMA",
color: colors.sky,
unit: Unit.ratio,
}),
s({
metric: ratio.ratio2ySd.sma,
name: "2y SMA",
color: colors.indigo,
unit: Unit.ratio,
}),
s({
metric: ratio.ratio4ySd.sma,
name: "4y SMA",
color: colors.purple,
unit: Unit.ratio,
}),
s({
metric: ratio.ratioSd.sma,
name: "All SMA",
color: colors.rose,
unit: Unit.ratio,
}),
...percentileMap.map(({ name: pctName, prop, color: pctColor }) =>
s({
metric: prop,
name: pctName,
color: pctColor,
defaultActive: false,
unit: Unit.ratio,
options: { lineStyle: 1 },
}),
),
createPriceLine({ unit: Unit.ratio, number: 1 }),
],
},
{
name: "ZScores",
tree: sdPatterns.map(({ nameAddon, titleAddon, sd }) => ({
name: nameAddon,
title: `${title} ${titleAddon} Z-Score`,
top: getSdBands(sd).map(({ name: bandName, prop, color: bandColor }) =>
s({ metric: prop, name: bandName, color: bandColor, unit: Unit.usd }),
),
bottom: [
s({ metric: sd.zscore, name: "Z-Score", color, unit: Unit.sd }),
createPriceLine({ unit: Unit.sd, number: 3 }),
createPriceLine({ unit: Unit.sd, number: 2 }),
createPriceLine({ unit: Unit.sd, number: 1 }),
createPriceLine({ unit: Unit.sd, number: 0 }),
createPriceLine({ unit: Unit.sd, number: -1 }),
createPriceLine({ unit: Unit.sd, number: -2 }),
createPriceLine({ unit: Unit.sd, number: -3 }),
],
})),
},
];
}
/**
* Create Cointime section
* @param {PartialContext} ctx
* @returns {PartialOptionsGroup}
*/
export function createCointimeSection(ctx) {
const { colors, brk, s } = ctx;
const { mergeMetricPatterns } = brk;
const { cointime, distribution, supply } = brk.tree.computed;
const { pricing, cap, activity, supply: cointimeSupply, adjusted } = cointime;
const { all } = distribution.utxoCohorts;
// Cointime prices data
const cointimePrices = [
{
price: pricing.trueMarketMean,
ratio: pricing.trueMarketMeanRatio,
name: "True market mean",
title: "true market mean",
color: colors.blue,
},
{
price: pricing.vaultedPrice,
ratio: pricing.vaultedPriceRatio,
name: "Vaulted",
title: "vaulted price",
color: colors.lime,
},
{
price: pricing.activePrice,
ratio: pricing.activePriceRatio,
name: "Active",
title: "active price",
color: colors.rose,
},
{
price: pricing.cointimePrice,
ratio: pricing.cointimePriceRatio,
name: "cointime",
title: "cointime price",
color: colors.yellow,
},
];
// Cointime capitalizations data
const cointimeCapitalizations = [
{
metric: cap.vaultedCap,
name: "vaulted",
title: "vaulted Capitalization",
color: colors.lime,
},
{
metric: cap.activeCap,
name: "active",
title: "active Capitalization",
color: colors.rose,
},
{
metric: cap.cointimeCap,
name: "cointime",
title: "cointime Capitalization",
color: colors.yellow,
},
{
metric: cap.investorCap,
name: "investor",
title: "investor Capitalization",
color: colors.fuchsia,
},
{
metric: cap.thermoCap,
name: "thermo",
title: "thermo Capitalization",
color: colors.emerald,
},
];
return {
name: "Cointime",
tree: [
// Prices
{
name: "Prices",
tree: [
{
name: "Compare",
title: "Compare Cointime Prices",
top: cointimePrices.map(({ price, name, color }) =>
s({ metric: price, name, color, unit: Unit.usd }),
),
},
...cointimePrices.map(({ price, ratio, name, color, title }) => ({
name,
tree: createCointimePriceWithRatioOptions(ctx, {
price,
ratio,
legend: name,
color,
title,
}),
})),
],
},
// Capitalization
{
name: "Capitalization",
tree: [
{
name: "Compare",
title: "Compare Cointime Capitalizations",
bottom: [
s({
metric: supply.marketCap.height,
name: "Market",
color: colors.default,
unit: Unit.usd,
}),
s({
metric: all.realized.realizedCap,
name: "Realized",
color: colors.orange,
unit: Unit.usd,
}),
...cointimeCapitalizations.map(({ metric, name, color }) =>
s({ metric, name, color, unit: Unit.usd }),
),
],
},
...cointimeCapitalizations.map(({ metric, name, color, title }) => ({
name,
title,
bottom: [
s({ metric, name, color, unit: Unit.usd }),
s({
metric: supply.marketCap.height,
name: "Market",
color: colors.default,
unit: Unit.usd,
}),
s({
metric: all.realized.realizedCap,
name: "Realized",
color: colors.orange,
unit: Unit.usd,
}),
],
})),
],
},
// Supply
{
name: "Supply",
title: "Cointime Supply",
bottom: [
// All supply (different pattern structure)
s({
metric: all.supply.supply.sats,
name: "All",
color: colors.orange,
unit: Unit.sats,
}),
s({
metric: all.supply.supply.bitcoin,
name: "All",
color: colors.orange,
unit: Unit.btc,
}),
s({
metric: all.supply.supply.dollars,
name: "All",
color: colors.orange,
unit: Unit.usd,
}),
// Cointime supplies (ActiveSupplyPattern)
.../** @type {const} */ ([
[cointimeSupply.vaultedSupply, "Vaulted", colors.lime],
[cointimeSupply.activeSupply, "Active", colors.rose],
]).flatMap(([supplyItem, name, color]) => [
s({ metric: supplyItem.sats, name, color, unit: Unit.sats }),
s({ metric: supplyItem.bitcoin, name, color, unit: Unit.btc }),
s({ metric: supplyItem.dollars, name, color, unit: Unit.usd }),
]),
],
},
// Liveliness & Vaultedness
{
name: "Liveliness & Vaultedness",
title: "Liveliness & Vaultedness",
bottom: [
s({
metric: activity.liveliness,
name: "Liveliness",
color: colors.rose,
unit: Unit.ratio,
}),
s({
metric: activity.vaultedness,
name: "Vaultedness",
color: colors.lime,
unit: Unit.ratio,
}),
s({
metric: activity.activityToVaultednessRatio,
name: "Liveliness / Vaultedness",
color: colors.purple,
unit: Unit.ratio,
}),
],
},
// Coinblocks
{
name: "Coinblocks",
title: "Coinblocks",
bottom: [
// Destroyed comes from the all cohort's activity
s({
metric: mergeMetricPatterns(
all.activity.coinblocksDestroyed.base,
all.activity.coinblocksDestroyed.sum,
),
name: "Destroyed",
color: colors.red,
unit: Unit.coinblocks,
}),
s({
metric: all.activity.coinblocksDestroyed.cumulative,
name: "Cumulative Destroyed",
color: colors.red,
defaultActive: false,
unit: Unit.coinblocks,
}),
// Created and stored from cointime
s({
metric: mergeMetricPatterns(
activity.coinblocksCreated.base,
activity.coinblocksCreated.sum,
),
name: "Created",
color: colors.orange,
unit: Unit.coinblocks,
}),
s({
metric: activity.coinblocksCreated.cumulative,
name: "Cumulative Created",
color: colors.orange,
defaultActive: false,
unit: Unit.coinblocks,
}),
s({
metric: mergeMetricPatterns(
activity.coinblocksStored.base,
activity.coinblocksStored.sum,
),
name: "Stored",
color: colors.green,
unit: Unit.coinblocks,
}),
s({
metric: activity.coinblocksStored.cumulative,
name: "Cumulative Stored",
color: colors.green,
defaultActive: false,
unit: Unit.coinblocks,
}),
],
},
// Adjusted metrics
{
name: "Adjusted",
tree: [
// Inflation
{
name: "Inflation",
title: "Cointime-Adjusted Inflation Rate",
bottom: [
s({
metric: mergeMetricPatterns(
supply.inflation.indexes.dateindex,
supply.inflation.indexes.rest,
),
name: "Base",
color: colors.orange,
unit: Unit.percentage,
}),
s({
metric: adjusted.cointimeAdjInflationRate,
name: "Adjusted",
color: colors.purple,
unit: Unit.percentage,
}),
],
},
// Velocity
{
name: "Velocity",
title: "Cointime-Adjusted Transactions Velocity",
bottom: [
s({
metric: mergeMetricPatterns(
supply.velocity.btc.dateindex,
supply.velocity.btc.rest,
),
name: "BTC",
color: colors.orange,
unit: Unit.ratio,
}),
s({
metric: adjusted.cointimeAdjTxBtcVelocity,
name: "Adj. BTC",
color: colors.red,
unit: Unit.ratio,
}),
s({
metric: mergeMetricPatterns(
supply.velocity.usd.dateindex,
supply.velocity.usd.rest,
),
name: "USD",
color: colors.emerald,
unit: Unit.ratio,
}),
s({
metric: adjusted.cointimeAdjTxUsdVelocity,
name: "Adj. USD",
color: colors.lime,
unit: Unit.ratio,
}),
],
},
],
},
],
};
}

View File

@@ -1,13 +1,12 @@
import { createPartialOptions } from "./partial/index.js";
import { createPartialOptions } from "./partial.js";
import {
createButtonElement,
createAnchorElement,
insertElementAtIndex,
} from "../utils/dom";
import { serdeUnit } from "../utils/serde";
import { pushHistory, resetParams } from "../utils/url";
import { readStored, writeToStorage } from "../utils/storage";
import { stringToId } from "../utils/format";
} from "../utils/dom.js";
import { pushHistory, resetParams } from "../utils/url.js";
import { readStored, writeToStorage } from "../utils/storage.js";
import { stringToId } from "../utils/format.js";
import { collect, markUsed, logUnused } from "./unused.js";
/**
@@ -47,21 +46,28 @@ export function initOptions({ colors, signals, brk, qrcode }) {
/**
* @param {AnyFetchedSeriesBlueprint[]} [arr]
*/
function arrayToRecord(arr = []) {
return [...(arr || [])].reduce((record, blueprint) => {
function arrayToMap(arr = []) {
/** @type {Map<Unit, AnyFetchedSeriesBlueprint[]>} */
const map = new Map();
for (const blueprint of arr || []) {
if (!blueprint.metric) {
throw new Error(
`Blueprint missing metric: ${JSON.stringify(blueprint)}`,
);
}
if (!blueprint.unit) {
throw new Error(
`Blueprint missing unit: ${blueprint.title}`,
);
}
markUsed(blueprint.metric);
// Use any index's path - unit is the same regardless of index (e.g., supply is "sats" for both height and dateindex)
const unit =
blueprint.unit ?? serdeUnit.deserialize(blueprint.metric.name);
record[unit] ??= [];
record[unit].push(blueprint);
return record;
}, /** @type {Record<Unit, AnyFetchedSeriesBlueprint[]>} */ ({}));
const unit = blueprint.unit;
if (!map.has(unit)) {
map.set(unit, []);
}
map.get(unit)?.push(blueprint);
}
return map;
}
/**
@@ -77,11 +83,10 @@ export function initOptions({ colors, signals, brk, qrcode }) {
/**
* @param {Object} args
* @param {Option} args.option
* @param {string} args.frame
* @param {Signal<string | null>} args.qrcode
* @param {string} [args.name]
*/
function createOptionElement({ option, frame, name, qrcode }) {
function createOptionElement({ option, name, qrcode }) {
const title = option.title;
if (option.kind === "url") {
const href = option.url();
@@ -299,8 +304,8 @@ export function initOptions({ colors, signals, brk, qrcode }) {
name,
title,
path,
top: arrayToRecord(anyPartial.top),
bottom: arrayToRecord(anyPartial.bottom),
top: arrayToMap(anyPartial.top),
bottom: arrayToMap(anyPartial.bottom),
}),
);
}
@@ -338,7 +343,6 @@ export function initOptions({ colors, signals, brk, qrcode }) {
const element = createOptionElement({
option,
frame: "nav",
qrcode,
});

View File

@@ -1,5 +1,6 @@
/** Moving averages section */
import { Unit } from "../../utils/units.js";
import { periodIdToName } from "./utils.js";
/**
@@ -93,29 +94,29 @@ export function createPriceWithRatioOptions(ctx, { title, legend, ratio, color }
{
name: "price",
title,
top: [s({ metric: priceMetric, name: legend, color, unit: "usd" })],
top: [s({ metric: priceMetric, name: legend, color, unit: Unit.usd })],
},
{
name: "Ratio",
title: `${title} Ratio`,
top: [
s({ metric: priceMetric, name: legend, color, unit: "usd" }),
s({ metric: priceMetric, name: legend, color, unit: Unit.usd }),
...percentileUsdMap.map(({ name: pctName, prop, color: pctColor }) =>
s({ metric: prop, name: pctName, color: pctColor, defaultActive: false, unit: "usd", options: { lineStyle: 1 } }),
s({ metric: prop, name: pctName, color: pctColor, defaultActive: false, unit: Unit.usd, options: { lineStyle: 1 } }),
),
],
bottom: [
s({ metric: ratio.ratio, name: "ratio", color, unit: "ratio" }),
s({ metric: ratio.ratio1wSma, name: "1w sma", color: colors.lime, unit: "ratio" }),
s({ metric: ratio.ratio1mSma, name: "1m sma", color: colors.teal, unit: "ratio" }),
s({ metric: ratio.ratio1ySd.sma, name: "1y sma", color: colors.sky, unit: "ratio" }),
s({ metric: ratio.ratio2ySd.sma, name: "2y sma", color: colors.indigo, unit: "ratio" }),
s({ metric: ratio.ratio4ySd.sma, name: "4y sma", color: colors.purple, unit: "ratio" }),
s({ metric: ratio.ratioSd.sma, name: "all sma", color: colors.rose, unit: "ratio" }),
s({ metric: ratio.ratio, name: "Ratio", color, unit: Unit.ratio }),
s({ metric: ratio.ratio1wSma, name: "1w SMA", color: colors.lime, unit: Unit.ratio }),
s({ metric: ratio.ratio1mSma, name: "1m SMA", color: colors.teal, unit: Unit.ratio }),
s({ metric: ratio.ratio1ySd.sma, name: "1y SMA", color: colors.sky, unit: Unit.ratio }),
s({ metric: ratio.ratio2ySd.sma, name: "2y SMA", color: colors.indigo, unit: Unit.ratio }),
s({ metric: ratio.ratio4ySd.sma, name: "4y SMA", color: colors.purple, unit: Unit.ratio }),
s({ metric: ratio.ratioSd.sma, name: "All SMA", color: colors.rose, unit: Unit.ratio }),
...percentileMap.map(({ name: pctName, prop, color: pctColor }) =>
s({ metric: prop, name: pctName, color: pctColor, defaultActive: false, unit: "ratio", options: { lineStyle: 1 } }),
s({ metric: prop, name: pctName, color: pctColor, defaultActive: false, unit: Unit.ratio, options: { lineStyle: 1 } }),
),
createPriceLine({ unit: "ratio", number: 1 }),
createPriceLine({ unit: Unit.ratio, number: 1 }),
],
},
{
@@ -124,17 +125,17 @@ export function createPriceWithRatioOptions(ctx, { title, legend, ratio, color }
name: nameAddon,
title: `${title} ${titleAddon} Z-Score`,
top: getSdBands(sd).map(({ name: bandName, prop, color: bandColor }) =>
s({ metric: prop, name: bandName, color: bandColor, unit: "usd" }),
s({ metric: prop, name: bandName, color: bandColor, unit: Unit.usd }),
),
bottom: [
s({ metric: sd.zscore, name: "zscore", color, unit: "sd" }),
createPriceLine({ unit: "sd", number: 3 }),
createPriceLine({ unit: "sd", number: 2 }),
createPriceLine({ unit: "sd", number: 1 }),
createPriceLine({ unit: "sd", number: 0 }),
createPriceLine({ unit: "sd", number: -1 }),
createPriceLine({ unit: "sd", number: -2 }),
createPriceLine({ unit: "sd", number: -3 }),
s({ metric: sd.zscore, name: "Z-Score", color, unit: Unit.sd }),
createPriceLine({ unit: Unit.sd, number: 3 }),
createPriceLine({ unit: Unit.sd, number: 2 }),
createPriceLine({ unit: Unit.sd, number: 1 }),
createPriceLine({ unit: Unit.sd, number: 0 }),
createPriceLine({ unit: Unit.sd, number: -1 }),
createPriceLine({ unit: Unit.sd, number: -2 }),
createPriceLine({ unit: Unit.sd, number: -3 }),
],
})),
},
@@ -161,7 +162,7 @@ export function createAveragesSection(ctx, averages) {
name: "Compare",
title: `Market Price ${nameAddon} Moving Averages`,
top: averages.map(({ id, color, sma, ema }) =>
s({ metric: (metricAddon === "sma" ? sma : ema).price, name: id, color, unit: "usd" }),
s({ metric: (metricAddon === "sma" ? sma : ema).price, name: id, color, unit: Unit.usd }),
),
},
...averages.map(({ name, color, sma, ema }) => ({

View File

@@ -0,0 +1,106 @@
/** Market section - Main entry point */
import { Unit } from "../../utils/units.js";
import { buildAverages, createAveragesSection } from "./averages.js";
import { createPerformanceSection } from "./performance.js";
import { createIndicatorsSection } from "./indicators/index.js";
import { createInvestingSection } from "./investing.js";
/**
* Create Market section
* @param {PartialContext} ctx
* @returns {PartialOptionsGroup}
*/
export function createMarketSection(ctx) {
const { colors, brk, s } = ctx;
const { market, supply } = brk.tree.computed;
const {
movingAverage,
ath,
returns,
volatility,
range,
dca,
lookback,
indicators,
} = market;
const averages = buildAverages(colors, movingAverage);
return {
name: "Market",
tree: [
// Price
{
name: "Price",
title: "Bitcoin Price",
},
// Capitalization
{
name: "Capitalization",
title: "Market Capitalization",
bottom: [
s({
metric: brk.mergeMetricPatterns(
supply.marketCap.height,
supply.marketCap.indexes,
),
name: "Capitalization",
unit: Unit.usd,
}),
],
},
// All Time High
{
name: "All Time High",
title: "All Time High",
top: [s({ metric: ath.priceAth, name: "ATH", unit: Unit.usd })],
bottom: [
s({
metric: ath.priceDrawdown,
name: "Drawdown",
color: colors.red,
unit: Unit.percentage,
}),
s({ metric: ath.daysSincePriceAth, name: "Since", unit: Unit.days }),
s({
metric: ath.yearsSincePriceAth,
name: "Since",
unit: Unit.years,
}),
s({
metric: ath.maxDaysBetweenPriceAths,
name: "Max",
color: colors.red,
unit: Unit.days,
}),
s({
metric: ath.maxYearsBetweenPriceAths,
name: "Max",
color: colors.red,
unit: Unit.years,
}),
],
},
// Averages
createAveragesSection(ctx, averages),
// Performance
createPerformanceSection(ctx, returns),
// Indicators
createIndicatorsSection(ctx, {
volatility,
range,
movingAverage,
indicators,
}),
// Investing
createInvestingSection(ctx, { dca, lookback, returns }),
],
};
}

View File

@@ -0,0 +1,85 @@
/** Bands indicators (MinMax, Mayer Multiple) */
import { Unit } from "../../../utils/units.js";
/**
* Create Bands section
* @param {PartialContext} ctx
* @param {Object} args
* @param {Market["range"]} args.range
* @param {Market["movingAverage"]} args.movingAverage
*/
export function createBandsSection(ctx, { range, movingAverage }) {
const { s, colors } = ctx;
return {
name: "Bands",
tree: [
{
name: "MinMax",
tree: [
{
id: "1w",
title: "1 Week",
min: range.price1wMin,
max: range.price1wMax,
},
{
id: "2w",
title: "2 Week",
min: range.price2wMin,
max: range.price2wMax,
},
{
id: "1m",
title: "1 Month",
min: range.price1mMin,
max: range.price1mMax,
},
{
id: "1y",
title: "1 Year",
min: range.price1yMin,
max: range.price1yMax,
},
].map(({ id, title, min, max }) => ({
name: id,
title: `Bitcoin Price ${title} MinMax Bands`,
top: [
s({ metric: min, name: "Min", color: colors.red, unit: Unit.usd }),
s({
metric: max,
name: "Max",
color: colors.green,
unit: Unit.usd,
}),
],
})),
},
{
name: "Mayer Multiple",
title: "Mayer Multiple",
top: [
s({
metric: movingAverage.price200dSma.price,
name: "200d SMA",
color: colors.yellow,
unit: Unit.usd,
}),
s({
metric: movingAverage.price200dSmaX24,
name: "200d SMA x2.4",
color: colors.green,
unit: Unit.usd,
}),
s({
metric: movingAverage.price200dSmaX08,
name: "200d SMA x0.8",
color: colors.red,
unit: Unit.usd,
}),
],
},
],
};
}

View File

@@ -1,5 +1,7 @@
/** Momentum indicators (RSI, StochRSI, Stochastic, MACD) */
import { Unit } from "../../../utils/units.js";
/**
* Create Momentum section
* @param {PartialContext} ctx
@@ -19,25 +21,25 @@ export function createMomentumSection(ctx, indicators) {
metric: indicators.rsi14d,
name: "RSI",
color: colors.indigo,
unit: "index",
unit: Unit.index,
}),
s({
metric: indicators.rsi14dMin,
name: "Min",
color: colors.red,
defaultActive: false,
unit: "index",
unit: Unit.index,
}),
s({
metric: indicators.rsi14dMax,
name: "Max",
color: colors.green,
defaultActive: false,
unit: "index",
unit: Unit.index,
}),
createPriceLine({ unit: "index", number: 70 }),
createPriceLine({ unit: "index", number: 50, defaultActive: false }),
createPriceLine({ unit: "index", number: 30 }),
createPriceLine({ unit: Unit.index, number: 70 }),
createPriceLine({ unit: Unit.index, number: 50, defaultActive: false }),
createPriceLine({ unit: Unit.index, number: 30 }),
],
},
{
@@ -48,32 +50,32 @@ export function createMomentumSection(ctx, indicators) {
// metric: indicators.stochRsi,
// name: "Stoch RSI",
// color: colors.purple,
// unit: "index",
// unit: Unit.index,
// }),
s({
metric: indicators.stochRsiK,
name: "K",
color: colors.blue,
unit: "index",
unit: Unit.index,
}),
s({
metric: indicators.stochRsiD,
name: "D",
color: colors.orange,
unit: "index",
unit: Unit.index,
}),
createPriceLine({ unit: "index", number: 80 }),
createPriceLine({ unit: "index", number: 20 }),
createPriceLine({ unit: Unit.index, number: 80 }),
createPriceLine({ unit: Unit.index, number: 20 }),
],
},
// {
// name: "Stochastic",
// title: "Stochastic Oscillator",
// bottom: [
// s({ metric: indicators.stochK, name: "K", color: colors.blue, unit: "index" }),
// s({ metric: indicators.stochD, name: "D", color: colors.orange, unit: "index" }),
// createPriceLine({ unit: "index", number: 80 }),
// createPriceLine({ unit: "index", number: 20 }),
// s({ metric: indicators.stochK, name: "K", color: colors.blue, unit: Unit.index }),
// s({ metric: indicators.stochD, name: "D", color: colors.orange, unit: Unit.index }),
// createPriceLine({ unit: Unit.index, number: 80 }),
// createPriceLine({ unit: Unit.index, number: 20 }),
// ],
// },
{
@@ -84,21 +86,21 @@ export function createMomentumSection(ctx, indicators) {
metric: indicators.macdLine,
name: "MACD",
color: colors.blue,
unit: "usd",
unit: Unit.usd,
}),
s({
metric: indicators.macdSignal,
name: "Signal",
color: colors.orange,
unit: "usd",
unit: Unit.usd,
}),
/** @type {FetchedHistogramSeriesBlueprint} */ ({
metric: indicators.macdHistogram,
title: "Histogram",
type: "Histogram",
unit: "usd",
unit: Unit.usd,
}),
createPriceLine({ unit: "usd" }),
createPriceLine({ unit: Unit.usd }),
],
},
],

View File

@@ -1,5 +1,7 @@
/** On-chain indicators (Pi Cycle, Puell, NVT, Gini) */
import { Unit } from "../../../utils/units.js";
/**
* Create On-chain section
* @param {PartialContext} ctx
@@ -21,13 +23,13 @@ export function createOnchainSection(ctx, { indicators, movingAverage }) {
metric: movingAverage.price111dSma.price,
name: "111d SMA",
color: colors.green,
unit: "usd",
unit: Unit.usd,
}),
s({
metric: movingAverage.price350dSmaX2,
name: "350d SMA x2",
color: colors.red,
unit: "usd",
unit: Unit.usd,
}),
],
bottom: [
@@ -35,9 +37,9 @@ export function createOnchainSection(ctx, { indicators, movingAverage }) {
metric: indicators.piCycle,
name: "Pi Cycle",
color: colors.purple,
unit: "ratio",
unit: Unit.ratio,
}),
createPriceLine({ unit: "ratio", number: 1 }),
createPriceLine({ unit: Unit.ratio, number: 1 }),
],
},
{
@@ -48,7 +50,7 @@ export function createOnchainSection(ctx, { indicators, movingAverage }) {
metric: indicators.puellMultiple,
name: "Puell",
color: colors.green,
unit: "ratio",
unit: Unit.ratio,
}),
],
},
@@ -60,7 +62,7 @@ export function createOnchainSection(ctx, { indicators, movingAverage }) {
metric: indicators.nvt,
name: "NVT",
color: colors.orange,
unit: "ratio",
unit: Unit.ratio,
}),
],
},
@@ -72,7 +74,7 @@ export function createOnchainSection(ctx, { indicators, movingAverage }) {
metric: indicators.gini,
name: "Gini",
color: colors.red,
unit: "ratio",
unit: Unit.ratio,
}),
],
},

View File

@@ -1,5 +1,7 @@
/** Volatility indicators (Index, True Range, Choppiness, Sharpe, Sortino) */
import { Unit } from "../../../utils/units.js";
/**
* Create Volatility section
* @param {PartialContext} ctx
@@ -17,43 +19,43 @@ export function createVolatilitySection(ctx, { volatility, range }) {
name: "Index",
title: "Bitcoin Price Volatility Index",
bottom: [
s({ metric: volatility.price1wVolatility, name: "1w", color: colors.red, unit: "percentage" }),
s({ metric: volatility.price1mVolatility, name: "1m", color: colors.orange, unit: "percentage" }),
s({ metric: volatility.price1yVolatility, name: "1y", color: colors.lime, unit: "percentage" }),
s({ metric: volatility.price1wVolatility, name: "1w", color: colors.red, unit: Unit.percentage }),
s({ metric: volatility.price1mVolatility, name: "1m", color: colors.orange, unit: Unit.percentage }),
s({ metric: volatility.price1yVolatility, name: "1y", color: colors.lime, unit: Unit.percentage }),
],
},
{
name: "True Range",
title: "Bitcoin Price True Range",
bottom: [s({ metric: range.priceTrueRange, name: "value", color: colors.yellow, unit: "usd" })],
bottom: [s({ metric: range.priceTrueRange, name: "Value", color: colors.yellow, unit: Unit.usd })],
},
{
name: "Choppiness",
title: "Bitcoin Price Choppiness Index",
bottom: [
s({ metric: range.price2wChoppinessIndex, name: "2w", color: colors.red, unit: "index" }),
createPriceLine({ unit: "index", number: 61.8 }),
createPriceLine({ unit: "index", number: 38.2 }),
s({ metric: range.price2wChoppinessIndex, name: "2w", color: colors.red, unit: Unit.index }),
createPriceLine({ unit: Unit.index, number: 61.8 }),
createPriceLine({ unit: Unit.index, number: 38.2 }),
],
},
{
name: "Sharpe Ratio",
title: "Sharpe Ratio",
bottom: [
s({ metric: volatility.sharpe1w, name: "1w", color: colors.red, unit: "ratio" }),
s({ metric: volatility.sharpe1m, name: "1m", color: colors.orange, unit: "ratio" }),
s({ metric: volatility.sharpe1y, name: "1y", color: colors.lime, unit: "ratio" }),
createPriceLine({ unit: "ratio" }),
s({ metric: volatility.sharpe1w, name: "1w", color: colors.red, unit: Unit.ratio }),
s({ metric: volatility.sharpe1m, name: "1m", color: colors.orange, unit: Unit.ratio }),
s({ metric: volatility.sharpe1y, name: "1y", color: colors.lime, unit: Unit.ratio }),
createPriceLine({ unit: Unit.ratio }),
],
},
{
name: "Sortino Ratio",
title: "Sortino Ratio",
bottom: [
s({ metric: volatility.sortino1w, name: "1w", color: colors.red, unit: "ratio" }),
s({ metric: volatility.sortino1m, name: "1m", color: colors.orange, unit: "ratio" }),
s({ metric: volatility.sortino1y, name: "1y", color: colors.lime, unit: "ratio" }),
createPriceLine({ unit: "ratio" }),
s({ metric: volatility.sortino1w, name: "1w", color: colors.red, unit: Unit.ratio }),
s({ metric: volatility.sortino1m, name: "1m", color: colors.orange, unit: Unit.ratio }),
s({ metric: volatility.sortino1y, name: "1y", color: colors.lime, unit: Unit.ratio }),
createPriceLine({ unit: Unit.ratio }),
],
},
],

View File

@@ -1,5 +1,6 @@
/** Investing section (DCA) */
import { Unit } from "../../utils/units.js";
import { periodIdToName } from "./utils.js";
/**
@@ -24,7 +25,7 @@ export function buildDcaClasses(colors, dca) {
year,
color: colors[colorKey],
defaultActive,
costBasis: dca.classAvgPrice[`_${year}`],
costBasis: dca.classAveragePrice[`_${year}`],
returns: dca.classReturns[`_${year}`],
stack: dca.classStack[`_${year}`],
}));
@@ -65,7 +66,7 @@ export function createInvestingSection(ctx, { dca, lookback, returns }) {
const name = periodIdToName(id, true);
const priceAgo = lookback.priceAgo[key];
const priceReturns = returns.priceReturns[key];
const dcaCostBasis = dca.periodAvgPrice[key];
const dcaCostBasis = dca.periodAveragePrice[key];
const dcaReturns = dca.periodReturns[key];
const dcaStack = dca.periodStack[key];
const lumpSumStack = dca.periodLumpSumStack[key];
@@ -76,8 +77,18 @@ export function createInvestingSection(ctx, { dca, lookback, returns }) {
name: "Cost basis",
title: `${name} DCA vs Lump Sum (Cost Basis)`,
top: [
s({ metric: dcaCostBasis, name: "DCA", color: colors.green, unit: "usd" }),
s({ metric: priceAgo, name: "Lump sum", color: colors.orange, unit: "usd" }),
s({
metric: dcaCostBasis,
name: "DCA",
color: colors.green,
unit: Unit.usd,
}),
s({
metric: priceAgo,
name: "Lump sum",
color: colors.orange,
unit: Unit.usd,
}),
],
},
{
@@ -88,28 +99,58 @@ export function createInvestingSection(ctx, { dca, lookback, returns }) {
metric: dcaReturns,
title: "DCA",
type: "Baseline",
unit: "percentage",
unit: Unit.percentage,
}),
/** @type {AnyFetchedSeriesBlueprint} */ ({
metric: priceReturns,
title: "Lump sum",
type: "Baseline",
colors: [colors.lime, colors.red],
unit: "percentage",
unit: Unit.percentage,
}),
createPriceLine({ unit: "percentage" }),
createPriceLine({ unit: Unit.percentage }),
],
},
{
name: "Stack",
title: `${name} DCA vs Lump Sum Stack ($100/day)`,
bottom: [
s({ metric: dcaStack.sats, name: "DCA", color: colors.green, unit: "sats" }),
s({ metric: dcaStack.bitcoin, name: "DCA", color: colors.green, unit: "btc" }),
s({ metric: dcaStack.dollars, name: "DCA", color: colors.green, unit: "usd" }),
s({ metric: lumpSumStack.sats, name: "Lump sum", color: colors.orange, unit: "sats" }),
s({ metric: lumpSumStack.bitcoin, name: "Lump sum", color: colors.orange, unit: "btc" }),
s({ metric: lumpSumStack.dollars, name: "Lump sum", color: colors.orange, unit: "usd" }),
s({
metric: dcaStack.sats,
name: "DCA",
color: colors.green,
unit: Unit.sats,
}),
s({
metric: dcaStack.bitcoin,
name: "DCA",
color: colors.green,
unit: Unit.btc,
}),
s({
metric: dcaStack.dollars,
name: "DCA",
color: colors.green,
unit: Unit.usd,
}),
s({
metric: lumpSumStack.sats,
name: "Lump sum",
color: colors.orange,
unit: Unit.sats,
}),
s({
metric: lumpSumStack.bitcoin,
name: "Lump sum",
color: colors.orange,
unit: Unit.btc,
}),
s({
metric: lumpSumStack.dollars,
name: "Lump sum",
color: colors.orange,
unit: Unit.usd,
}),
],
},
],
@@ -128,32 +169,60 @@ export function createInvestingSection(ctx, { dca, lookback, returns }) {
{
name: "Cost basis",
title: "DCA Cost Basis by Year",
top: dcaClasses.map(({ year, color, defaultActive, costBasis }) =>
s({ metric: costBasis, name: `${year}`, color, defaultActive, unit: "usd" }),
top: dcaClasses.map(
({ year, color, defaultActive, costBasis }) =>
s({
metric: costBasis,
name: `${year}`,
color,
defaultActive,
unit: Unit.usd,
}),
),
},
{
name: "Returns",
title: "DCA Returns by Year",
bottom: dcaClasses.map(({ year, color, defaultActive, returns }) =>
/** @type {AnyFetchedSeriesBlueprint} */ ({
metric: returns,
title: `${year}`,
type: "Baseline",
color,
defaultActive,
unit: "percentage",
}),
bottom: dcaClasses.map(
({ year, color, defaultActive, returns }) =>
/** @type {AnyFetchedSeriesBlueprint} */ ({
metric: returns,
title: `${year}`,
type: "Baseline",
color,
defaultActive,
unit: Unit.percentage,
}),
),
},
{
name: "Stack",
title: "DCA Stack by Year ($100/day)",
bottom: dcaClasses.flatMap(({ year, color, defaultActive, stack }) => [
s({ metric: stack.sats, name: `${year}`, color, defaultActive, unit: "sats" }),
s({ metric: stack.bitcoin, name: `${year}`, color, defaultActive, unit: "btc" }),
s({ metric: stack.dollars, name: `${year}`, color, defaultActive, unit: "usd" }),
]),
bottom: dcaClasses.flatMap(
({ year, color, defaultActive, stack }) => [
s({
metric: stack.sats,
name: `${year}`,
color,
defaultActive,
unit: Unit.sats,
}),
s({
metric: stack.bitcoin,
name: `${year}`,
color,
defaultActive,
unit: Unit.btc,
}),
s({
metric: stack.dollars,
name: `${year}`,
color,
defaultActive,
unit: Unit.usd,
}),
],
),
},
],
},
@@ -164,7 +233,14 @@ export function createInvestingSection(ctx, { dca, lookback, returns }) {
{
name: "Cost basis",
title: `DCA Class ${year} Cost Basis`,
top: [s({ metric: costBasis, name: "Cost basis", color, unit: "usd" })],
top: [
s({
metric: costBasis,
name: "Cost basis",
color,
unit: Unit.usd,
}),
],
},
{
name: "Returns",
@@ -175,7 +251,7 @@ export function createInvestingSection(ctx, { dca, lookback, returns }) {
title: "Returns",
type: "Baseline",
color,
unit: "percentage",
unit: Unit.percentage,
}),
],
},
@@ -183,9 +259,24 @@ export function createInvestingSection(ctx, { dca, lookback, returns }) {
name: "Stack",
title: `DCA Class ${year} Stack ($100/day)`,
bottom: [
s({ metric: stack.sats, name: "Stack", color, unit: "sats" }),
s({ metric: stack.bitcoin, name: "Stack", color, unit: "btc" }),
s({ metric: stack.dollars, name: "Stack", color, unit: "usd" }),
s({
metric: stack.sats,
name: "Stack",
color,
unit: Unit.sats,
}),
s({
metric: stack.bitcoin,
name: "Stack",
color,
unit: Unit.btc,
}),
s({
metric: stack.dollars,
name: "Stack",
color,
unit: Unit.usd,
}),
],
},
],

View File

@@ -1,5 +1,6 @@
/** Performance section */
import { Unit } from "../../utils/units.js";
import { periodIdToName } from "./utils.js";
/**
@@ -36,22 +37,22 @@ export function createPerformanceSection(ctx, returns) {
bottom: [
/** @type {AnyFetchedSeriesBlueprint} */ ({
metric: priceReturns,
title: "total",
title: "Total",
type: "Baseline",
unit: "percentage",
unit: Unit.percentage,
}),
...(cagr
? [
/** @type {AnyFetchedSeriesBlueprint} */ ({
metric: cagr,
title: "cagr",
title: "CAGR",
type: "Baseline",
colors: [colors.lime, colors.pink],
unit: "percentage",
unit: Unit.percentage,
}),
]
: []),
createPriceLine({ unit: "percentage" }),
createPriceLine({ unit: Unit.percentage }),
],
};
}),

View File

@@ -1,6 +1,6 @@
/** Partial options - Main entry point */
import { localhost } from "../../utils/env.js";
import { localhost } from "../utils/env.js";
import { createContext } from "./context.js";
import {
buildCohortData,

View File

@@ -1,485 +0,0 @@
/** Chain section builder - typed tree-based patterns */
/**
* Create Chain section
* @param {PartialContext} ctx
* @returns {PartialOptionsGroup}
*/
export function createChainSection(ctx) {
const { colors, brk, s, createPriceLine } = ctx;
const { blocks, transactions, pools, inputs, outputs, market, scripts, supply } = brk.tree.computed;
const { indexed } = brk.tree;
/**
* Create sum/cumulative series from a BlockCountPattern
* @template T
* @param {BlockCountPattern<T>} pattern
* @param {string} name
* @param {Color} [sumColor]
* @param {Color} [cumulativeColor]
* @param {Unit} unit
*/
const fromBlockCount = (pattern, name, unit, sumColor, cumulativeColor) => [
s({ metric: pattern.base, name: `${name} sum`, color: sumColor, unit }),
s({ metric: pattern.cumulative, name: `${name} cumulative`, color: cumulativeColor ?? colors.blue, unit, defaultActive: false }),
];
/**
* Create series from BlockSizePattern (has average, min, max, percentiles)
* @template T
* @param {BlockSizePattern<T>} pattern
* @param {string} name
* @param {Unit} unit
*/
const fromBlockSize = (pattern, name, unit) => [
s({ metric: pattern.average, name: `${name} avg`, unit }),
s({ metric: pattern.sum, name: `${name} sum`, color: colors.blue, unit, defaultActive: false }),
s({ metric: pattern.cumulative, name: `${name} cumulative`, color: colors.indigo, unit, defaultActive: false }),
s({ metric: pattern.min, name: `${name} min`, color: colors.red, unit, defaultActive: false }),
s({ metric: pattern.max, name: `${name} max`, color: colors.green, unit, defaultActive: false }),
s({ metric: pattern.pct10, name: `${name} pct10`, color: colors.rose, unit, defaultActive: false }),
s({ metric: pattern.pct25, name: `${name} pct25`, color: colors.pink, unit, defaultActive: false }),
s({ metric: pattern.median, name: `${name} median`, color: colors.purple, unit, defaultActive: false }),
s({ metric: pattern.pct75, name: `${name} pct75`, color: colors.violet, unit, defaultActive: false }),
s({ metric: pattern.pct90, name: `${name} pct90`, color: colors.fuchsia, unit, defaultActive: false }),
];
/**
* Create series from BlockIntervalPattern (has average, min, max, percentiles)
* @template T
* @param {BlockIntervalPattern<T>} pattern
* @param {string} name
* @param {Unit} unit
*/
const fromBlockInterval = (pattern, name, unit) => [
s({ metric: pattern.average, name: `${name} avg`, unit }),
s({ metric: pattern.min, name: `${name} min`, color: colors.red, unit, defaultActive: false }),
s({ metric: pattern.max, name: `${name} max`, color: colors.green, unit, defaultActive: false }),
s({ metric: pattern.pct10, name: `${name} pct10`, color: colors.rose, unit, defaultActive: false }),
s({ metric: pattern.pct25, name: `${name} pct25`, color: colors.pink, unit, defaultActive: false }),
s({ metric: pattern.median, name: `${name} median`, color: colors.purple, unit, defaultActive: false }),
s({ metric: pattern.pct75, name: `${name} pct75`, color: colors.violet, unit, defaultActive: false }),
s({ metric: pattern.pct90, name: `${name} pct90`, color: colors.fuchsia, unit, defaultActive: false }),
];
/**
* Create series from BitcoinPattern (has base, cumulative)
* @template T
* @param {BitcoinPattern<T>} pattern
* @param {string} name
* @param {Unit} unit
* @param {Color} [sumColor]
* @param {Color} [cumulativeColor]
*/
const fromBitcoin = (pattern, name, unit, sumColor, cumulativeColor) => [
s({ metric: pattern.base, name: `${name}`, color: sumColor, unit }),
s({ metric: pattern.cumulative, name: `${name} cumulative`, color: cumulativeColor ?? colors.blue, unit, defaultActive: false }),
];
/**
* Create series from CoinbasePattern (has sats, bitcoin, dollars as BitcoinPattern)
* BitcoinPattern has .base and .cumulative (no .sum)
* @param {CoinbasePattern} pattern
* @param {string} name
* @param {Color} sumColor
* @param {Color} cumulativeColor
*/
const fromCoinbase = (pattern, name, sumColor, cumulativeColor) => [
s({ metric: pattern.sats.base, name: `${name}`, color: sumColor, unit: "sats" }),
s({ metric: pattern.sats.cumulative, name: `${name} cumulative`, color: cumulativeColor, unit: "sats", defaultActive: false }),
s({ metric: pattern.bitcoin.base, name: `${name}`, color: sumColor, unit: "btc" }),
s({ metric: pattern.bitcoin.cumulative, name: `${name} cumulative`, color: cumulativeColor, unit: "btc", defaultActive: false }),
s({ metric: pattern.dollars.base, name: `${name}`, color: sumColor, unit: "usd" }),
s({ metric: pattern.dollars.cumulative, name: `${name} cumulative`, color: cumulativeColor, unit: "usd", defaultActive: false }),
];
/**
* Create series from ValuePattern (has sats, bitcoin, dollars as BlockCountPattern)
* BlockCountPattern has .base, .sum, and .cumulative
* @param {ValuePattern} pattern
* @param {string} name
* @param {Color} sumColor
* @param {Color} cumulativeColor
*/
const fromValuePattern = (pattern, name, sumColor, cumulativeColor) => [
s({ metric: pattern.sats.base, name: `${name}`, color: sumColor, unit: "sats" }),
s({ metric: pattern.sats.cumulative, name: `${name} cumulative`, color: cumulativeColor, unit: "sats", defaultActive: false }),
s({ metric: pattern.bitcoin.base, name: `${name}`, color: sumColor, unit: "btc" }),
s({ metric: pattern.bitcoin.cumulative, name: `${name} cumulative`, color: cumulativeColor, unit: "btc", defaultActive: false }),
s({ metric: pattern.dollars.base, name: `${name}`, color: sumColor, unit: "usd" }),
s({ metric: pattern.dollars.cumulative, name: `${name} cumulative`, color: cumulativeColor, unit: "usd", defaultActive: false }),
];
/**
* Create series from RewardPattern (has .base as Indexes2<Sats>, plus bitcoin/dollars as BlockCountPattern, sats as SatsPattern)
* Note: SatsPattern only has cumulative and sum, so we use pattern.base for raw sats
* @param {RewardPattern} pattern
* @param {string} name
* @param {Color} sumColor
* @param {Color} cumulativeColor
*/
const fromRewardPattern = (pattern, name, sumColor, cumulativeColor) => [
s({ metric: pattern.base, name: `${name}`, color: sumColor, unit: "sats" }),
s({ metric: pattern.sats.cumulative, name: `${name} cumulative`, color: cumulativeColor, unit: "sats", defaultActive: false }),
s({ metric: pattern.bitcoin.base, name: `${name}`, color: sumColor, unit: "btc" }),
s({ metric: pattern.bitcoin.cumulative, name: `${name} cumulative`, color: cumulativeColor, unit: "btc", defaultActive: false }),
s({ metric: pattern.dollars.base, name: `${name}`, color: sumColor, unit: "usd" }),
s({ metric: pattern.dollars.cumulative, name: `${name} cumulative`, color: cumulativeColor, unit: "usd", defaultActive: false }),
];
// Build pools tree dynamically
const poolEntries = Object.entries(pools.vecs);
const poolsTree = poolEntries.map(([key, pool]) => {
const poolName = brk.POOL_ID_TO_POOL_NAME[/** @type {keyof typeof brk.POOL_ID_TO_POOL_NAME} */ (key.toLowerCase())] || key;
return {
name: poolName,
tree: [
{
name: "Dominance",
title: `Mining Dominance of ${poolName}`,
bottom: [
s({ metric: pool._1dDominance.base, name: "1d", color: colors.rose, unit: "percentage", defaultActive: false }),
s({ metric: pool._1wDominance, name: "1w", color: colors.red, unit: "percentage", defaultActive: false }),
s({ metric: pool._1mDominance, name: "1m", unit: "percentage" }),
s({ metric: pool._1yDominance, name: "1y", color: colors.lime, unit: "percentage", defaultActive: false }),
s({ metric: pool.dominance.base, name: "all time", color: colors.teal, unit: "percentage", defaultActive: false }),
],
},
{
name: "Blocks mined",
title: `Blocks mined by ${poolName}`,
bottom: [
s({ metric: pool.blocksMined.base, name: "Sum", unit: "count" }),
s({ metric: pool.blocksMined.cumulative, name: "Cumulative", color: colors.blue, unit: "count" }),
s({ metric: pool._1wBlocksMined, name: "1w Sum", color: colors.red, unit: "count", defaultActive: false }),
s({ metric: pool._1mBlocksMined, name: "1m Sum", color: colors.pink, unit: "count", defaultActive: false }),
s({ metric: pool._1yBlocksMined, name: "1y Sum", color: colors.purple, unit: "count", defaultActive: false }),
],
},
{
name: "Rewards",
title: `Rewards collected by ${poolName}`,
bottom: [
...fromValuePattern(pool.coinbase, "coinbase", colors.orange, colors.red),
...fromRewardPattern(pool.subsidy, "subsidy", colors.lime, colors.emerald),
...fromRewardPattern(pool.fee, "fee", colors.cyan, colors.indigo),
],
},
{
name: "Days since block",
title: `Days since ${poolName} mined a block`,
bottom: [
s({ metric: pool.daysSinceBlock, name: "Since block", unit: "days" }),
],
},
],
};
});
return {
name: "Chain",
tree: [
// Block
{
name: "Block",
tree: [
{
name: "Count",
title: "Block Count",
bottom: [
...fromBlockCount(blocks.count.blockCount, "Block", "count"),
s({ metric: blocks.count.blockCountTarget, name: "Target", color: colors.gray, unit: "count", options: { lineStyle: 4 } }),
s({ metric: blocks.count._1wBlockCount, name: "1w sum", color: colors.red, unit: "count", defaultActive: false }),
s({ metric: blocks.count._1mBlockCount, name: "1m sum", color: colors.pink, unit: "count", defaultActive: false }),
s({ metric: blocks.count._1yBlockCount, name: "1y sum", color: colors.purple, unit: "count", defaultActive: false }),
],
},
{
name: "Interval",
title: "Block Interval",
bottom: [
s({ metric: blocks.interval.interval, name: "Interval", unit: "secs" }),
...fromBlockInterval(blocks.interval.blockInterval, "Interval", "secs"),
createPriceLine({ unit: "secs", name: "Target", number: 600 }),
],
},
{
name: "Size",
title: "Block Size",
bottom: [
s({ metric: blocks.size.vbytes, name: "vbytes raw", unit: "vb" }),
s({ metric: indexed.block.weight, name: "weight raw", unit: "wu" }),
...fromBlockSize(blocks.size.blockSize, "size", "bytes"),
...fromBlockSize(blocks.weight.blockWeight, "weight", "wu"),
...fromBlockSize(blocks.size.blockVbytes, "vbytes", "vb"),
],
},
],
},
// Transaction
{
name: "Transaction",
tree: [
{
name: "Count",
title: "Transaction Count",
bottom: fromBitcoin(transactions.count.txCount, "Count", "count"),
},
{
name: "Volume",
title: "Transaction Volume",
bottom: [
s({ metric: transactions.volume.sentSum.sats, name: "Sent", unit: "sats" }),
s({ metric: transactions.volume.sentSum.bitcoin.base, name: "Sent", unit: "btc" }),
s({ metric: transactions.volume.sentSum.dollars, name: "Sent", unit: "usd" }),
s({ metric: transactions.volume.annualizedVolume, name: "annualized", color: colors.red, unit: "sats", defaultActive: false }),
s({ metric: transactions.volume.annualizedVolumeBtc, name: "annualized", color: colors.red, unit: "btc", defaultActive: false }),
s({ metric: transactions.volume.annualizedVolumeUsd, name: "annualized", color: colors.lime, unit: "usd", defaultActive: false }),
],
},
{
name: "Size",
title: "Transaction Size",
bottom: [
...fromBlockInterval(transactions.size.txWeight, "weight", "wu"),
...fromBlockInterval(transactions.size.txVsize, "vsize", "vb"),
],
},
{
name: "Versions",
title: "Transaction Versions",
bottom: [
...fromBlockCount(transactions.versions.txV1, "v1", "count", colors.orange, colors.red),
...fromBlockCount(transactions.versions.txV2, "v2", "count", colors.cyan, colors.blue),
...fromBlockCount(transactions.versions.txV3, "v3", "count", colors.lime, colors.green),
],
},
{
name: "Velocity",
title: "Transactions Velocity",
bottom: [
s({ metric: supply.velocity.btc, name: "bitcoin", unit: "ratio" }),
s({ metric: supply.velocity.usd, name: "dollars", color: colors.emerald, unit: "ratio" }),
],
},
{
name: "Speed",
title: "Transactions Per Second",
bottom: [
s({ metric: transactions.volume.txPerSec, name: "Transactions", unit: "/sec" }),
],
},
],
},
// Input
{
name: "Input",
tree: [
{
name: "Count",
title: "Transaction Input Count",
bottom: [
...fromBlockSize(inputs.count.count, "Input", "count"),
],
},
{
name: "Speed",
title: "Inputs Per Second",
bottom: [
s({ metric: transactions.volume.inputsPerSec, name: "Inputs", unit: "/sec" }),
],
},
],
},
// Output
{
name: "Output",
tree: [
{
name: "Count",
title: "Transaction Output Count",
bottom: [
...fromBlockSize(outputs.count.count, "Output", "count"),
],
},
{
name: "Speed",
title: "Outputs Per Second",
bottom: [
s({ metric: transactions.volume.outputsPerSec, name: "Outputs", unit: "/sec" }),
],
},
],
},
// UTXO
{
name: "UTXO",
tree: [
{
name: "Count",
title: "UTXO Count",
bottom: [
s({ metric: outputs.count.utxoCount.base, name: "Count", unit: "count" }),
],
},
],
},
// Coinbase
{
name: "Coinbase",
title: "Coinbase Rewards",
bottom: fromCoinbase(blocks.rewards.coinbase, "Coinbase", colors.orange, colors.red),
},
// Subsidy
{
name: "Subsidy",
title: "Block Subsidy",
bottom: [
...fromCoinbase(blocks.rewards.subsidy, "Subsidy", colors.lime, colors.emerald),
s({ metric: blocks.rewards.subsidyDominance, name: "Dominance", color: colors.purple, unit: "percentage", defaultActive: false }),
],
},
// Fee
{
name: "Fee",
tree: [
{
name: "Total",
title: "Transaction Fees",
bottom: [
s({ metric: transactions.fees.fee.sats.sum, name: "Sum", unit: "sats" }),
s({ metric: transactions.fees.fee.sats.cumulative, name: "Cumulative", color: colors.blue, unit: "sats", defaultActive: false }),
s({ metric: transactions.fees.fee.bitcoin.sum, name: "Sum", unit: "btc" }),
s({ metric: transactions.fees.fee.bitcoin.cumulative, name: "Cumulative", color: colors.blue, unit: "btc", defaultActive: false }),
s({ metric: transactions.fees.fee.dollars.sum, name: "Sum", unit: "usd" }),
s({ metric: transactions.fees.fee.dollars.cumulative, name: "Cumulative", color: colors.blue, unit: "usd", defaultActive: false }),
s({ metric: blocks.rewards.feeDominance, name: "Dominance", color: colors.purple, unit: "percentage", defaultActive: false }),
],
},
{
name: "Rate",
title: "Fee Rate",
bottom: [
s({ metric: transactions.fees.feeRate.base, name: "Rate", unit: "sat/vb" }),
s({ metric: transactions.fees.feeRate.average, name: "Average", color: colors.blue, unit: "sat/vb" }),
s({ metric: transactions.fees.feeRate.median, name: "Median", color: colors.purple, unit: "sat/vb" }),
s({ metric: transactions.fees.feeRate.min, name: "Min", color: colors.red, unit: "sat/vb", defaultActive: false }),
s({ metric: transactions.fees.feeRate.max, name: "Max", color: colors.green, unit: "sat/vb", defaultActive: false }),
s({ metric: transactions.fees.feeRate.pct10, name: "pct10", color: colors.rose, unit: "sat/vb", defaultActive: false }),
s({ metric: transactions.fees.feeRate.pct25, name: "pct25", color: colors.pink, unit: "sat/vb", defaultActive: false }),
s({ metric: transactions.fees.feeRate.pct75, name: "pct75", color: colors.violet, unit: "sat/vb", defaultActive: false }),
s({ metric: transactions.fees.feeRate.pct90, name: "pct90", color: colors.fuchsia, unit: "sat/vb", defaultActive: false }),
],
},
],
},
// Mining
{
name: "Mining",
tree: [
{
name: "Hashrate",
title: "Network Hashrate",
bottom: [
s({ metric: blocks.mining.hashRate, name: "Hashrate", unit: "h/s" }),
s({ metric: blocks.mining.hashRate1wSma, name: "1w SMA", color: colors.red, unit: "h/s", defaultActive: false }),
s({ metric: blocks.mining.hashRate1mSma, name: "1m SMA", color: colors.orange, unit: "h/s", defaultActive: false }),
s({ metric: blocks.mining.hashRate2mSma, name: "2m SMA", color: colors.yellow, unit: "h/s", defaultActive: false }),
s({ metric: blocks.mining.hashRate1ySma, name: "1y SMA", color: colors.lime, unit: "h/s", defaultActive: false }),
],
},
{
name: "Difficulty",
title: "Network Difficulty",
bottom: [
s({ metric: blocks.mining.difficulty, name: "Difficulty", unit: "difficulty" }),
s({ metric: blocks.mining.difficultyAdjustment, name: "Adjustment", color: colors.orange, unit: "percentage", defaultActive: false }),
s({ metric: blocks.mining.difficultyAsHash, name: "As hash", color: colors.default, unit: "h/s", defaultActive: false, options: { lineStyle: 1 } }),
s({ metric: blocks.difficulty.blocksBeforeNextDifficultyAdjustment, name: "Blocks until adj.", color: colors.indigo, unit: "blocks", defaultActive: false }),
s({ metric: blocks.difficulty.daysBeforeNextDifficultyAdjustment, name: "Days until adj.", color: colors.purple, unit: "days", defaultActive: false }),
],
},
{
name: "Hash Price",
title: "Hash Price",
bottom: [
s({ metric: blocks.mining.hashPriceThs, name: "TH/s", color: colors.emerald, unit: "usd/(th/s)/day" }),
s({ metric: blocks.mining.hashPricePhs, name: "PH/s", color: colors.emerald, unit: "usd/(ph/s)/day" }),
s({ metric: blocks.mining.hashPriceRebound, name: "Rebound", color: colors.yellow, unit: "percentage" }),
s({ metric: blocks.mining.hashPriceThsMin, name: "TH/s Min", color: colors.red, unit: "usd/(th/s)/day", options: { lineStyle: 1 } }),
s({ metric: blocks.mining.hashPricePhsMin, name: "PH/s Min", color: colors.red, unit: "usd/(ph/s)/day", options: { lineStyle: 1 } }),
],
},
{
name: "Hash Value",
title: "Hash Value",
bottom: [
s({ metric: blocks.mining.hashValueThs, name: "TH/s", color: colors.orange, unit: "sats/(th/s)/day" }),
s({ metric: blocks.mining.hashValuePhs, name: "PH/s", color: colors.orange, unit: "sats/(ph/s)/day" }),
s({ metric: blocks.mining.hashValueRebound, name: "Rebound", color: colors.yellow, unit: "percentage" }),
s({ metric: blocks.mining.hashValueThsMin, name: "TH/s Min", color: colors.red, unit: "sats/(th/s)/day", options: { lineStyle: 1 } }),
s({ metric: blocks.mining.hashValuePhsMin, name: "PH/s Min", color: colors.red, unit: "sats/(ph/s)/day", options: { lineStyle: 1 } }),
],
},
{
name: "Halving",
title: "Halving Info",
bottom: [
s({ metric: blocks.halving.blocksBeforeNextHalving, name: "Blocks until halving", unit: "blocks" }),
s({ metric: blocks.halving.daysBeforeNextHalving, name: "Days until halving", color: colors.orange, unit: "days" }),
s({ metric: blocks.halving.halvingepoch, name: "Halving epoch", color: colors.purple, unit: "epoch", defaultActive: false }),
],
},
{
name: "Puell Multiple",
title: "Puell Multiple",
bottom: [
s({ metric: market.indicators.puellMultiple, name: "Puell Multiple", unit: "ratio" }),
createPriceLine({ unit: "ratio", number: 1 }),
],
},
],
},
// Pools
{
name: "Pools",
tree: poolsTree,
},
// Unspendable
{
name: "Unspendable",
tree: [
{
name: "OP_RETURN",
tree: [
{
name: "Outputs",
title: "OP_RETURN Outputs",
bottom: fromBitcoin(scripts.count.opreturnCount, "Count", "count"),
},
],
},
],
},
// Inflation
{
name: "Inflation",
title: "Inflation Rate",
bottom: [
s({ metric: supply.inflation.indexes, name: "Rate", unit: "percentage" }),
],
},
],
};
}

View File

@@ -1,306 +0,0 @@
/** Cointime section builder - typed tree-based patterns */
/**
* Create price with ratio options for cointime prices
* @param {PartialContext} ctx
* @param {Object} args
* @param {string} args.name
* @param {string} args.title
* @param {string} args.legend
* @param {AnyMetricPattern} args.price
* @param {ActivePriceRatioPattern} args.ratio
* @param {Color} [args.color]
* @returns {PartialOptionsTree}
*/
function createCointimePriceWithRatioOptions(ctx, { name, title, legend, price, ratio, color }) {
const { s, colors, createPriceLine } = ctx;
// Percentile USD mappings
const percentileUsdMap = [
{ name: "pct99", prop: ratio.ratioPct99Usd, color: colors.rose },
{ name: "pct98", prop: ratio.ratioPct98Usd, color: colors.pink },
{ name: "pct95", prop: ratio.ratioPct95Usd, color: colors.fuchsia },
{ name: "pct5", prop: ratio.ratioPct5Usd, color: colors.cyan },
{ name: "pct2", prop: ratio.ratioPct2Usd, color: colors.sky },
{ name: "pct1", prop: ratio.ratioPct1Usd, color: colors.blue },
];
// Percentile ratio mappings
const percentileMap = [
{ name: "pct99", prop: ratio.ratioPct99, color: colors.rose },
{ name: "pct98", prop: ratio.ratioPct98, color: colors.pink },
{ name: "pct95", prop: ratio.ratioPct95, color: colors.fuchsia },
{ name: "pct5", prop: ratio.ratioPct5, color: colors.cyan },
{ name: "pct2", prop: ratio.ratioPct2, color: colors.sky },
{ name: "pct1", prop: ratio.ratioPct1, color: colors.blue },
];
// SD patterns by window
const sdPatterns = [
{ nameAddon: "all", titleAddon: "", sd: ratio.ratioSd },
{ nameAddon: "4y", titleAddon: "4y", sd: ratio.ratio4ySd },
{ nameAddon: "2y", titleAddon: "2y", sd: ratio.ratio2ySd },
{ nameAddon: "1y", titleAddon: "1y", sd: ratio.ratio1ySd },
];
/** @param {Ratio1ySdPattern} sd */
const getSdBands = (sd) => [
{ name: "0σ", prop: sd._0sdUsd, color: colors.lime },
{ name: "+0.5σ", prop: sd.p05sdUsd, color: colors.yellow },
{ name: "+1σ", prop: sd.p1sdUsd, color: colors.amber },
{ name: "+1.5σ", prop: sd.p15sdUsd, color: colors.orange },
{ name: "+2σ", prop: sd.p2sdUsd, color: colors.red },
{ name: "+2.5σ", prop: sd.p25sdUsd, color: colors.rose },
{ name: "+3σ", prop: sd.p3sd, color: colors.pink },
{ name: "0.5σ", prop: sd.m05sdUsd, color: colors.teal },
{ name: "1σ", prop: sd.m1sdUsd, color: colors.cyan },
{ name: "1.5σ", prop: sd.m15sdUsd, color: colors.sky },
{ name: "2σ", prop: sd.m2sdUsd, color: colors.blue },
{ name: "2.5σ", prop: sd.m25sdUsd, color: colors.indigo },
{ name: "3σ", prop: sd.m3sd, color: colors.violet },
];
return [
{
name: "price",
title,
top: [s({ metric: price, name: legend, color, unit: "usd" })],
},
{
name: "Ratio",
title: `${title} Ratio`,
top: [
s({ metric: price, name: legend, color, unit: "usd" }),
...percentileUsdMap.map(({ name: pctName, prop, color: pctColor }) =>
s({
metric: prop,
name: pctName,
color: pctColor,
defaultActive: false,
unit: "usd",
options: { lineStyle: 1 },
}),
),
],
bottom: [
s({ metric: ratio.ratio, name: "ratio", color, unit: "ratio" }),
s({ metric: ratio.ratio1wSma, name: "1w sma", color: colors.lime, unit: "ratio" }),
s({ metric: ratio.ratio1mSma, name: "1m sma", color: colors.teal, unit: "ratio" }),
s({ metric: ratio.ratio1ySd.sma, name: "1y sma", color: colors.sky, unit: "ratio" }),
s({ metric: ratio.ratio2ySd.sma, name: "2y sma", color: colors.indigo, unit: "ratio" }),
s({ metric: ratio.ratio4ySd.sma, name: "4y sma", color: colors.purple, unit: "ratio" }),
s({ metric: ratio.ratioSd.sma, name: "all sma", color: colors.rose, unit: "ratio" }),
...percentileMap.map(({ name: pctName, prop, color: pctColor }) =>
s({
metric: prop,
name: pctName,
color: pctColor,
defaultActive: false,
unit: "ratio",
options: { lineStyle: 1 },
}),
),
createPriceLine({ unit: "ratio", number: 1 }),
],
},
{
name: "ZScores",
tree: sdPatterns.map(({ nameAddon, titleAddon, sd }) => ({
name: nameAddon,
title: `${title} ${titleAddon} Z-Score`,
top: getSdBands(sd).map(({ name: bandName, prop, color: bandColor }) =>
s({ metric: prop, name: bandName, color: bandColor, unit: "usd" }),
),
bottom: [
s({ metric: sd.zscore, name: "zscore", color, unit: "sd" }),
createPriceLine({ unit: "sd", number: 3 }),
createPriceLine({ unit: "sd", number: 2 }),
createPriceLine({ unit: "sd", number: 1 }),
createPriceLine({ unit: "sd", number: 0 }),
createPriceLine({ unit: "sd", number: -1 }),
createPriceLine({ unit: "sd", number: -2 }),
createPriceLine({ unit: "sd", number: -3 }),
],
})),
},
];
}
/**
* Create Cointime section
* @param {PartialContext} ctx
* @returns {PartialOptionsGroup}
*/
export function createCointimeSection(ctx) {
const { colors, brk, s } = ctx;
const { cointime, distribution, supply } = brk.tree.computed;
const { pricing, cap, activity, supply: cointimeSupply, adjusted } = cointime;
const utxoCohorts = distribution.utxoCohorts;
// Cointime prices data
const cointimePrices = [
{
price: pricing.trueMarketMean,
ratio: pricing.trueMarketMeanRatio,
name: "True market mean",
title: "true market mean",
color: colors.blue,
},
{
price: pricing.vaultedPrice,
ratio: pricing.vaultedPriceRatio,
name: "Vaulted",
title: "vaulted price",
color: colors.lime,
},
{
price: pricing.activePrice,
ratio: pricing.activePriceRatio,
name: "Active",
title: "active price",
color: colors.rose,
},
{
price: pricing.cointimePrice,
ratio: pricing.cointimePriceRatio,
name: "cointime",
title: "cointime price",
color: colors.yellow,
},
];
// Cointime capitalizations data
const cointimeCapitalizations = [
{ metric: cap.vaultedCap, name: "vaulted", title: "vaulted Capitalization", color: colors.lime },
{ metric: cap.activeCap, name: "active", title: "active Capitalization", color: colors.rose },
{ metric: cap.cointimeCap, name: "cointime", title: "cointime Capitalization", color: colors.yellow },
{ metric: cap.investorCap, name: "investor", title: "investor Capitalization", color: colors.fuchsia },
{ metric: cap.thermoCap, name: "thermo", title: "thermo Capitalization", color: colors.emerald },
];
return {
name: "Cointime",
tree: [
// Prices
{
name: "Prices",
tree: [
{
name: "Compare",
title: "Compare Cointime Prices",
top: cointimePrices.map(({ price, name, color }) =>
s({ metric: price, name, color, unit: "usd" }),
),
},
...cointimePrices.map(({ price, ratio, name, color, title }) => ({
name,
tree: createCointimePriceWithRatioOptions(ctx, {
price,
ratio,
legend: name,
color,
name,
title,
}),
})),
],
},
// Capitalization
{
name: "Capitalization",
tree: [
{
name: "Compare",
title: "Compare Cointime Capitalizations",
bottom: [
s({ metric: supply.marketCap.height, name: "Market", color: colors.default, unit: "usd" }),
s({ metric: utxoCohorts.all.realized.realizedCap, name: "Realized", color: colors.orange, unit: "usd" }),
...cointimeCapitalizations.map(({ metric, name, color }) =>
s({ metric, name, color, unit: "usd" }),
),
],
},
...cointimeCapitalizations.map(({ metric, name, color, title }) => ({
name,
title,
bottom: [
s({ metric, name, color, unit: "usd" }),
s({ metric: supply.marketCap.height, name: "Market", color: colors.default, unit: "usd" }),
s({ metric: utxoCohorts.all.realized.realizedCap, name: "Realized", color: colors.orange, unit: "usd" }),
],
})),
],
},
// Supply
{
name: "Supply",
title: "Cointime Supply",
bottom: /** @type {const} */ ([
[utxoCohorts.all.supply.supply, "all", colors.orange],
[cointimeSupply.vaultedSupply, "vaulted", colors.lime],
[cointimeSupply.activeSupply, "active", colors.rose],
]).flatMap(([supplyItem, name, color]) => [
s({ metric: supplyItem.sats, name, color, unit: "sats" }),
s({ metric: supplyItem.bitcoin, name, color, unit: "btc" }),
s({ metric: supplyItem.dollars, name, color, unit: "usd" }),
]),
},
// Liveliness & Vaultedness
{
name: "Liveliness & Vaultedness",
title: "Liveliness & Vaultedness",
bottom: [
s({ metric: activity.liveliness, name: "Liveliness", color: colors.rose, unit: "ratio" }),
s({ metric: activity.vaultedness, name: "Vaultedness", color: colors.lime, unit: "ratio" }),
s({ metric: activity.activityToVaultednessRatio, name: "Liveliness / Vaultedness", color: colors.purple, unit: "ratio" }),
],
},
// Coinblocks
{
name: "Coinblocks",
title: "Coinblocks",
bottom: [
// Destroyed comes from the all cohort's activity
s({ metric: utxoCohorts.all.activity.coinblocksDestroyed.base, name: "Destroyed", color: colors.red, unit: "coinblocks" }),
s({ metric: utxoCohorts.all.activity.coinblocksDestroyed.cumulative, name: "Cumulative Destroyed", color: colors.red, defaultActive: false, unit: "coinblocks" }),
// Created and stored from cointime
s({ metric: activity.coinblocksCreated.base, name: "created", color: colors.orange, unit: "coinblocks" }),
s({ metric: activity.coinblocksCreated.cumulative, name: "Cumulative created", color: colors.orange, defaultActive: false, unit: "coinblocks" }),
s({ metric: activity.coinblocksStored.base, name: "stored", color: colors.green, unit: "coinblocks" }),
s({ metric: activity.coinblocksStored.cumulative, name: "Cumulative stored", color: colors.green, defaultActive: false, unit: "coinblocks" }),
],
},
// Adjusted metrics
{
name: "Adjusted",
tree: [
// Inflation
{
name: "inflation",
title: "Cointime-Adjusted inflation rate",
bottom: [
s({ metric: supply.inflation.indexes, name: "base", color: colors.orange, unit: "percentage" }),
s({ metric: adjusted.cointimeAdjInflationRate, name: "adjusted", color: colors.purple, unit: "percentage" }),
],
},
// Velocity
{
name: "Velocity",
title: "Cointime-Adjusted transactions velocity",
bottom: [
s({ metric: supply.velocity.btc, name: "btc", color: colors.orange, unit: "ratio" }),
s({ metric: adjusted.cointimeAdjTxBtcVelocity, name: "adj. btc", color: colors.red, unit: "ratio" }),
s({ metric: supply.velocity.usd, name: "usd", color: colors.emerald, unit: "ratio" }),
s({ metric: adjusted.cointimeAdjTxUsdVelocity, name: "adj. usd", color: colors.lime, unit: "ratio" }),
],
},
],
},
],
};
}

View File

@@ -1,62 +0,0 @@
/** Market section - Main entry point */
import { buildAverages, createAveragesSection } from "./averages.js";
import { createPerformanceSection } from "./performance.js";
import { createIndicatorsSection } from "./indicators/index.js";
import { createInvestingSection } from "./investing.js";
/**
* Create Market section
* @param {PartialContext} ctx
* @returns {PartialOptionsGroup}
*/
export function createMarketSection(ctx) {
const { colors, brk, s } = ctx;
const { market, supply } = brk.tree.computed;
const { movingAverage, ath, returns, volatility, range, dca, lookback, indicators } = market;
const averages = buildAverages(colors, movingAverage);
return {
name: "Market",
tree: [
// Price
{
name: "Price",
title: "Bitcoin Price",
},
// Capitalization
{
name: "Capitalization",
title: "Market Capitalization",
bottom: [s({ metric: supply.marketCap.indexes, name: "Capitalization", unit: "usd" })],
},
// All Time High
{
name: "All Time High",
title: "All Time High",
top: [s({ metric: ath.priceAth, name: "ath", unit: "usd" })],
bottom: [
s({ metric: ath.priceDrawdown, name: "Drawdown", color: colors.red, unit: "percentage" }),
s({ metric: ath.daysSincePriceAth, name: "since", unit: "days" }),
s({ metric: ath.maxDaysBetweenPriceAths, name: "Max", color: colors.red, unit: "days" }),
s({ metric: ath.maxYearsBetweenPriceAths, name: "Max", color: colors.red, unit: "years" }),
],
},
// Averages
createAveragesSection(ctx, averages),
// Performance
createPerformanceSection(ctx, returns),
// Indicators
createIndicatorsSection(ctx, { volatility, range, movingAverage, indicators }),
// Investing
createInvestingSection(ctx, { dca, lookback, returns }),
],
};
}

View File

@@ -1,43 +0,0 @@
/** Bands indicators (MinMax, Mayer Multiple) */
/**
* Create Bands section
* @param {PartialContext} ctx
* @param {Object} args
* @param {Market["range"]} args.range
* @param {Market["movingAverage"]} args.movingAverage
*/
export function createBandsSection(ctx, { range, movingAverage }) {
const { s, colors } = ctx;
return {
name: "Bands",
tree: [
{
name: "MinMax",
tree: [
{ id: "1w", title: "1 Week", min: range.price1wMin, max: range.price1wMax },
{ id: "2w", title: "2 Week", min: range.price2wMin, max: range.price2wMax },
{ id: "1m", title: "1 Month", min: range.price1mMin, max: range.price1mMax },
{ id: "1y", title: "1 Year", min: range.price1yMin, max: range.price1yMax },
].map(({ id, title, min, max }) => ({
name: id,
title: `Bitcoin Price ${title} MinMax Bands`,
top: [
s({ metric: min, name: "min", color: colors.red, unit: "usd" }),
s({ metric: max, name: "max", color: colors.green, unit: "usd" }),
],
})),
},
{
name: "Mayer Multiple",
title: "Mayer Multiple",
top: [
s({ metric: movingAverage.price200dSma.price, name: "200d sma", color: colors.yellow, unit: "usd" }),
s({ metric: movingAverage.price200dSmaX24, name: "200d sma x2.4", color: colors.green, unit: "usd" }),
s({ metric: movingAverage.price200dSmaX08, name: "200d sma x0.8", color: colors.red, unit: "usd" }),
],
},
],
};
}

View File

@@ -49,9 +49,9 @@ export function fromBlockCount(colors, pattern, title, color) {
}
/**
* Create series from a BitcoinPattern ({ base, sum, cumulative, average, min, max, median, pct* })
* Create series from a DollarsPattern ({ base, sum, cumulative, average, min, max, percentiles.* })
* @param {Colors} colors
* @param {BitcoinPattern<any>} pattern
* @param {DollarsPattern<any>} pattern
* @param {string} title
* @param {Color} [color]
* @returns {AnyFetchedSeriesBlueprint[]}
@@ -85,31 +85,31 @@ export function fromBitcoin(colors, pattern, title, color) {
defaultActive: false,
},
{
metric: pattern.median,
metric: pattern.percentiles.median,
title: "Median",
color: colors.amber,
defaultActive: false,
},
{
metric: pattern.pct75,
metric: pattern.percentiles.pct75,
title: "pct75",
color: colors.red,
defaultActive: false,
},
{
metric: pattern.pct25,
metric: pattern.percentiles.pct25,
title: "pct25",
color: colors.yellow,
defaultActive: false,
},
{
metric: pattern.pct90,
metric: pattern.percentiles.pct90,
title: "pct90",
color: colors.rose,
defaultActive: false,
},
{
metric: pattern.pct10,
metric: pattern.percentiles.pct10,
title: "pct10",
color: colors.lime,
defaultActive: false,
@@ -118,7 +118,7 @@ export function fromBitcoin(colors, pattern, title, color) {
}
/**
* Create series from a BlockSizePattern ({ sum, cumulative, average, min, max, median, pct* })
* Create series from a BlockSizePattern ({ sum, cumulative, avg, min, max, distribution.percentiles.* })
* @param {Colors} colors
* @param {BlockSizePattern<any>} pattern
* @param {string} title
@@ -148,31 +148,31 @@ export function fromBlockSize(colors, pattern, title, color) {
defaultActive: false,
},
{
metric: pattern.median,
metric: pattern.distribution.percentiles.median,
title: "Median",
color: colors.amber,
defaultActive: false,
},
{
metric: pattern.pct75,
metric: pattern.distribution.percentiles.pct75,
title: "pct75",
color: colors.red,
defaultActive: false,
},
{
metric: pattern.pct25,
metric: pattern.distribution.percentiles.pct25,
title: "pct25",
color: colors.yellow,
defaultActive: false,
},
{
metric: pattern.pct90,
metric: pattern.distribution.percentiles.pct90,
title: "pct90",
color: colors.rose,
defaultActive: false,
},
{
metric: pattern.pct10,
metric: pattern.distribution.percentiles.pct10,
title: "pct10",
color: colors.lime,
defaultActive: false,

View File

@@ -68,8 +68,8 @@
* @typedef {PartialOption & PartialChartOptionSpecific} PartialChartOption
*
* @typedef {Object} ProcessedChartOptionAddons
* @property {Record<Unit, AnyFetchedSeriesBlueprint[]>} top
* @property {Record<Unit, AnyFetchedSeriesBlueprint[]>} bottom
* @property {Map<Unit, AnyFetchedSeriesBlueprint[]>} top
* @property {Map<Unit, AnyFetchedSeriesBlueprint[]>} bottom
*
* @typedef {Required<Omit<PartialChartOption, "top" | "bottom">> & ProcessedChartOptionAddons & ProcessedOptionAddons} ChartOption
*
@@ -168,9 +168,9 @@
* @typedef {Object} PartialContext
* @property {Colors} colors
* @property {BrkClient} brk
* @property {(args: { metric: AnyMetricPattern, name: string, color?: Color, defaultActive?: boolean, unit?: Unit, options?: LineSeriesPartialOptions }) => AnyFetchedSeriesBlueprint} s
* @property {(args: { metric: AnyMetricPattern, name: string, unit: Unit, color?: Color, defaultActive?: boolean, options?: LineSeriesPartialOptions }) => AnyFetchedSeriesBlueprint} s
* @property {(pattern: BlockCountPattern<any>, title: string, color?: Color) => AnyFetchedSeriesBlueprint[]} fromBlockCount
* @property {(pattern: BitcoinPattern<any>, title: string, color?: Color) => AnyFetchedSeriesBlueprint[]} fromBitcoin
* @property {(pattern: DollarsPattern<any>, title: string, color?: Color) => AnyFetchedSeriesBlueprint[]} fromBitcoin
* @property {(pattern: BlockSizePattern<any>, title: string, color?: Color) => AnyFetchedSeriesBlueprint[]} fromBlockSize
* @property {(args: { number?: number, name?: string, defaultActive?: boolean, lineStyle?: LineStyle, color?: Color, unit: Unit }) => FetchedLineSeriesBlueprint} createPriceLine
* @property {(args: { numbers: number[], unit: Unit }) => FetchedLineSeriesBlueprint[]} createPriceLines