website: snapshot

This commit is contained in:
nym21
2026-02-03 11:03:51 +01:00
parent c02fc37491
commit 277a0eb6a7
25 changed files with 1536 additions and 1347 deletions

View File

@@ -1,6 +1,6 @@
import { ios, canShare } from "../utils/env.js";
import { style } from "../utils/elements.js";
import { colors } from "./colors.js";
import { colors } from "../utils/colors.js";
export const canCapture = !ios || canShare;
@@ -90,7 +90,11 @@ export function capture({ screenshot, chartWidth, parent, legends }) {
if (hasBottomLegend) {
drawLegend(
legends.bottom.element,
pad + titleOffset + topLegendOffset + screenshot.height + legendHeight / 2,
pad +
titleOffset +
topLegendOffset +
screenshot.height +
legendHeight / 2,
);
}
@@ -99,7 +103,11 @@ export function capture({ screenshot, chartWidth, parent, legends }) {
ctx.font = `${fontSize}px ${style.fontFamily}`;
ctx.textAlign = "right";
ctx.textBaseline = "bottom";
ctx.fillText(window.location.host, canvas.width - pad, canvas.height - pad / 2);
ctx.fillText(
window.location.host,
canvas.width - pad,
canvas.height - pad / 2,
);
// Open in new tab
canvas.toBlob((blob) => {

View File

@@ -8,7 +8,7 @@ import {
} from "../modules/lightweight-charts/5.1.0/dist/lightweight-charts.standalone.production.mjs";
import { createLegend } from "./legend.js";
import { capture } from "./capture.js";
import { colors } from "./colors.js";
import { colors } from "../utils/colors.js";
import { createRadios, createSelect } from "../utils/dom.js";
import { createPersistedValue } from "../utils/persisted.js";
import { onChange as onThemeChange } from "../utils/theme.js";

View File

@@ -0,0 +1,8 @@
import { BrkClient } from "./modules/brk-client/index.js";
// const brk = new BrkClient("https://next.bitview.space");
const brk = new BrkClient("/");
console.log(`VERSION = ${brk.VERSION}`);
export { brk };

View File

@@ -1,7 +1,6 @@
import { webSockets } from "./utils/ws.js";
import * as formatters from "./utils/format.js";
import { onFirstIntersection, getElementById, isHidden } from "./utils/dom.js";
import { BrkClient } from "./modules/brk-client/index.js";
import { initOptions } from "./options/full.js";
import {
init as initChart,
@@ -106,17 +105,12 @@ function initFrameSelectors() {
}
initFrameSelectors();
// const brk = new BrkClient("https://next.bitview.space");
const brk = new BrkClient("/");
console.log(`VERSION = ${brk.VERSION}`);
webSockets.kraken1dCandle.onLatest((latest) => {
console.log("close:", latest.close);
window.document.title = `${latest.close.toLocaleString("en-us")} | ${window.location.host}`;
});
const options = initOptions(brk);
const options = initOptions();
window.addEventListener("popstate", (_event) => {
const path = window.document.location.pathname.split("/").filter((v) => v);
@@ -158,7 +152,7 @@ function initSelected() {
element = chartElement;
if (firstTimeLoadingChart) {
initChart(brk);
initChart();
}
firstTimeLoadingChart = false;

View File

@@ -1,14 +1,14 @@
import { colors } from "../utils/colors.js";
import { brk } from "../client.js";
import { Unit } from "../utils/units.js";
import { dots, line, price } from "./series.js";
import { satsBtcUsd, createPriceRatioCharts } from "./shared.js";
/**
* Create Cointime section
* @param {PartialContext} ctx
* @returns {PartialOptionsGroup}
*/
export function createCointimeSection(ctx) {
const { colors, brk } = ctx;
export function createCointimeSection() {
const { cointime, distribution, supply } = brk.metrics;
const {
pricing,
@@ -141,8 +141,16 @@ export function createCointimeSection(ctx) {
name: "Compare",
title: "Cointime Prices",
top: [
price({ metric: all.realized.realizedPrice, name: "Realized", color: colors.orange }),
price({ metric: all.realized.investorPrice, name: "Investor", color: colors.fuchsia }),
price({
metric: all.realized.realizedPrice,
name: "Realized",
color: colors.orange,
}),
price({
metric: all.realized.investorPrice,
name: "Investor",
color: colors.fuchsia,
}),
...prices.map(({ pricePattern, name, color }) =>
price({ metric: pricePattern, name, color }),
),
@@ -150,13 +158,20 @@ export function createCointimeSection(ctx) {
},
...prices.map(({ pricePattern, ratio, name, color }) => ({
name,
tree: createPriceRatioCharts(ctx, {
tree: createPriceRatioCharts({
context: `${name} Price`,
legend: name,
pricePattern,
ratio,
color,
priceReferences: [price({ metric: all.realized.realizedPrice, name: "Realized", color: colors.orange, defaultActive: false })],
priceReferences: [
price({
metric: all.realized.realizedPrice,
name: "Realized",
color: colors.orange,
defaultActive: false,
}),
],
}),
})),
],

View File

@@ -1,175 +0,0 @@
/** Cohort color mappings */
/** @type {Readonly<Record<string, ColorName>>} */
export const termColors = {
short: "yellow",
long: "fuchsia",
};
/** @type {Readonly<Record<string, ColorName>>} */
export const maxAgeColors = {
_1w: "red",
_1m: "orange",
_2m: "amber",
_3m: "yellow",
_4m: "lime",
_5m: "green",
_6m: "teal",
_1y: "sky",
_2y: "indigo",
_3y: "violet",
_4y: "purple",
_5y: "fuchsia",
_6y: "pink",
_7y: "red",
_8y: "orange",
_10y: "amber",
_12y: "yellow",
_15y: "lime",
};
/** @type {Readonly<Record<string, ColorName>>} */
export const minAgeColors = {
_1d: "red",
_1w: "orange",
_1m: "yellow",
_2m: "lime",
_3m: "green",
_4m: "teal",
_5m: "cyan",
_6m: "blue",
_1y: "indigo",
_2y: "violet",
_3y: "purple",
_4y: "fuchsia",
_5y: "pink",
_6y: "rose",
_7y: "red",
_8y: "orange",
_10y: "yellow",
_12y: "lime",
};
/** @type {Readonly<Record<string, ColorName>>} */
export const ageRangeColors = {
upTo1d: "pink",
_1dTo1w: "red",
_1wTo1m: "orange",
_1mTo2m: "yellow",
_2mTo3m: "yellow",
_3mTo4m: "lime",
_4mTo5m: "lime",
_5mTo6m: "lime",
_6mTo1y: "green",
_1yTo2y: "cyan",
_2yTo3y: "blue",
_3yTo4y: "indigo",
_4yTo5y: "violet",
_5yTo6y: "purple",
_6yTo7y: "purple",
_7yTo8y: "fuchsia",
_8yTo10y: "fuchsia",
_10yTo12y: "pink",
_12yTo15y: "red",
from15y: "orange",
};
/** @type {Readonly<Record<string, ColorName>>} */
export const epochColors = {
_0: "red",
_1: "yellow",
_2: "orange",
_3: "lime",
_4: "green",
};
/** @type {Readonly<Record<string, ColorName>>} */
export const geAmountColors = {
_1sat: "orange",
_10sats: "orange",
_100sats: "yellow",
_1kSats: "lime",
_10kSats: "green",
_100kSats: "cyan",
_1mSats: "blue",
_10mSats: "indigo",
_1btc: "purple",
_10btc: "violet",
_100btc: "fuchsia",
_1kBtc: "pink",
_10kBtc: "red",
};
/** @type {Readonly<Record<string, ColorName>>} */
export const ltAmountColors = {
_10sats: "orange",
_100sats: "yellow",
_1kSats: "lime",
_10kSats: "green",
_100kSats: "cyan",
_1mSats: "blue",
_10mSats: "indigo",
_1btc: "purple",
_10btc: "violet",
_100btc: "fuchsia",
_1kBtc: "pink",
_10kBtc: "red",
_100kBtc: "orange",
};
/** @type {Readonly<Record<string, ColorName>>} */
export const amountRangeColors = {
_0sats: "red",
_1satTo10sats: "orange",
_10satsTo100sats: "yellow",
_100satsTo1kSats: "lime",
_1kSatsTo10kSats: "green",
_10kSatsTo100kSats: "cyan",
_100kSatsTo1mSats: "blue",
_1mSatsTo10mSats: "indigo",
_10mSatsTo1btc: "purple",
_1btcTo10btc: "violet",
_10btcTo100btc: "fuchsia",
_100btcTo1kBtc: "pink",
_1kBtcTo10kBtc: "red",
_10kBtcTo100kBtc: "orange",
_100kBtcOrMore: "yellow",
};
/** @type {Readonly<Record<string, ColorName>>} */
export const spendableTypeColors = {
p2pk65: "red",
p2pk33: "orange",
p2pkh: "yellow",
p2ms: "lime",
p2sh: "green",
p2wpkh: "teal",
p2wsh: "blue",
p2tr: "indigo",
p2a: "purple",
opreturn: "pink",
unknown: "violet",
empty: "fuchsia",
};
/** @type {Readonly<Record<string, ColorName>>} */
export const yearColors = {
_2009: "red",
_2010: "orange",
_2011: "amber",
_2012: "yellow",
_2013: "lime",
_2014: "green",
_2015: "teal",
_2016: "cyan",
_2017: "sky",
_2018: "blue",
_2019: "indigo",
_2020: "violet",
_2021: "purple",
_2022: "fuchsia",
_2023: "pink",
_2024: "rose",
_2025: "red",
_2026: "orange",
};

View File

@@ -1,15 +0,0 @@
// Re-export all color mappings
export {
termColors,
maxAgeColors,
minAgeColors,
ageRangeColors,
epochColors,
geAmountColors,
ltAmountColors,
amountRangeColors,
spendableTypeColors,
yearColors,
} from "./cohorts.js";
export { averageColors, dcaColors } from "./misc.js";

View File

@@ -1,42 +0,0 @@
/** Miscellaneous color mappings for DCA and averages */
/**
* Moving average period colors
* Format: [periodId, days, colorName]
* @type {readonly [string, number, ColorName][]}
*/
export const averageColors = [
["1w", 7, "red"],
["8d", 8, "orange"],
["13d", 13, "amber"],
["21d", 21, "yellow"],
["1m", 30, "lime"],
["34d", 34, "green"],
["55d", 55, "emerald"],
["89d", 89, "teal"],
["144d", 144, "cyan"],
["200d", 200, "sky"],
["1y", 365, "blue"],
["2y", 730, "indigo"],
["200w", 1400, "violet"],
["4y", 1460, "purple"],
];
/**
* DCA class colors by year
* Format: [year, colorName, defaultActive]
* @type {readonly [number, ColorName, boolean][]}
*/
export const dcaColors = [
[2015, "pink", false],
[2016, "red", false],
[2017, "orange", true],
[2018, "yellow", true],
[2019, "green", true],
[2020, "teal", true],
[2021, "sky", true],
[2022, "blue", true],
[2023, "purple", true],
[2024, "fuchsia", true],
[2025, "pink", true],
];

View File

@@ -1,5 +1,7 @@
/** Constant helpers for creating price lines and reference lines */
import { colors } from "../utils/colors.js";
import { brk } from "../client.js";
import { line } from "./series.js";
/**
@@ -23,14 +25,14 @@ export function getConstant(constants, num) {
/**
* Create a price line series (horizontal reference line)
* @param {{ ctx: PartialContext, number?: number, name?: string } & Omit<(Parameters<typeof line>)[0], 'name' | 'metric'>} args
* @param {{ number?: number, name?: string } & Omit<(Parameters<typeof line>)[0], 'name' | 'metric'>} args
*/
export function priceLine(args) {
return line({
...args,
metric: getConstant(args.ctx.brk.metrics.constants, args.number || 0),
metric: getConstant(brk.metrics.constants, args.number || 0),
name: args.name || `${args.number ?? 0}`,
color: args.color ?? args.ctx.colors.gray,
color: args.color ?? colors.gray,
options: {
lineStyle: args.style ?? 4,
lastValueVisible: false,

View File

@@ -1,45 +0,0 @@
import {
fromBaseStatsPattern,
fromStatsPattern,
chartsFromFull,
chartsFromSum,
chartsFromValueFull,
} from "./series.js";
import { colors } from "../chart/colors.js";
/**
* @template {(arg: any, ...args: any[]) => any} F
* @typedef {F extends (arg: any, ...args: infer P) => infer R ? (...args: P) => R : never} OmitFirstArg
*/
/** @typedef {ReturnType<typeof createContext>} PartialContext */
/**
* @template {(colors: Colors, ...args: any[]) => any} T
* @param {T} fn
* @returns {OmitFirstArg<T>}
*/
const bind = (fn) =>
/** @type {any} */ (
// @ts-ignore
(...args) => fn(colors, ...args)
);
/**
* Create a context object with all dependencies for building partial options
* @param {Object} args
* @param {BrkClient} args.brk
*/
export function createContext({ brk }) {
return {
colors,
brk,
// Series helpers (return series arrays for a single chart)
fromBaseStatsPattern: bind(fromBaseStatsPattern),
fromStatsPattern: bind(fromStatsPattern),
// Chart helpers (return chart trees for Sum/Distribution/Cumulative folders)
chartsFromFull: bind(chartsFromFull),
chartsFromSum: bind(chartsFromSum),
chartsFromValueFull: bind(chartsFromValueFull),
};
}

View File

@@ -4,6 +4,7 @@
* Address cohorts use _0satsPattern which has CostBasisPattern (no percentiles)
*/
import { colors } from "../../utils/colors.js";
import { Unit } from "../../utils/units.js";
import { priceLine } from "../constants.js";
import { line, baseline, price } from "../series.js";
@@ -35,11 +36,10 @@ import {
/**
* Create a cohort folder for address cohorts
* Includes address count section (addrCount exists on AddressCohortObject)
* @param {PartialContext} ctx
* @param {AddressCohortObject | AddressCohortGroupObject} args
* @returns {PartialOptionsGroup}
*/
export function createAddressCohortFolder(ctx, args) {
export function createAddressCohortFolder(args) {
const list = "list" in args ? args.list : [args];
const useGroupName = "list" in args;
const isSingle = !("list" in args);
@@ -55,10 +55,8 @@ export function createAddressCohortFolder(ctx, args) {
name: "Supply",
title: title("Supply"),
bottom: createSingleSupplySeries(
ctx,
/** @type {AddressCohortObject} */ (args),
createSingleSupplyRelativeOptions(
ctx,
/** @type {AddressCohortObject} */ (args),
),
),
@@ -80,7 +78,7 @@ export function createAddressCohortFolder(ctx, args) {
{
name: "Address Count",
title: title("Address Count"),
bottom: createAddressCountSeries(ctx, list, useGroupName),
bottom: createAddressCountSeries(list, useGroupName),
},
// Realized section
@@ -122,7 +120,7 @@ export function createAddressCohortFolder(ctx, args) {
{
name: "Capitalization",
title: title("Realized Cap"),
bottom: createRealizedCapWithExtras(ctx, list, args, useGroupName),
bottom: createRealizedCapWithExtras(list, args, useGroupName),
},
{
name: "Value",
@@ -137,9 +135,8 @@ export function createAddressCohortFolder(ctx, args) {
),
},
...(useGroupName
? createGroupedRealizedPnlSection(ctx, list, title)
? createGroupedRealizedPnlSection(list, title)
: createRealizedPnlSection(
ctx,
/** @type {AddressCohortObject} */ (args),
title,
)),
@@ -147,7 +144,7 @@ export function createAddressCohortFolder(ctx, args) {
},
// Unrealized section
...createUnrealizedSection(ctx, list, useGroupName, title),
...createUnrealizedSection(list, useGroupName, title),
// Cost basis section (no percentiles for address cohorts)
...createCostBasisSection(list, useGroupName, title),
@@ -199,13 +196,12 @@ function createRealizedPriceOptions(args, title) {
/**
* Create realized cap with extras
* @param {PartialContext} ctx
* @param {readonly AddressCohortObject[]} list
* @param {AddressCohortObject | AddressCohortGroupObject} args
* @param {boolean} useGroupName
* @returns {AnyFetchedSeriesBlueprint[]}
*/
function createRealizedCapWithExtras(ctx, list, args, useGroupName) {
function createRealizedCapWithExtras(list, args, useGroupName) {
const isSingle = !("list" in args);
return list.flatMap(({ color, name, tree }) => [
@@ -231,13 +227,11 @@ function createRealizedCapWithExtras(ctx, list, args, useGroupName) {
/**
* Create realized PnL section for single cohort
* @param {PartialContext} ctx
* @param {AddressCohortObject} args
* @param {(metric: string) => string} title
* @returns {PartialOptionsTree}
*/
function createRealizedPnlSection(ctx, args, title) {
const { colors } = ctx;
function createRealizedPnlSection(args, title) {
const { realized } = args.tree;
return [
@@ -381,16 +375,13 @@ function createRealizedPnlSection(ctx, args, title) {
unit: Unit.pctMcap,
}),
priceLine({
ctx,
unit: Unit.usd,
number: 1,
}),
priceLine({
ctx,
unit: Unit.pctMcap,
}),
priceLine({
ctx,
unit: Unit.pctRcap,
}),
],
@@ -401,7 +392,6 @@ function createRealizedPnlSection(ctx, args, title) {
bottom: [
...createSingleSoprSeries(colors, args.tree),
priceLine({
ctx,
unit: Unit.ratio,
number: 1,
}),
@@ -575,12 +565,11 @@ function createRealizedPnlSection(ctx, args, title) {
/**
* Create grouped realized P&L section for address cohorts (for compare view)
* @param {PartialContext} ctx
* @param {readonly AddressCohortObject[]} list
* @param {(metric: string) => string} title
* @returns {PartialOptionsTree}
*/
function createGroupedRealizedPnlSection(ctx, list, title) {
function createGroupedRealizedPnlSection(list, title) {
const pnlConfigs = /** @type {const} */ ([
{
name: "Profit",
@@ -814,15 +803,12 @@ function createGroupedRealizedPnlSection(ctx, list, title) {
/**
* Create unrealized section
* @param {PartialContext} ctx
* @param {readonly AddressCohortObject[]} list
* @param {boolean} useGroupName
* @param {(metric: string) => string} title
* @returns {PartialOptionsTree}
*/
function createUnrealizedSection(ctx, list, useGroupName, title) {
const { colors } = ctx;
function createUnrealizedSection(list, useGroupName, title) {
return [
{
name: "Unrealized",
@@ -1005,7 +991,6 @@ function createUnrealizedSection(ctx, list, useGroupName, title) {
unit: Unit.ratio,
}),
priceLine({
ctx,
unit: Unit.ratio,
}),
]),
@@ -1021,7 +1006,6 @@ function createUnrealizedSection(ctx, list, useGroupName, title) {
unit: Unit.usd,
}),
priceLine({
ctx,
unit: Unit.usd,
}),
]),

View File

@@ -1,18 +1,8 @@
/** Build cohort data arrays from brk.metrics */
import {
termColors,
maxAgeColors,
minAgeColors,
ageRangeColors,
epochColors,
geAmountColors,
ltAmountColors,
amountRangeColors,
spendableTypeColors,
yearColors,
} from "../colors/index.js";
import { colors } from "../../utils/colors.js";
import { entries } from "../../utils/array.js";
import { brk } from "../../client.js";
/** @type {readonly AddressableType[]} */
const ADDRESSABLE_TYPES = [
@@ -32,10 +22,8 @@ const isAddressable = (key) =>
/**
* Build all cohort data from brk tree
* @param {Colors} colors
* @param {BrkClient} brk
*/
export function buildCohortData(colors, brk) {
export function buildCohortData() {
const utxoCohorts = brk.metrics.distribution.utxoCohorts;
const addressCohorts = brk.metrics.distribution.addressCohorts;
const { addrCount } = brk.metrics.distribution;
@@ -66,7 +54,7 @@ export function buildCohortData(colors, brk) {
const termShort = {
name: shortNames.short,
title: shortNames.long,
color: colors[termColors.short],
color: colors.term.short,
tree: utxoCohorts.term.short,
};
@@ -74,7 +62,7 @@ export function buildCohortData(colors, brk) {
const termLong = {
name: longNames.short,
title: longNames.long,
color: colors[termColors.long],
color: colors.term.long,
tree: utxoCohorts.term.long,
};
@@ -84,7 +72,7 @@ export function buildCohortData(colors, brk) {
return {
name: names.short,
title: `UTXOs ${names.long}`,
color: colors[maxAgeColors[key]],
color: colors.age[key],
tree,
};
});
@@ -95,7 +83,7 @@ export function buildCohortData(colors, brk) {
return {
name: names.short,
title: `UTXOs ${names.long}`,
color: colors[minAgeColors[key]],
color: colors.age[key],
tree,
};
});
@@ -106,7 +94,7 @@ export function buildCohortData(colors, brk) {
return {
name: names.short,
title: `UTXOs ${names.long}`,
color: colors[ageRangeColors[key]],
color: colors.ageRange[key],
tree,
};
});
@@ -117,7 +105,7 @@ export function buildCohortData(colors, brk) {
return {
name: names.short,
title: names.long,
color: colors[epochColors[key]],
color: colors.epoch[key],
tree,
};
});
@@ -128,7 +116,7 @@ export function buildCohortData(colors, brk) {
return {
name: names.short,
title: `UTXOs ${names.long}`,
color: colors[geAmountColors[key]],
color: colors.amount[key],
tree,
};
});
@@ -140,7 +128,7 @@ export function buildCohortData(colors, brk) {
return {
name: names.short,
title: `Addresses ${names.long}`,
color: colors[geAmountColors[key]],
color: colors.amount[key],
tree,
};
},
@@ -152,7 +140,7 @@ export function buildCohortData(colors, brk) {
return {
name: names.short,
title: `UTXOs ${names.long}`,
color: colors[ltAmountColors[key]],
color: colors.amount[key],
tree,
};
});
@@ -164,7 +152,7 @@ export function buildCohortData(colors, brk) {
return {
name: names.short,
title: `Addresses ${names.long}`,
color: colors[ltAmountColors[key]],
color: colors.amount[key],
tree,
};
},
@@ -177,7 +165,7 @@ export function buildCohortData(colors, brk) {
return {
name: names.short,
title: `UTXOs ${names.long}`,
color: colors[amountRangeColors[key]],
color: colors.amountRange[key],
tree,
};
},
@@ -190,7 +178,7 @@ export function buildCohortData(colors, brk) {
return {
name: names.short,
title: `Addresses ${names.long}`,
color: colors[amountRangeColors[key]],
color: colors.amountRange[key],
tree,
};
},
@@ -202,7 +190,7 @@ export function buildCohortData(colors, brk) {
return {
name: names.short,
title: names.short,
color: colors[spendableTypeColors[key]],
color: colors.scriptType[key],
tree: utxoCohorts.type[key],
addrCount: addrCount[key],
};
@@ -215,7 +203,7 @@ export function buildCohortData(colors, brk) {
return {
name: names.short,
title: names.short,
color: colors[spendableTypeColors[key]],
color: colors.scriptType[key],
tree,
};
});
@@ -226,7 +214,7 @@ export function buildCohortData(colors, brk) {
return {
name: names.short,
title: names.long,
color: colors[yearColors[key]],
color: colors.year[key],
tree,
};
});

View File

@@ -1,9 +1,14 @@
/** Shared cohort chart section builders */
import { colors } from "../../utils/colors.js";
import { Unit } from "../../utils/units.js";
import { priceLine } from "../constants.js";
import { baseline, dots, line, price } from "../series.js";
import { satsBtcUsd, createPriceRatioCharts, formatCohortTitle } from "../shared.js";
import {
satsBtcUsd,
createPriceRatioCharts,
formatCohortTitle,
} from "../shared.js";
// ============================================================================
// Generic Price Helpers
@@ -12,15 +17,20 @@ import { satsBtcUsd, createPriceRatioCharts, formatCohortTitle } from "../shared
/**
* Create price folder (price + ratio + z-scores wrapped in folder)
* For cohorts with full extended ratio metrics (ActivePriceRatioPattern)
* @param {PartialContext} ctx
* @param {{ name: string, cohortTitle?: string, priceMetric: ActivePricePattern, ratioPattern: AnyRatioPattern, color: Color }} args
* @returns {PartialOptionsGroup}
*/
export function createPriceFolder(ctx, { name, cohortTitle, priceMetric, ratioPattern, color }) {
export function createPriceFolder({
name,
cohortTitle,
priceMetric,
ratioPattern,
color,
}) {
const context = cohortTitle ? `${cohortTitle} ${name}` : name;
return {
name,
tree: createPriceRatioCharts(ctx, {
tree: createPriceRatioCharts({
context,
legend: name,
pricePattern: priceMetric,
@@ -37,7 +47,13 @@ export function createPriceFolder(ctx, { name, cohortTitle, priceMetric, ratioPa
* @param {{ name: string, context: string, priceMetric: ActivePricePattern, ratioMetric: R, color: Color }} args
* @returns {PartialOptionsTree}
*/
export function createBasicPriceCharts({ name, context, priceMetric, ratioMetric, color }) {
export function createBasicPriceCharts({
name,
context,
priceMetric,
ratioMetric,
color,
}) {
return [
{
name: "Price",
@@ -67,11 +83,23 @@ export function createBasicPriceCharts({ name, context, priceMetric, ratioMetric
* @param {{ name: string, cohortTitle?: string, priceMetric: ActivePricePattern, ratioMetric: R, color: Color }} args
* @returns {PartialOptionsGroup}
*/
export function createBasicPriceFolder({ name, cohortTitle, priceMetric, ratioMetric, color }) {
export function createBasicPriceFolder({
name,
cohortTitle,
priceMetric,
ratioMetric,
color,
}) {
const context = cohortTitle ? `${cohortTitle} ${name}` : name;
return {
name,
tree: createBasicPriceCharts({ name, context, priceMetric, ratioMetric, color }),
tree: createBasicPriceCharts({
name,
context,
priceMetric,
ratioMetric,
color,
}),
};
}
@@ -81,7 +109,13 @@ export function createBasicPriceFolder({ name, cohortTitle, priceMetric, ratioMe
* @param {{ name: string, title: (metric: string) => string, list: readonly T[], getPrice: (tree: T['tree']) => ActivePricePattern, getRatio: (tree: T['tree']) => AnyMetricPattern }} args
* @returns {PartialOptionsTree}
*/
export function createGroupedPriceCharts({ name, title, list, getPrice, getRatio }) {
export function createGroupedPriceCharts({
name,
title,
list,
getPrice,
getRatio,
}) {
return [
{
name: "Price",
@@ -94,7 +128,13 @@ export function createGroupedPriceCharts({ name, title, list, getPrice, getRatio
name: "Ratio",
title: title(`${name} Ratio`),
bottom: list.map(({ color, name: cohortName, tree }) =>
baseline({ metric: getRatio(tree), name: cohortName, color, unit: Unit.ratio, base: 1 }),
baseline({
metric: getRatio(tree),
name: cohortName,
color,
unit: Unit.ratio,
base: 1,
}),
),
},
];
@@ -106,7 +146,13 @@ export function createGroupedPriceCharts({ name, title, list, getPrice, getRatio
* @param {{ name: string, title: (metric: string) => string, list: readonly T[], getPrice: (tree: T['tree']) => ActivePricePattern, getRatio: (tree: T['tree']) => AnyMetricPattern }} args
* @returns {PartialOptionsGroup}
*/
export function createGroupedPriceFolder({ name, title, list, getPrice, getRatio }) {
export function createGroupedPriceFolder({
name,
title,
list,
getPrice,
getRatio,
}) {
return {
name,
tree: createGroupedPriceCharts({ name, title, list, getPrice, getRatio }),
@@ -115,20 +161,38 @@ export function createGroupedPriceFolder({ name, title, list, getPrice, getRatio
/**
* Create base supply series (without relative metrics)
* @param {PartialContext} ctx
* @param {CohortObject | CohortWithoutRelative} cohort
* @returns {AnyFetchedSeriesBlueprint[]}
*/
function createSingleSupplySeriesBase(ctx, cohort) {
const { colors } = ctx;
function createSingleSupplySeriesBase(cohort) {
const { tree } = cohort;
return [
...satsBtcUsd({ pattern: tree.supply.total, name: "Supply", color: colors.default }),
...satsBtcUsd({ pattern: tree.supply._30dChange, name: "30d Change", color: colors.orange }),
...satsBtcUsd({ pattern: tree.unrealized.supplyInProfit, name: "In Profit", color: colors.green }),
...satsBtcUsd({ pattern: tree.unrealized.supplyInLoss, name: "In Loss", color: colors.red }),
...satsBtcUsd({ pattern: tree.supply.halved, name: "half", color: colors.gray }).map((s) => ({
...satsBtcUsd({
pattern: tree.supply.total,
name: "Supply",
color: colors.default,
}),
...satsBtcUsd({
pattern: tree.supply._30dChange,
name: "30d Change",
color: colors.orange,
}),
...satsBtcUsd({
pattern: tree.unrealized.supplyInProfit,
name: "In Profit",
color: colors.green,
}),
...satsBtcUsd({
pattern: tree.unrealized.supplyInLoss,
name: "In Loss",
color: colors.red,
}),
...satsBtcUsd({
pattern: tree.supply.halved,
name: "half",
color: colors.gray,
}).map((s) => ({
...s,
options: { lineStyle: 4 },
})),
@@ -137,12 +201,10 @@ function createSingleSupplySeriesBase(ctx, cohort) {
/**
* Create supply relative to own supply metrics
* @param {PartialContext} ctx
* @param {UtxoCohortObject | AddressCohortObject} cohort
* @returns {AnyFetchedSeriesBlueprint[]}
*/
function createSingleSupplyRelativeToOwnMetrics(ctx, cohort) {
const { colors } = ctx;
function createSingleSupplyRelativeToOwnMetrics(cohort) {
const { tree } = cohort;
return [
@@ -159,42 +221,42 @@ function createSingleSupplyRelativeToOwnMetrics(ctx, cohort) {
unit: Unit.pctOwn,
}),
priceLine({
ctx,
unit: Unit.pctOwn,
number: 100,
style: 0,
color: colors.default,
}),
priceLine({ ctx, unit: Unit.pctOwn, number: 50 }),
priceLine({ unit: Unit.pctOwn, number: 50 }),
];
}
/**
* Create supply section for a single cohort (with relative metrics)
* @param {PartialContext} ctx
* @param {UtxoCohortObject | AddressCohortObject} cohort
* @param {Object} [options]
* @param {AnyFetchedSeriesBlueprint[]} [options.supplyRelative] - Supply relative to circulating supply metrics
* @param {AnyFetchedSeriesBlueprint[]} [options.pnlRelative] - Supply in profit/loss relative to circulating supply metrics
* @returns {AnyFetchedSeriesBlueprint[]}
*/
export function createSingleSupplySeries(ctx, cohort, { supplyRelative = [], pnlRelative = [] } = {}) {
export function createSingleSupplySeries(
cohort,
{ supplyRelative = [], pnlRelative = [] } = {},
) {
return [
...createSingleSupplySeriesBase(ctx, cohort),
...createSingleSupplySeriesBase(cohort),
...supplyRelative,
...pnlRelative,
...createSingleSupplyRelativeToOwnMetrics(ctx, cohort),
...createSingleSupplyRelativeToOwnMetrics(cohort),
];
}
/**
* Create supply series for cohorts WITHOUT relative metrics
* @param {PartialContext} ctx
* @param {CohortWithoutRelative} cohort
* @returns {AnyFetchedSeriesBlueprint[]}
*/
export function createSingleSupplySeriesWithoutRelative(ctx, cohort) {
return createSingleSupplySeriesBase(ctx, cohort);
export function createSingleSupplySeriesWithoutRelative(cohort) {
return createSingleSupplySeriesBase(cohort);
}
/**
@@ -207,7 +269,11 @@ export function createSingleSupplySeriesWithoutRelative(ctx, cohort) {
*/
export function createGroupedSupplyTotalSeries(list, { relativeMetrics } = {}) {
return list.flatMap((cohort) => [
...satsBtcUsd({ pattern: cohort.tree.supply.total, name: cohort.name, color: cohort.color }),
...satsBtcUsd({
pattern: cohort.tree.supply.total,
name: cohort.name,
color: cohort.color,
}),
...(relativeMetrics ? relativeMetrics(cohort) : []),
]);
}
@@ -220,9 +286,16 @@ export function createGroupedSupplyTotalSeries(list, { relativeMetrics } = {}) {
* @param {(cohort: T[number]) => AnyFetchedSeriesBlueprint[]} [options.relativeMetrics] - Generator for relative supply metrics
* @returns {AnyFetchedSeriesBlueprint[]}
*/
export function createGroupedSupplyInProfitSeries(list, { relativeMetrics } = {}) {
export function createGroupedSupplyInProfitSeries(
list,
{ relativeMetrics } = {},
) {
return list.flatMap((cohort) => [
...satsBtcUsd({ pattern: cohort.tree.unrealized.supplyInProfit, name: cohort.name, color: cohort.color }),
...satsBtcUsd({
pattern: cohort.tree.unrealized.supplyInProfit,
name: cohort.name,
color: cohort.color,
}),
...(relativeMetrics ? relativeMetrics(cohort) : []),
]);
}
@@ -235,9 +308,16 @@ export function createGroupedSupplyInProfitSeries(list, { relativeMetrics } = {}
* @param {(cohort: T[number]) => AnyFetchedSeriesBlueprint[]} [options.relativeMetrics] - Generator for relative supply metrics
* @returns {AnyFetchedSeriesBlueprint[]}
*/
export function createGroupedSupplyInLossSeries(list, { relativeMetrics } = {}) {
export function createGroupedSupplyInLossSeries(
list,
{ relativeMetrics } = {},
) {
return list.flatMap((cohort) => [
...satsBtcUsd({ pattern: cohort.tree.unrealized.supplyInLoss, name: cohort.name, color: cohort.color }),
...satsBtcUsd({
pattern: cohort.tree.unrealized.supplyInLoss,
name: cohort.name,
color: cohort.color,
}),
...(relativeMetrics ? relativeMetrics(cohort) : []),
]);
}
@@ -253,14 +333,20 @@ export function createGroupedSupplyInLossSeries(list, { relativeMetrics } = {})
* @param {(cohort: T[number]) => AnyFetchedSeriesBlueprint[]} [options.lossRelativeMetrics] - Generator for supply in loss relative metrics
* @returns {PartialOptionsGroup}
*/
export function createGroupedSupplySection(list, title, { supplyRelativeMetrics, profitRelativeMetrics, lossRelativeMetrics } = {}) {
export function createGroupedSupplySection(
list,
title,
{ supplyRelativeMetrics, profitRelativeMetrics, lossRelativeMetrics } = {},
) {
return {
name: "Supply",
tree: [
{
name: "Total",
title: title("Supply"),
bottom: createGroupedSupplyTotalSeries(list, { relativeMetrics: supplyRelativeMetrics }),
bottom: createGroupedSupplyTotalSeries(list, {
relativeMetrics: supplyRelativeMetrics,
}),
},
{
name: "30d Change",
@@ -272,12 +358,16 @@ export function createGroupedSupplySection(list, title, { supplyRelativeMetrics,
{
name: "In Profit",
title: title("Supply In Profit"),
bottom: createGroupedSupplyInProfitSeries(list, { relativeMetrics: profitRelativeMetrics }),
bottom: createGroupedSupplyInProfitSeries(list, {
relativeMetrics: profitRelativeMetrics,
}),
},
{
name: "In Loss",
title: title("Supply In Loss"),
bottom: createGroupedSupplyInLossSeries(list, { relativeMetrics: lossRelativeMetrics }),
bottom: createGroupedSupplyInLossSeries(list, {
relativeMetrics: lossRelativeMetrics,
}),
},
],
};
@@ -289,16 +379,15 @@ export function createGroupedSupplySection(list, title, { supplyRelativeMetrics,
/**
* Create supply relative to circulating supply series for single cohort
* @param {PartialContext} ctx
* @param {CohortWithCirculatingSupplyRelative} cohort
* @returns {AnyFetchedSeriesBlueprint[]}
*/
export function createSupplyRelativeToCirculatingSeries(ctx, cohort) {
export function createSupplyRelativeToCirculatingSeries(cohort) {
return [
line({
metric: cohort.tree.relative.supplyRelToCirculatingSupply,
name: "Supply",
color: ctx.colors.default,
color: colors.default,
unit: Unit.pctSupply,
}),
];
@@ -306,22 +395,21 @@ export function createSupplyRelativeToCirculatingSeries(ctx, cohort) {
/**
* Create supply in profit/loss relative to circulating supply series for single cohort
* @param {PartialContext} ctx
* @param {CohortWithCirculatingSupplyRelative} cohort
* @returns {AnyFetchedSeriesBlueprint[]}
*/
export function createSupplyPnlRelativeToCirculatingSeries(ctx, cohort) {
export function createSupplyPnlRelativeToCirculatingSeries(cohort) {
return [
line({
metric: cohort.tree.relative.supplyInProfitRelToCirculatingSupply,
name: "In Profit",
color: ctx.colors.green,
color: colors.green,
unit: Unit.pctSupply,
}),
line({
metric: cohort.tree.relative.supplyInLossRelToCirculatingSupply,
name: "In Loss",
color: ctx.colors.red,
color: colors.red,
unit: Unit.pctSupply,
}),
];
@@ -387,14 +475,13 @@ export const groupedSupplyRelativeGenerators = {
/**
* Create single cohort supply relative options for cohorts with circulating supply relative
* @param {PartialContext} ctx
* @param {CohortWithCirculatingSupplyRelative} cohort
* @returns {{ supplyRelative: AnyFetchedSeriesBlueprint[], pnlRelative: AnyFetchedSeriesBlueprint[] }}
*/
export function createSingleSupplyRelativeOptions(ctx, cohort) {
export function createSingleSupplyRelativeOptions(cohort) {
return {
supplyRelative: createSupplyRelativeToCirculatingSeries(ctx, cohort),
pnlRelative: createSupplyPnlRelativeToCirculatingSeries(ctx, cohort),
supplyRelative: createSupplyRelativeToCirculatingSeries(cohort),
pnlRelative: createSupplyPnlRelativeToCirculatingSeries(cohort),
};
}
@@ -417,14 +504,11 @@ export function createUtxoCountSeries(list, useGroupName) {
/**
* Create address count series (for address cohorts only)
* @param {PartialContext} ctx
* @param {readonly AddressCohortObject[]} list
* @param {boolean} useGroupName
* @returns {AnyFetchedSeriesBlueprint[]}
*/
export function createAddressCountSeries(ctx, list, useGroupName) {
const { colors } = ctx;
export function createAddressCountSeries(list, useGroupName) {
return list.flatMap(({ color, name, tree }) => [
line({
metric: tree.addrCount,
@@ -491,29 +575,130 @@ export function createCostBasisPercentilesSeries(colors, list, useGroupName) {
return list.flatMap(({ name, tree }) => {
const cb = tree.costBasis;
const p = cb.percentiles;
const n = (/** @type {number} */ pct) => (useGroupName ? `${name} p${pct}` : `p${pct}`);
const n = (/** @type {number} */ pct) =>
useGroupName ? `${name} p${pct}` : `p${pct}`;
return [
price({ metric: cb.max, name: n(100), color: colors.purple, defaultActive: false }),
price({ metric: p.pct95, name: n(95), color: colors.fuchsia, defaultActive: false }),
price({ metric: p.pct90, name: n(90), color: colors.pink, defaultActive: false }),
price({ metric: p.pct85, name: n(85), color: colors.pink, defaultActive: false }),
price({ metric: p.pct80, name: n(80), color: colors.rose, defaultActive: false }),
price({ metric: p.pct75, name: n(75), color: colors.red, defaultActive: false }),
price({ metric: p.pct70, name: n(70), color: colors.orange, defaultActive: false }),
price({ metric: p.pct65, name: n(65), color: colors.amber, defaultActive: false }),
price({ metric: p.pct60, name: n(60), color: colors.yellow, defaultActive: false }),
price({ metric: p.pct55, name: n(55), color: colors.yellow, defaultActive: false }),
price({
metric: cb.max,
name: n(100),
color: colors.purple,
defaultActive: false,
}),
price({
metric: p.pct95,
name: n(95),
color: colors.fuchsia,
defaultActive: false,
}),
price({
metric: p.pct90,
name: n(90),
color: colors.pink,
defaultActive: false,
}),
price({
metric: p.pct85,
name: n(85),
color: colors.pink,
defaultActive: false,
}),
price({
metric: p.pct80,
name: n(80),
color: colors.rose,
defaultActive: false,
}),
price({
metric: p.pct75,
name: n(75),
color: colors.red,
defaultActive: false,
}),
price({
metric: p.pct70,
name: n(70),
color: colors.orange,
defaultActive: false,
}),
price({
metric: p.pct65,
name: n(65),
color: colors.amber,
defaultActive: false,
}),
price({
metric: p.pct60,
name: n(60),
color: colors.yellow,
defaultActive: false,
}),
price({
metric: p.pct55,
name: n(55),
color: colors.yellow,
defaultActive: false,
}),
price({ metric: p.pct50, name: n(50), color: colors.avocado }),
price({ metric: p.pct45, name: n(45), color: colors.lime, defaultActive: false }),
price({ metric: p.pct40, name: n(40), color: colors.green, defaultActive: false }),
price({ metric: p.pct35, name: n(35), color: colors.emerald, defaultActive: false }),
price({ metric: p.pct30, name: n(30), color: colors.teal, defaultActive: false }),
price({ metric: p.pct25, name: n(25), color: colors.teal, defaultActive: false }),
price({ metric: p.pct20, name: n(20), color: colors.cyan, defaultActive: false }),
price({ metric: p.pct15, name: n(15), color: colors.sky, defaultActive: false }),
price({ metric: p.pct10, name: n(10), color: colors.blue, defaultActive: false }),
price({ metric: p.pct05, name: n(5), color: colors.indigo, defaultActive: false }),
price({ metric: cb.min, name: n(0), color: colors.violet, defaultActive: false }),
price({
metric: p.pct45,
name: n(45),
color: colors.lime,
defaultActive: false,
}),
price({
metric: p.pct40,
name: n(40),
color: colors.green,
defaultActive: false,
}),
price({
metric: p.pct35,
name: n(35),
color: colors.emerald,
defaultActive: false,
}),
price({
metric: p.pct30,
name: n(30),
color: colors.teal,
defaultActive: false,
}),
price({
metric: p.pct25,
name: n(25),
color: colors.teal,
defaultActive: false,
}),
price({
metric: p.pct20,
name: n(20),
color: colors.cyan,
defaultActive: false,
}),
price({
metric: p.pct15,
name: n(15),
color: colors.sky,
defaultActive: false,
}),
price({
metric: p.pct10,
name: n(10),
color: colors.blue,
defaultActive: false,
}),
price({
metric: p.pct05,
name: n(5),
color: colors.indigo,
defaultActive: false,
}),
price({
metric: cb.min,
name: n(0),
color: colors.violet,
defaultActive: false,
}),
];
});
}
@@ -526,30 +711,125 @@ export function createCostBasisPercentilesSeries(colors, list, useGroupName) {
* @param {boolean} useGroupName
* @returns {FetchedPriceSeriesBlueprint[]}
*/
export function createInvestedCapitalPercentilesSeries(colors, list, useGroupName) {
export function createInvestedCapitalPercentilesSeries(
colors,
list,
useGroupName,
) {
return list.flatMap(({ name, tree }) => {
const ic = tree.costBasis.investedCapital;
const n = (/** @type {number} */ pct) => (useGroupName ? `${name} p${pct}` : `p${pct}`);
const n = (/** @type {number} */ pct) =>
useGroupName ? `${name} p${pct}` : `p${pct}`;
return [
price({ metric: ic.pct95, name: n(95), color: colors.fuchsia, defaultActive: false }),
price({ metric: ic.pct90, name: n(90), color: colors.pink, defaultActive: false }),
price({ metric: ic.pct85, name: n(85), color: colors.pink, defaultActive: false }),
price({ metric: ic.pct80, name: n(80), color: colors.rose, defaultActive: false }),
price({ metric: ic.pct75, name: n(75), color: colors.red, defaultActive: false }),
price({ metric: ic.pct70, name: n(70), color: colors.orange, defaultActive: false }),
price({ metric: ic.pct65, name: n(65), color: colors.amber, defaultActive: false }),
price({ metric: ic.pct60, name: n(60), color: colors.yellow, defaultActive: false }),
price({ metric: ic.pct55, name: n(55), color: colors.yellow, defaultActive: false }),
price({
metric: ic.pct95,
name: n(95),
color: colors.fuchsia,
defaultActive: false,
}),
price({
metric: ic.pct90,
name: n(90),
color: colors.pink,
defaultActive: false,
}),
price({
metric: ic.pct85,
name: n(85),
color: colors.pink,
defaultActive: false,
}),
price({
metric: ic.pct80,
name: n(80),
color: colors.rose,
defaultActive: false,
}),
price({
metric: ic.pct75,
name: n(75),
color: colors.red,
defaultActive: false,
}),
price({
metric: ic.pct70,
name: n(70),
color: colors.orange,
defaultActive: false,
}),
price({
metric: ic.pct65,
name: n(65),
color: colors.amber,
defaultActive: false,
}),
price({
metric: ic.pct60,
name: n(60),
color: colors.yellow,
defaultActive: false,
}),
price({
metric: ic.pct55,
name: n(55),
color: colors.yellow,
defaultActive: false,
}),
price({ metric: ic.pct50, name: n(50), color: colors.avocado }),
price({ metric: ic.pct45, name: n(45), color: colors.lime, defaultActive: false }),
price({ metric: ic.pct40, name: n(40), color: colors.green, defaultActive: false }),
price({ metric: ic.pct35, name: n(35), color: colors.emerald, defaultActive: false }),
price({ metric: ic.pct30, name: n(30), color: colors.teal, defaultActive: false }),
price({ metric: ic.pct25, name: n(25), color: colors.teal, defaultActive: false }),
price({ metric: ic.pct20, name: n(20), color: colors.cyan, defaultActive: false }),
price({ metric: ic.pct15, name: n(15), color: colors.sky, defaultActive: false }),
price({ metric: ic.pct10, name: n(10), color: colors.blue, defaultActive: false }),
price({ metric: ic.pct05, name: n(5), color: colors.indigo, defaultActive: false }),
price({
metric: ic.pct45,
name: n(45),
color: colors.lime,
defaultActive: false,
}),
price({
metric: ic.pct40,
name: n(40),
color: colors.green,
defaultActive: false,
}),
price({
metric: ic.pct35,
name: n(35),
color: colors.emerald,
defaultActive: false,
}),
price({
metric: ic.pct30,
name: n(30),
color: colors.teal,
defaultActive: false,
}),
price({
metric: ic.pct25,
name: n(25),
color: colors.teal,
defaultActive: false,
}),
price({
metric: ic.pct20,
name: n(20),
color: colors.cyan,
defaultActive: false,
}),
price({
metric: ic.pct15,
name: n(15),
color: colors.sky,
defaultActive: false,
}),
price({
metric: ic.pct10,
name: n(10),
color: colors.blue,
defaultActive: false,
}),
price({
metric: ic.pct05,
name: n(5),
color: colors.indigo,
defaultActive: false,
}),
];
});
}
@@ -946,14 +1226,13 @@ export function createInvestorPriceRatioSeries(list) {
/**
* Create investor price folder for extended cohorts (with full Z-scores)
* For cohorts with ActivePriceRatioPattern (all, term.*, ageRange.* UTXO cohorts)
* @param {PartialContext} ctx
* @param {{ tree: { realized: RealizedWithExtras }, color: Color }} cohort
* @param {string} [cohortTitle] - Cohort title (e.g., "STH")
* @returns {PartialOptionsGroup}
*/
export function createInvestorPriceFolderFull(ctx, cohort, cohortTitle) {
export function createInvestorPriceFolderFull(cohort, cohortTitle) {
const { tree, color } = cohort;
return createPriceFolder(ctx, {
return createPriceFolder({
name: "Investor Price",
cohortTitle,
priceMetric: tree.realized.investorPrice,

File diff suppressed because it is too large Load Diff

View File

@@ -3,17 +3,20 @@ import { createButtonElement, createAnchorElement } 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, extractTreeStructure } from "./unused.js";
import {
collect,
markUsed,
logUnused,
extractTreeStructure,
} from "./unused.js";
import { localhost } from "../utils/env.js";
import { setQr } from "../panes/share.js";
import { getConstant } from "./constants.js";
import { colors } from "../chart/colors.js";
import { colors } from "../utils/colors.js";
import { Unit } from "../utils/units.js";
import { brk } from "../client.js";
/**
* @param {BrkClient} brk
*/
export function initOptions(brk) {
export function initOptions() {
collect(brk.metrics);
const LS_SELECTED_KEY = `selected_path`;
@@ -26,9 +29,7 @@ export function initOptions(brk) {
JSON.parse(readStored(LS_SELECTED_KEY) || "[]") || []
).filter((v) => v);
const partialOptions = createPartialOptions({
brk,
});
const partialOptions = createPartialOptions();
// Log tree structure for analysis (localhost only)
if (localhost) {
@@ -115,9 +116,10 @@ export function initOptions(brk) {
// Check for price pattern blueprint (has dollars/sats sub-metrics)
// Use unknown cast for safe property access check
const maybePriceMetric = /** @type {{ dollars?: AnyMetricPattern, sats?: AnyMetricPattern }} */ (
/** @type {unknown} */ (blueprint.metric)
);
const maybePriceMetric =
/** @type {{ dollars?: AnyMetricPattern, sats?: AnyMetricPattern }} */ (
/** @type {unknown} */ (blueprint.metric)
);
if (maybePriceMetric.dollars?.by && maybePriceMetric.sats?.by) {
const { dollars, sats } = maybePriceMetric;
markUsed(dollars);
@@ -131,7 +133,9 @@ export function initOptions(brk) {
}
// After continue, we know this is a regular metric blueprint
const regularBlueprint = /** @type {AnyFetchedSeriesBlueprint} */ (blueprint);
const regularBlueprint = /** @type {AnyFetchedSeriesBlueprint} */ (
blueprint
);
const metric = regularBlueprint.metric;
const unit = regularBlueprint.unit;
if (!unit) continue;
@@ -171,7 +175,11 @@ export function initOptions(brk) {
title: `${baseValue}`,
color: colors.gray,
unit,
options: { lineStyle: 4, lastValueVisible: false, crosshairMarkerVisible: false },
options: {
lineStyle: 4,
lastValueVisible: false,
crosshairMarkerVisible: false,
},
});
}
}
@@ -247,7 +255,11 @@ export function initOptions(brk) {
* @param {string} parentPathStr
* @returns {{ nodes: ProcessedNode[], count: number }}
*/
function processPartialTree(partialTree, parentPath = [], parentPathStr = "") {
function processPartialTree(
partialTree,
parentPath = [],
parentPathStr = "",
) {
/** @type {ProcessedNode[]} */
const nodes = [];
let totalCount = 0;
@@ -258,7 +270,11 @@ export function initOptions(brk) {
const serName = stringToId(anyPartial.name);
const pathStr = parentPathStr ? `${parentPathStr}/${serName}` : serName;
const path = parentPath.concat(serName);
const { nodes: children, count } = processPartialTree(anyPartial.tree, path, pathStr);
const { nodes: children, count } = processPartialTree(
anyPartial.tree,
path,
pathStr,
);
// Skip groups with no children
if (count === 0) continue;

View File

@@ -1,5 +1,7 @@
/** Investing section - Investment strategy tools and analysis */
import { colors } from "../utils/colors.js";
import { brk } from "../client.js";
import { Unit } from "../utils/units.js";
import { line, baseline, price, dotted } from "./series.js";
import { satsBtcUsd } from "./shared.js";
@@ -39,6 +41,9 @@ const YEARS_2020S = /** @type {const} */ ([
]);
const YEARS_2010S = /** @type {const} */ ([2019, 2018, 2017, 2016, 2015]);
/** @typedef {typeof YEARS_2020S[number] | typeof YEARS_2010S[number]} DcaYear */
/** @typedef {`_${DcaYear}`} DcaYearKey */
/** @param {AllPeriodKey} key */
const periodName = (key) => periodIdToName(key.slice(1), true);
@@ -65,14 +70,14 @@ const periodName = (key) => periodIdToName(key.slice(1), true);
* Build DCA class entry from year
* @param {Colors} colors
* @param {MarketDca} dca
* @param {number} year
* @param {DcaYear} year
* @returns {BaseEntryItem}
*/
function buildYearEntry(colors, dca, year) {
const key = /** @type {keyof Colors["dcaYears"]} */ (`_${year}`);
const key = /** @type {DcaYearKey} */ (`_${year}`);
return {
name: `${year}`,
color: colors.dcaYears[key],
color: colors.year[key],
costBasis: dca.classAveragePrice[key],
returns: dca.classReturns[key],
minReturn: dca.classMinReturn[key],
@@ -85,21 +90,19 @@ function buildYearEntry(colors, dca, year) {
/**
* Create Investing section
* @param {PartialContext} ctx
* @returns {PartialOptionsGroup}
*/
export function createInvestingSection(ctx) {
const { brk } = ctx;
export function createInvestingSection() {
const { market } = brk.metrics;
const { dca, lookback, returns } = market;
return {
name: "Investing",
tree: [
createDcaVsLumpSumSection(ctx, { dca, lookback, returns }),
createDcaByPeriodSection(ctx, { dca, returns }),
createLumpSumByPeriodSection(ctx, { dca, lookback, returns }),
createDcaByStartYearSection(ctx, { dca }),
createDcaVsLumpSumSection({ dca, lookback, returns }),
createDcaByPeriodSection({ dca, returns }),
createLumpSumByPeriodSection({ dca, lookback, returns }),
createDcaByStartYearSection({ dca }),
],
};
}
@@ -290,15 +293,12 @@ function createLongSingleEntry(colors, item) {
/**
* Create DCA vs Lump Sum section
* @param {PartialContext} ctx
* @param {Object} args
* @param {Market["dca"]} args.dca
* @param {Market["lookback"]} args.lookback
* @param {Market["returns"]} args.returns
*/
export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) {
const { colors } = ctx;
export function createDcaVsLumpSumSection({ dca, lookback, returns }) {
/** @param {AllPeriodKey} key */
const topPane = (key) => [
price({
@@ -531,21 +531,19 @@ export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) {
/**
* Create period-based section (DCA or Lump Sum)
* @param {PartialContext} ctx
* @param {Object} args
* @param {Market["dca"]} args.dca
* @param {Market["lookback"]} [args.lookback]
* @param {Market["returns"]} args.returns
*/
function createPeriodSection(ctx, { dca, lookback, returns }) {
const { colors } = ctx;
function createPeriodSection({ dca, lookback, returns }) {
const isLumpSum = !!lookback;
const suffix = isLumpSum ? "Lump Sum" : "DCA";
/** @param {AllPeriodKey} key @returns {BaseEntryItem} */
const buildBaseEntry = (key) => ({
name: periodName(key),
color: colors.dcaPeriods[key],
color: colors.dca[key],
costBasis: isLumpSum ? lookback[key] : dca.periodAveragePrice[key],
returns: isLumpSum ? dca.periodLumpSumReturns[key] : dca.periodReturns[key],
minReturn: isLumpSum
@@ -617,36 +615,31 @@ function createPeriodSection(ctx, { dca, lookback, returns }) {
/**
* Create DCA by Period section
* @param {PartialContext} ctx
* @param {Object} args
* @param {Market["dca"]} args.dca
* @param {Market["returns"]} args.returns
*/
export function createDcaByPeriodSection(ctx, { dca, returns }) {
return createPeriodSection(ctx, { dca, returns });
export function createDcaByPeriodSection({ dca, returns }) {
return createPeriodSection({ dca, returns });
}
/**
* Create Lump Sum by Period section
* @param {PartialContext} ctx
* @param {Object} args
* @param {Market["dca"]} args.dca
* @param {Market["lookback"]} args.lookback
* @param {Market["returns"]} args.returns
*/
export function createLumpSumByPeriodSection(ctx, { dca, lookback, returns }) {
return createPeriodSection(ctx, { dca, lookback, returns });
export function createLumpSumByPeriodSection({ dca, lookback, returns }) {
return createPeriodSection({ dca, lookback, returns });
}
/**
* Create DCA by Start Year section
* @param {PartialContext} ctx
* @param {Object} args
* @param {Market["dca"]} args.dca
*/
export function createDcaByStartYearSection(ctx, { dca }) {
const { colors } = ctx;
export function createDcaByStartYearSection({ dca }) {
/** @param {string} name @param {string} title @param {BaseEntryItem[]} entries */
const createDecadeGroup = (name, title, entries) => ({
name,

View File

@@ -1,5 +1,7 @@
/** Market section */
import { colors } from "../utils/colors.js";
import { brk } from "../client.js";
import { includes } from "../utils/array.js";
import { Unit } from "../utils/units.js";
import { priceLine, priceLines } from "./constants.js";
@@ -27,21 +29,27 @@ import { periodIdToName } from "./utils.js";
* @property {ActivePriceRatioPattern} ratio
*/
const commonMaIds = /** @type {const} */ (["1w", "1m", "200d", "1y", "200w", "4y"]);
const commonMaIds = /** @type {const} */ ([
"1w",
"1m",
"200d",
"1y",
"200w",
"4y",
]);
/**
* @param {PartialContext} ctx
* @param {string} label
* @param {MaPeriod[]} averages
*/
function createMaSubSection(ctx, label, averages) {
function createMaSubSection(label, averages) {
const common = averages.filter((a) => includes(commonMaIds, a.id));
const more = averages.filter((a) => !includes(commonMaIds, a.id));
/** @param {MaPeriod} a */
const toFolder = (a) => ({
name: periodIdToName(a.id, true),
tree: createPriceRatioCharts(ctx, {
tree: createPriceRatioCharts({
context: `${periodIdToName(a.id, true)} ${label}`,
legend: "average",
pricePattern: a.ratio.price,
@@ -56,7 +64,9 @@ function createMaSubSection(ctx, label, averages) {
{
name: "Compare",
title: `Price ${label}s`,
top: averages.map((a) => price({ metric: a.ratio.price, name: a.id, color: a.color })),
top: averages.map((a) =>
price({ metric: a.ratio.price, name: a.id, color: a.color }),
),
},
...common.map(toFolder),
{ name: "More...", tree: more.map(toFolder) },
@@ -94,12 +104,21 @@ function returnsSubSection(name, periods) {
{
name: "Compare",
title: `${name} Returns`,
bottom: periods.map((p) => baseline({ metric: p.returns, name: p.id, color: p.color, unit: Unit.percentage })),
bottom: periods.map((p) =>
baseline({
metric: p.returns,
name: p.id,
color: p.color,
unit: Unit.percentage,
}),
),
},
...periods.map((p) => ({
name: periodIdToName(p.id, true),
title: `${periodIdToName(p.id, true)} Returns`,
bottom: [baseline({ metric: p.returns, name: "Total", unit: Unit.percentage })],
bottom: [
baseline({ metric: p.returns, name: "Total", unit: Unit.percentage }),
],
})),
],
};
@@ -116,7 +135,14 @@ function returnsSubSectionWithCagr(name, periods) {
{
name: "Compare",
title: `${name} Returns`,
bottom: periods.map((p) => baseline({ metric: p.returns, name: p.id, color: p.color, unit: Unit.percentage })),
bottom: periods.map((p) =>
baseline({
metric: p.returns,
name: p.id,
color: p.color,
unit: Unit.percentage,
}),
),
},
...periods.map((p) => ({
name: periodIdToName(p.id, true),
@@ -141,7 +167,9 @@ function historicalSubSection(name, periods) {
{
name: "Compare",
title: `${name} Historical`,
top: periods.map((p) => price({ metric: p.lookback, name: p.id, color: p.color })),
top: periods.map((p) =>
price({ metric: p.lookback, name: p.id, color: p.color }),
),
},
...periods.map((p) => ({
name: periodIdToName(p.id, true),
@@ -154,33 +182,119 @@ function historicalSubSection(name, periods) {
/**
* Create Market section
* @param {PartialContext} ctx
* @returns {PartialOptionsGroup}
*/
export function createMarketSection(ctx) {
const { colors, brk } = ctx;
export function createMarketSection() {
const { market, supply, price: priceMetrics } = brk.metrics;
const { movingAverage: ma, ath, returns, volatility, range, indicators, lookback } = market;
const {
movingAverage: ma,
ath,
returns,
volatility,
range,
indicators,
lookback,
} = market;
/** @type {Period[]} */
const shortPeriods = [
{ id: "1d", color: colors.returns._1d, returns: returns.priceReturns._1d, lookback: lookback._1d },
{ id: "1w", color: colors.returns._1w, returns: returns.priceReturns._1w, lookback: lookback._1w },
{ id: "1m", color: colors.returns._1m, returns: returns.priceReturns._1m, lookback: lookback._1m },
{ id: "3m", color: colors.returns._3m, returns: returns.priceReturns._3m, lookback: lookback._3m, defaultActive: false },
{ id: "6m", color: colors.returns._6m, returns: returns.priceReturns._6m, lookback: lookback._6m, defaultActive: false },
{ id: "1y", color: colors.returns._1y, returns: returns.priceReturns._1y, lookback: lookback._1y },
{
id: "1d",
color: colors.returns._1d,
returns: returns.priceReturns._1d,
lookback: lookback._1d,
},
{
id: "1w",
color: colors.returns._1w,
returns: returns.priceReturns._1w,
lookback: lookback._1w,
},
{
id: "1m",
color: colors.returns._1m,
returns: returns.priceReturns._1m,
lookback: lookback._1m,
},
{
id: "3m",
color: colors.returns._3m,
returns: returns.priceReturns._3m,
lookback: lookback._3m,
defaultActive: false,
},
{
id: "6m",
color: colors.returns._6m,
returns: returns.priceReturns._6m,
lookback: lookback._6m,
defaultActive: false,
},
{
id: "1y",
color: colors.returns._1y,
returns: returns.priceReturns._1y,
lookback: lookback._1y,
},
];
/** @type {PeriodWithCagr[]} */
const longPeriods = [
{ id: "2y", color: colors.returns._2y, returns: returns.priceReturns._2y, cagr: returns.cagr._2y, lookback: lookback._2y, defaultActive: false },
{ id: "3y", color: colors.returns._3y, returns: returns.priceReturns._3y, cagr: returns.cagr._3y, lookback: lookback._3y, defaultActive: false },
{ id: "4y", color: colors.returns._4y, returns: returns.priceReturns._4y, cagr: returns.cagr._4y, lookback: lookback._4y },
{ id: "5y", color: colors.returns._5y, returns: returns.priceReturns._5y, cagr: returns.cagr._5y, lookback: lookback._5y, defaultActive: false },
{ id: "6y", color: colors.returns._6y, returns: returns.priceReturns._6y, cagr: returns.cagr._6y, lookback: lookback._6y, defaultActive: false },
{ id: "8y", color: colors.returns._8y, returns: returns.priceReturns._8y, cagr: returns.cagr._8y, lookback: lookback._8y, defaultActive: false },
{ id: "10y", color: colors.returns._10y, returns: returns.priceReturns._10y, cagr: returns.cagr._10y, lookback: lookback._10y, defaultActive: false },
{
id: "2y",
color: colors.returns._2y,
returns: returns.priceReturns._2y,
cagr: returns.cagr._2y,
lookback: lookback._2y,
defaultActive: false,
},
{
id: "3y",
color: colors.returns._3y,
returns: returns.priceReturns._3y,
cagr: returns.cagr._3y,
lookback: lookback._3y,
defaultActive: false,
},
{
id: "4y",
color: colors.returns._4y,
returns: returns.priceReturns._4y,
cagr: returns.cagr._4y,
lookback: lookback._4y,
},
{
id: "5y",
color: colors.returns._5y,
returns: returns.priceReturns._5y,
cagr: returns.cagr._5y,
lookback: lookback._5y,
defaultActive: false,
},
{
id: "6y",
color: colors.returns._6y,
returns: returns.priceReturns._6y,
cagr: returns.cagr._6y,
lookback: lookback._6y,
defaultActive: false,
},
{
id: "8y",
color: colors.returns._8y,
returns: returns.priceReturns._8y,
cagr: returns.cagr._8y,
lookback: lookback._8y,
defaultActive: false,
},
{
id: "10y",
color: colors.returns._10y,
returns: returns.priceReturns._10y,
cagr: returns.cagr._10y,
lookback: lookback._10y,
defaultActive: false,
},
];
/** @type {MaPeriod[]} */
@@ -225,12 +339,48 @@ export function createMarketSection(ctx) {
// SMA vs EMA comparison periods (common periods only)
const smaVsEma = [
{ id: "1w", name: "1 Week", color: colors.ma._1w, sma: ma.price1wSma, ema: ma.price1wEma },
{ id: "1m", name: "1 Month", color: colors.ma._1m, sma: ma.price1mSma, ema: ma.price1mEma },
{ id: "200d", name: "200 Day", color: colors.ma._200d, sma: ma.price200dSma, ema: ma.price200dEma },
{ id: "1y", name: "1 Year", color: colors.ma._1y, sma: ma.price1ySma, ema: ma.price1yEma },
{ id: "200w", name: "200 Week", color: colors.ma._200w, sma: ma.price200wSma, ema: ma.price200wEma },
{ id: "4y", name: "4 Year", color: colors.ma._4y, sma: ma.price4ySma, ema: ma.price4yEma },
{
id: "1w",
name: "1 Week",
color: colors.ma._1w,
sma: ma.price1wSma,
ema: ma.price1wEma,
},
{
id: "1m",
name: "1 Month",
color: colors.ma._1m,
sma: ma.price1mSma,
ema: ma.price1mEma,
},
{
id: "200d",
name: "200 Day",
color: colors.ma._200d,
sma: ma.price200dSma,
ema: ma.price200dEma,
},
{
id: "1y",
name: "1 Year",
color: colors.ma._1y,
sma: ma.price1ySma,
ema: ma.price1yEma,
},
{
id: "200w",
name: "200 Week",
color: colors.ma._200w,
sma: ma.price200wSma,
ema: ma.price200wEma,
},
{
id: "4y",
name: "4 Year",
color: colors.ma._4y,
sma: ma.price4ySma,
ema: ma.price4yEma,
},
];
return {
@@ -241,13 +391,25 @@ export function createMarketSection(ctx) {
{
name: "Sats/$",
title: "Sats per Dollar",
bottom: [line({ metric: priceMetrics.sats.split.close, name: "Sats/$", unit: Unit.sats })],
bottom: [
line({
metric: priceMetrics.sats.split.close,
name: "Sats/$",
unit: Unit.sats,
}),
],
},
{
name: "Capitalization",
title: "Market Capitalization",
bottom: [line({ metric: supply.marketCap, name: "Capitalization", unit: Unit.usd })],
bottom: [
line({
metric: supply.marketCap,
name: "Capitalization",
unit: Unit.usd,
}),
],
},
{
@@ -257,17 +419,42 @@ export function createMarketSection(ctx) {
name: "Drawdown",
title: "ATH Drawdown",
top: [price({ metric: ath.priceAth, name: "ATH" })],
bottom: [line({ metric: ath.priceDrawdown, name: "Drawdown", color: colors.red, unit: Unit.percentage })],
bottom: [
line({
metric: ath.priceDrawdown,
name: "Drawdown",
color: colors.red,
unit: Unit.percentage,
}),
],
},
{
name: "Time Since",
title: "Time Since ATH",
top: [price({ metric: ath.priceAth, name: "ATH" })],
bottom: [
line({ metric: ath.daysSincePriceAth, name: "Since", unit: Unit.days }),
line({ metric: ath.yearsSincePriceAth, name: "Since", unit: Unit.years }),
line({ metric: ath.maxDaysBetweenPriceAths, name: "Max", color: colors.red, unit: Unit.days }),
line({ metric: ath.maxYearsBetweenPriceAths, name: "Max", color: colors.red, unit: Unit.years }),
line({
metric: ath.daysSincePriceAth,
name: "Since",
unit: Unit.days,
}),
line({
metric: ath.yearsSincePriceAth,
name: "Since",
unit: Unit.years,
}),
line({
metric: ath.maxDaysBetweenPriceAths,
name: "Max",
color: colors.red,
unit: Unit.days,
}),
line({
metric: ath.maxYearsBetweenPriceAths,
name: "Max",
color: colors.red,
unit: Unit.years,
}),
],
},
],
@@ -297,25 +484,47 @@ export function createMarketSection(ctx) {
{
name: "Volatility",
tree: [
volatilityChart(colors, "Index", "Volatility Index", Unit.percentage, {
_1w: volatility.price1wVolatility,
_1m: volatility.price1mVolatility,
_1y: volatility.price1yVolatility,
}),
volatilityChart(
colors,
"Index",
"Volatility Index",
Unit.percentage,
{
_1w: volatility.price1wVolatility,
_1m: volatility.price1mVolatility,
_1y: volatility.price1yVolatility,
},
),
{
name: "True Range",
title: "True Range",
bottom: [
line({ metric: range.priceTrueRange, name: "Daily", color: colors.yellow, unit: Unit.usd }),
line({ metric: range.priceTrueRange2wSum, name: "2w Sum", color: colors.orange, unit: Unit.usd, defaultActive: false }),
line({
metric: range.priceTrueRange,
name: "Daily",
color: colors.yellow,
unit: Unit.usd,
}),
line({
metric: range.priceTrueRange2wSum,
name: "2w Sum",
color: colors.orange,
unit: Unit.usd,
defaultActive: false,
}),
],
},
{
name: "Choppiness",
title: "Choppiness Index",
bottom: [
line({ metric: range.price2wChoppinessIndex, name: "2w", color: colors.red, unit: Unit.index }),
...priceLines({ ctx, unit: Unit.index, numbers: [61.8, 38.2] }),
line({
metric: range.price2wChoppinessIndex,
name: "2w",
color: colors.red,
unit: Unit.index,
}),
...priceLines({ unit: Unit.index, numbers: [61.8, 38.2] }),
],
},
volatilityChart(colors, "Sharpe Ratio", "Sharpe Ratio", Unit.ratio, {
@@ -323,11 +532,17 @@ export function createMarketSection(ctx) {
_1m: volatility.sharpe1m,
_1y: volatility.sharpe1y,
}),
volatilityChart(colors, "Sortino Ratio", "Sortino Ratio", Unit.ratio, {
_1w: volatility.sortino1w,
_1m: volatility.sortino1m,
_1y: volatility.sortino1y,
}),
volatilityChart(
colors,
"Sortino Ratio",
"Sortino Ratio",
Unit.ratio,
{
_1w: volatility.sortino1w,
_1m: volatility.sortino1m,
_1y: volatility.sortino1y,
},
),
],
},
@@ -341,8 +556,17 @@ export function createMarketSection(ctx) {
name: "All Periods",
title: "SMA vs EMA Comparison",
top: smaVsEma.flatMap((p) => [
price({ metric: p.sma.price, name: `${p.id} SMA`, color: p.color }),
price({ metric: p.ema.price, name: `${p.id} EMA`, color: p.color, style: 1 }),
price({
metric: p.sma.price,
name: `${p.id} SMA`,
color: p.color,
}),
price({
metric: p.ema.price,
name: `${p.id} EMA`,
color: p.color,
style: 1,
}),
]),
},
...smaVsEma.map((p) => ({
@@ -350,13 +574,18 @@ export function createMarketSection(ctx) {
title: `${p.name} SMA vs EMA`,
top: [
price({ metric: p.sma.price, name: "SMA", color: p.color }),
price({ metric: p.ema.price, name: "EMA", color: p.color, style: 1 }),
price({
metric: p.ema.price,
name: "EMA",
color: p.color,
style: 1,
}),
],
})),
],
},
createMaSubSection(ctx, "SMA", sma),
createMaSubSection(ctx, "EMA", ema),
createMaSubSection("SMA", sma),
createMaSubSection("EMA", ema),
],
},
@@ -366,16 +595,46 @@ export function createMarketSection(ctx) {
{
name: "MinMax",
tree: [
{ id: "1w", name: "1 Week", min: range.price1wMin, max: range.price1wMax },
{ id: "2w", name: "2 Week", min: range.price2wMin, max: range.price2wMax },
{ id: "1m", name: "1 Month", min: range.price1mMin, max: range.price1mMax },
{ id: "1y", name: "1 Year", min: range.price1yMin, max: range.price1yMax },
{
id: "1w",
name: "1 Week",
min: range.price1wMin,
max: range.price1wMax,
},
{
id: "2w",
name: "2 Week",
min: range.price2wMin,
max: range.price2wMax,
},
{
id: "1m",
name: "1 Month",
min: range.price1mMin,
max: range.price1mMax,
},
{
id: "1y",
name: "1 Year",
min: range.price1yMin,
max: range.price1yMax,
},
].map((p) => ({
name: p.id,
title: `${p.name} MinMax`,
top: [
price({ metric: p.min, name: "Min", key: "price-min", color: colors.red }),
price({ metric: p.max, name: "Max", key: "price-max", color: colors.green }),
price({
metric: p.min,
name: "Min",
key: "price-min",
color: colors.red,
}),
price({
metric: p.max,
name: "Max",
key: "price-max",
color: colors.green,
}),
],
})),
},
@@ -383,9 +642,21 @@ export function createMarketSection(ctx) {
name: "Mayer Multiple",
title: "Mayer Multiple",
top: [
price({ metric: ma.price200dSma.price, name: "200d SMA", color: colors.yellow }),
price({ metric: ma.price200dSmaX24, name: "200d SMA x2.4", color: colors.green }),
price({ metric: ma.price200dSmaX08, name: "200d SMA x0.8", color: colors.red }),
price({
metric: ma.price200dSma.price,
name: "200d SMA",
color: colors.yellow,
}),
price({
metric: ma.price200dSmaX24,
name: "200d SMA x2.4",
color: colors.green,
}),
price({
metric: ma.price200dSmaX08,
name: "200d SMA x0.8",
color: colors.red,
}),
],
},
],
@@ -398,30 +669,71 @@ export function createMarketSection(ctx) {
name: "RSI",
title: "RSI (14d)",
bottom: [
line({ metric: indicators.rsi14d, name: "RSI", color: colors.indigo, unit: Unit.index }),
line({ metric: indicators.rsi14dMin, name: "Min", color: colors.red, defaultActive: false, unit: Unit.index }),
line({ metric: indicators.rsi14dMax, name: "Max", color: colors.green, defaultActive: false, unit: Unit.index }),
priceLine({ ctx, unit: Unit.index, number: 70 }),
priceLine({ ctx, unit: Unit.index, number: 50, defaultActive: false }),
priceLine({ ctx, unit: Unit.index, number: 30 }),
line({
metric: indicators.rsi14d,
name: "RSI",
color: colors.indigo,
unit: Unit.index,
}),
line({
metric: indicators.rsi14dMin,
name: "Min",
color: colors.red,
defaultActive: false,
unit: Unit.index,
}),
line({
metric: indicators.rsi14dMax,
name: "Max",
color: colors.green,
defaultActive: false,
unit: Unit.index,
}),
priceLine({ unit: Unit.index, number: 70 }),
priceLine({ unit: Unit.index, number: 50, defaultActive: false }),
priceLine({ unit: Unit.index, number: 30 }),
],
},
{
name: "StochRSI",
title: "Stochastic RSI",
bottom: [
line({ metric: indicators.stochRsiK, name: "K", color: colors.blue, unit: Unit.index }),
line({ metric: indicators.stochRsiD, name: "D", color: colors.orange, unit: Unit.index }),
...priceLines({ ctx, unit: Unit.index, numbers: [80, 20] }),
line({
metric: indicators.stochRsiK,
name: "K",
color: colors.blue,
unit: Unit.index,
}),
line({
metric: indicators.stochRsiD,
name: "D",
color: colors.orange,
unit: Unit.index,
}),
...priceLines({ unit: Unit.index, numbers: [80, 20] }),
],
},
{
name: "MACD",
title: "MACD",
bottom: [
line({ metric: indicators.macdLine, name: "MACD", color: colors.blue, unit: Unit.usd }),
line({ metric: indicators.macdSignal, name: "Signal", color: colors.orange, unit: Unit.usd }),
histogram({ metric: indicators.macdHistogram, name: "Histogram", unit: Unit.usd }),
line({
metric: indicators.macdLine,
name: "MACD",
color: colors.blue,
unit: Unit.usd,
}),
line({
metric: indicators.macdSignal,
name: "Signal",
color: colors.orange,
unit: Unit.usd,
}),
histogram({
metric: indicators.macdHistogram,
name: "Histogram",
unit: Unit.usd,
}),
],
},
],
@@ -454,25 +766,61 @@ export function createMarketSection(ctx) {
name: "Pi Cycle",
title: "Pi Cycle",
top: [
price({ metric: ma.price111dSma.price, name: "111d SMA", color: colors.green }),
price({ metric: ma.price350dSmaX2, name: "350d SMA x2", color: colors.red }),
price({
metric: ma.price111dSma.price,
name: "111d SMA",
color: colors.green,
}),
price({
metric: ma.price350dSmaX2,
name: "350d SMA x2",
color: colors.red,
}),
],
bottom: [
baseline({
metric: indicators.piCycle,
name: "Pi Cycle",
unit: Unit.ratio,
base: 1,
}),
],
bottom: [baseline({ metric: indicators.piCycle, name: "Pi Cycle", unit: Unit.ratio, base: 1 })],
},
{
name: "Puell Multiple",
title: "Puell Multiple",
bottom: [line({ metric: indicators.puellMultiple, name: "Puell", color: colors.green, unit: Unit.ratio })],
bottom: [
line({
metric: indicators.puellMultiple,
name: "Puell",
color: colors.green,
unit: Unit.ratio,
}),
],
},
{
name: "NVT",
title: "NVT Ratio",
bottom: [line({ metric: indicators.nvt, name: "NVT", color: colors.orange, unit: Unit.ratio })],
bottom: [
line({
metric: indicators.nvt,
name: "NVT",
color: colors.orange,
unit: Unit.ratio,
}),
],
},
{
name: "Gini",
title: "Gini Coefficient",
bottom: [line({ metric: indicators.gini, name: "Gini", color: colors.red, unit: Unit.ratio })],
bottom: [
line({
metric: indicators.gini,
name: "Gini",
color: colors.red,
unit: Unit.ratio,
}),
],
},
],
},

View File

@@ -2,7 +2,7 @@
import { Unit } from "../utils/units.js";
import { entries, includes } from "../utils/array.js";
import { colorAt } from "../chart/colors.js";
import { colorAt, colors } from "../utils/colors.js";
import {
line,
baseline,
@@ -16,6 +16,7 @@ import {
satsBtcUsdFromFull,
revenueBtcSatsUsd,
} from "./shared.js";
import { brk } from "../client.js";
/** Major pools to show in Compare section (by current hashrate dominance) */
const MAJOR_POOL_IDS = /** @type {const} */ ([
@@ -49,11 +50,9 @@ const ANTPOOL_AND_FRIENDS_IDS = /** @type {const} */ ([
/**
* Create Mining section
* @param {PartialContext} ctx
* @returns {PartialOptionsGroup}
*/
export function createMiningSection(ctx) {
const { colors, brk } = ctx;
export function createMiningSection() {
const { blocks, transactions, pools } = brk.metrics;
// Pre-compute pool entries with resolved names
@@ -174,7 +173,7 @@ export function createMiningSection(ctx) {
{
name: "Sum",
title: `Rewards: ${name}`,
bottom: revenueBtcSatsUsd(colors, {
bottom: revenueBtcSatsUsd({
coinbase: pool.coinbase,
subsidy: pool.subsidy,
fee: pool.fee,
@@ -184,7 +183,7 @@ export function createMiningSection(ctx) {
{
name: "Cumulative",
title: `Rewards: ${name} (Total)`,
bottom: revenueBtcSatsUsd(colors, {
bottom: revenueBtcSatsUsd({
coinbase: pool.coinbase,
subsidy: pool.subsidy,
fee: pool.fee,
@@ -330,7 +329,7 @@ export function createMiningSection(ctx) {
{
name: "Sum",
title: "Revenue Comparison",
bottom: revenueBtcSatsUsd(colors, {
bottom: revenueBtcSatsUsd({
coinbase: blocks.rewards.coinbase,
subsidy: blocks.rewards.subsidy,
fee: transactions.fees.fee,
@@ -340,7 +339,7 @@ export function createMiningSection(ctx) {
{
name: "Cumulative",
title: "Revenue Comparison (Total)",
bottom: revenueBtcSatsUsd(colors, {
bottom: revenueBtcSatsUsd({
coinbase: blocks.rewards.coinbase,
subsidy: blocks.rewards.subsidy,
fee: transactions.fees.fee,

View File

@@ -1,26 +1,26 @@
/** Network section - On-chain activity and health */
import { colors } from "../utils/colors.js";
import { brk } from "../client.js";
import { Unit } from "../utils/units.js";
import { includes } from "../utils/array.js";
import { priceLine } from "./constants.js";
import { line, dots, fromSupplyPattern } from "./series.js";
import {
line,
dots,
fromSupplyPattern,
fromBaseStatsPattern,
chartsFromFull,
chartsFromValueFull,
fromStatsPattern,
chartsFromSum,
} from "./series.js";
import { satsBtcUsd, satsBtcUsdFrom } from "./shared.js";
/**
* Create Network section
* @param {PartialContext} ctx
* @returns {PartialOptionsGroup}
*/
export function createNetworkSection(ctx) {
const {
colors,
brk,
fromBaseStatsPattern,
fromStatsPattern,
chartsFromFull,
chartsFromSum,
chartsFromValueFull,
} = ctx;
export function createNetworkSection() {
const {
blocks,
transactions,
@@ -31,162 +31,62 @@ export function createNetworkSection(ctx) {
distribution,
} = brk.metrics;
// Address types for mapping (newest to oldest)
const st = colors.scriptType;
// Addressable types - newest to oldest (for addresses/counts that only support addressable types)
const addressTypes = /** @type {const} */ ([
{
key: "p2a",
name: "P2A",
color: colors[spendableTypeColors.p2a],
defaultActive: false,
},
{
key: "p2tr",
name: "P2TR",
color: colors[spendableTypeColors.p2tr],
defaultActive: true,
},
{
key: "p2wsh",
name: "P2WSH",
color: colors[spendableTypeColors.p2wsh],
defaultActive: true,
},
{
key: "p2wpkh",
name: "P2WPKH",
color: colors[spendableTypeColors.p2wpkh],
defaultActive: true,
},
{
key: "p2sh",
name: "P2SH",
color: colors[spendableTypeColors.p2sh],
defaultActive: true,
},
{
key: "p2pkh",
name: "P2PKH",
color: colors[spendableTypeColors.p2pkh],
defaultActive: true,
},
{
key: "p2pk33",
name: "P2PK33",
color: colors[spendableTypeColors.p2pk33],
defaultActive: false,
},
{
key: "p2pk65",
name: "P2PK65",
color: colors[spendableTypeColors.p2pk65],
defaultActive: false,
},
{ key: "p2a", name: "P2A", color: st.p2a, defaultActive: false },
{ key: "p2tr", name: "P2TR", color: st.p2tr, defaultActive: true },
{ key: "p2wsh", name: "P2WSH", color: st.p2wsh, defaultActive: true },
{ key: "p2wpkh", name: "P2WPKH", color: st.p2wpkh, defaultActive: true },
{ key: "p2sh", name: "P2SH", color: st.p2sh, defaultActive: true },
{ key: "p2pkh", name: "P2PKH", color: st.p2pkh, defaultActive: true },
{ key: "p2pk33", name: "P2PK33", color: st.p2pk33, defaultActive: false },
{ key: "p2pk65", name: "P2PK65", color: st.p2pk65, defaultActive: false },
]);
// Address type groups (newest to oldest)
const legacyAddresses = /** @type {const} */ ([
{ key: "p2sh", name: "P2SH", color: colors[spendableTypeColors.p2sh] },
{ key: "p2pkh", name: "P2PKH", color: colors[spendableTypeColors.p2pkh] },
{
key: "p2pk33",
name: "P2PK33",
color: colors[spendableTypeColors.p2pk33],
},
{
key: "p2pk65",
name: "P2PK65",
color: colors[spendableTypeColors.p2pk65],
},
]);
const segwitAddresses = /** @type {const} */ ([
{ key: "p2wsh", name: "P2WSH", color: colors[spendableTypeColors.p2wsh] },
{
key: "p2wpkh",
name: "P2WPKH",
color: colors[spendableTypeColors.p2wpkh],
},
]);
const taprootAddresses = /** @type {const} */ ([
{ key: "p2a", name: "P2A", color: colors[spendableTypeColors.p2a] },
{ key: "p2tr", name: "P2TR", color: colors[spendableTypeColors.p2tr] },
]);
// Script types for output count comparisons (newest to oldest, then non-addressable)
const scriptTypes = /** @type {const} */ ([
{
key: "p2a",
name: "P2A",
color: colors[spendableTypeColors.p2a],
defaultActive: false,
},
{
key: "p2tr",
name: "P2TR",
color: colors[spendableTypeColors.p2tr],
defaultActive: true,
},
{
key: "p2wsh",
name: "P2WSH",
color: colors[spendableTypeColors.p2wsh],
defaultActive: true,
},
{
key: "p2wpkh",
name: "P2WPKH",
color: colors[spendableTypeColors.p2wpkh],
defaultActive: true,
},
{
key: "p2sh",
name: "P2SH",
color: colors[spendableTypeColors.p2sh],
defaultActive: true,
},
{
key: "p2pkh",
name: "P2PKH",
color: colors[spendableTypeColors.p2pkh],
defaultActive: true,
},
{
key: "p2pk33",
name: "P2PK33",
color: colors[spendableTypeColors.p2pk33],
defaultActive: false,
},
{
key: "p2pk65",
name: "P2PK65",
color: colors[spendableTypeColors.p2pk65],
defaultActive: false,
},
{
key: "p2ms",
name: "P2MS",
color: colors[spendableTypeColors.p2ms],
defaultActive: false,
},
// Non-addressable script types
const nonAddressableTypes = /** @type {const} */ ([
{ key: "p2ms", name: "P2MS", color: st.p2ms, defaultActive: false },
{
key: "opreturn",
name: "OP_RETURN",
color: colors[spendableTypeColors.opreturn],
color: st.opreturn,
defaultActive: false,
},
{
key: "emptyoutput",
name: "Empty",
color: colors[spendableTypeColors.empty],
color: st.empty,
defaultActive: false,
},
{
key: "unknownoutput",
name: "Unknown",
color: colors[spendableTypeColors.unknown],
color: st.unknown,
defaultActive: false,
},
]);
// All script types = addressable + non-addressable
const scriptTypes = [...addressTypes, ...nonAddressableTypes];
// Address type groups (by era)
const taprootAddresses = /** @type {const} */ ([
{ key: "p2a", name: "P2A", color: st.p2a },
{ key: "p2tr", name: "P2TR", color: st.p2tr },
]);
const segwitAddresses = /** @type {const} */ ([
{ key: "p2wsh", name: "P2WSH", color: st.p2wsh },
{ key: "p2wpkh", name: "P2WPKH", color: st.p2wpkh },
]);
const legacyAddresses = /** @type {const} */ ([
{ key: "p2sh", name: "P2SH", color: st.p2sh },
{ key: "p2pkh", name: "P2PKH", color: st.p2pkh },
{ key: "p2pk33", name: "P2PK33", color: st.p2pk33 },
{ key: "p2pk65", name: "P2PK65", color: st.p2pk65 },
]);
// Transacting types (transaction participation)
const transactingTypes = /** @type {const} */ ([
{
@@ -442,49 +342,25 @@ export function createNetworkSection(ctx) {
],
});
// Script type groups for Output Counts (newest to oldest)
// Script type groups for Output Counts
const legacyScripts = /** @type {const} */ ([
{ key: "p2pkh", name: "P2PKH", color: colors[spendableTypeColors.p2pkh] },
{
key: "p2pk33",
name: "P2PK33",
color: colors[spendableTypeColors.p2pk33],
},
{
key: "p2pk65",
name: "P2PK65",
color: colors[spendableTypeColors.p2pk65],
},
{ key: "p2pkh", name: "P2PKH", color: st.p2pkh },
{ key: "p2pk33", name: "P2PK33", color: st.p2pk33 },
{ key: "p2pk65", name: "P2PK65", color: st.p2pk65 },
]);
const scriptHashScripts = /** @type {const} */ ([
{ key: "p2sh", name: "P2SH", color: colors[spendableTypeColors.p2sh] },
{ key: "p2ms", name: "P2MS", color: colors[spendableTypeColors.p2ms] },
{ key: "p2sh", name: "P2SH", color: st.p2sh },
{ key: "p2ms", name: "P2MS", color: st.p2ms },
]);
const segwitScripts = /** @type {const} */ ([
{ key: "segwit", name: "All SegWit", color: colors.cyan },
{ key: "p2wsh", name: "P2WSH", color: colors[spendableTypeColors.p2wsh] },
{
key: "p2wpkh",
name: "P2WPKH",
color: colors[spendableTypeColors.p2wpkh],
},
{ key: "p2wsh", name: "P2WSH", color: st.p2wsh },
{ key: "p2wpkh", name: "P2WPKH", color: st.p2wpkh },
]);
const otherScripts = /** @type {const} */ ([
{
key: "opreturn",
name: "OP_RETURN",
color: colors[spendableTypeColors.opreturn],
},
{
key: "emptyoutput",
name: "Empty",
color: colors[spendableTypeColors.empty],
},
{
key: "unknownoutput",
name: "Unknown",
color: colors[spendableTypeColors.unknown],
},
{ key: "opreturn", name: "OP_RETURN", color: st.opreturn },
{ key: "emptyoutput", name: "Empty", color: st.empty },
{ key: "unknownoutput", name: "Unknown", color: st.unknown },
]);
/**
@@ -793,7 +669,7 @@ export function createNetworkSection(ctx) {
pattern: blocks.interval,
unit: Unit.secs,
}),
priceLine({ ctx, unit: Unit.secs, name: "Target", number: 600 }),
priceLine({ unit: Unit.secs, name: "Target", number: 600 }),
],
},
{

View File

@@ -1,6 +1,5 @@
/** Partial options - Main entry point */
import { createContext } from "./context.js";
import {
buildCohortData,
createCohortFolderAll,
@@ -20,22 +19,15 @@ import { createNetworkSection } from "./network.js";
import { createMiningSection } from "./mining.js";
import { createCointimeSection } from "./cointime.js";
import { createInvestingSection } from "./investing.js";
import { colors } from "../chart/colors.js";
// Re-export types for external consumers
export * from "./types.js";
export * from "./context.js";
/**
* Create partial options tree
* @param {Object} args
* @param {BrkClient} args.brk
* @returns {PartialOptionsTree}
*/
export function createPartialOptions({ brk }) {
// Create context with all helpers
const ctx = createContext({ brk });
export function createPartialOptions() {
// Build cohort data
const {
cohortAll,
@@ -54,29 +46,7 @@ export function createPartialOptions({ brk }) {
typeAddressable,
typeOther,
year,
} = buildCohortData(colors, brk);
// Helpers to map cohorts by capability type
/** @param {CohortWithAdjusted} cohort */
const mapWithAdjusted = (cohort) =>
createCohortFolderWithAdjusted(ctx, cohort);
/** @param {CohortAgeRange} cohort */
const mapAgeRange = (cohort) => createCohortFolderAgeRange(ctx, cohort);
/** @param {CohortBasicWithMarketCap} cohort */
const mapBasicWithMarketCap = (cohort) =>
createCohortFolderBasicWithMarketCap(ctx, cohort);
/** @param {CohortMinAge} cohort */
const mapMinAge = (cohort) => createCohortFolderMinAge(ctx, cohort);
/** @param {CohortBasicWithoutMarketCap} cohort */
const mapBasicWithoutMarketCap = (cohort) =>
createCohortFolderBasicWithoutMarketCap(ctx, cohort);
/** @param {CohortWithoutRelative} cohort */
const mapWithoutRelative = (cohort) =>
createCohortFolderWithoutRelative(ctx, cohort);
/** @param {CohortAddress} cohort */
const mapAddress = (cohort) => createCohortFolderAddress(ctx, cohort);
/** @param {AddressCohortObject} cohort */
const mapAddressCohorts = (cohort) => createAddressCohortFolder(ctx, cohort);
} = buildCohortData();
return [
// Debug explorer (disabled)
@@ -95,29 +65,29 @@ export function createPartialOptions({ brk }) {
name: "Charts",
tree: [
// Market section
createMarketSection(ctx),
createMarketSection(),
// Network section (on-chain activity)
createNetworkSection(ctx),
createNetworkSection(),
// Mining section (security & economics)
createMiningSection(ctx),
createMiningSection(),
// Cohorts section
{
name: "Distribution",
tree: [
// Overview - All UTXOs (adjustedSopr + percentiles but no RelToMarketCap)
createCohortFolderAll(ctx, { ...cohortAll, name: "Overview" }),
createCohortFolderAll({ ...cohortAll, name: "Overview" }),
// STH - Short term holder cohort (Full capability)
createCohortFolderFull(ctx, termShort),
createCohortFolderFull(termShort),
// LTH - Long term holder cohort (nupl)
createCohortFolderWithNupl(ctx, termLong),
createCohortFolderWithNupl(termLong),
// STH vs LTH - Direct comparison
createCohortFolderWithNupl(ctx, {
createCohortFolderWithNupl({
name: "STH vs LTH",
title: "STH vs LTH",
list: [termShort, termLong],
@@ -131,36 +101,36 @@ export function createPartialOptions({ brk }) {
{
name: "Younger Than",
tree: [
createCohortFolderWithAdjusted(ctx, {
createCohortFolderWithAdjusted({
name: "Compare",
title: "Max Age",
list: upToDate,
}),
...upToDate.map(mapWithAdjusted),
...upToDate.map(createCohortFolderWithAdjusted),
],
},
// Older Than (≥ X old)
{
name: "Older Than",
tree: [
createCohortFolderMinAge(ctx, {
createCohortFolderMinAge({
name: "Compare",
title: "Min Age",
list: fromDate,
}),
...fromDate.map(mapMinAge),
...fromDate.map(createCohortFolderMinAge),
],
},
// Range
{
name: "Range",
tree: [
createCohortFolderAgeRange(ctx, {
createCohortFolderAgeRange({
name: "Compare",
title: "Age Ranges",
list: dateRange,
}),
...dateRange.map(mapAgeRange),
...dateRange.map(createCohortFolderAgeRange),
],
},
],
@@ -174,36 +144,42 @@ export function createPartialOptions({ brk }) {
{
name: "Less Than",
tree: [
createCohortFolderBasicWithMarketCap(ctx, {
createCohortFolderBasicWithMarketCap({
name: "Compare",
title: "Max Size",
list: utxosUnderAmount,
}),
...utxosUnderAmount.map(mapBasicWithMarketCap),
...utxosUnderAmount.map(
createCohortFolderBasicWithMarketCap,
),
],
},
// More Than (≥ X sats)
{
name: "More Than",
tree: [
createCohortFolderBasicWithMarketCap(ctx, {
createCohortFolderBasicWithMarketCap({
name: "Compare",
title: "Min Size",
list: utxosAboveAmount,
}),
...utxosAboveAmount.map(mapBasicWithMarketCap),
...utxosAboveAmount.map(
createCohortFolderBasicWithMarketCap,
),
],
},
// Range
{
name: "Range",
tree: [
createCohortFolderBasicWithoutMarketCap(ctx, {
createCohortFolderBasicWithoutMarketCap({
name: "Compare",
title: "Size Ranges",
list: utxosAmountRanges,
}),
...utxosAmountRanges.map(mapBasicWithoutMarketCap),
...utxosAmountRanges.map(
createCohortFolderBasicWithoutMarketCap,
),
],
},
],
@@ -217,36 +193,36 @@ export function createPartialOptions({ brk }) {
{
name: "Less Than",
tree: [
createAddressCohortFolder(ctx, {
createAddressCohortFolder({
name: "Compare",
title: "Max Balance",
list: addressesUnderAmount,
}),
...addressesUnderAmount.map(mapAddressCohorts),
...addressesUnderAmount.map(createAddressCohortFolder),
],
},
// More Than (≥ X sats)
{
name: "More Than",
tree: [
createAddressCohortFolder(ctx, {
createAddressCohortFolder({
name: "Compare",
title: "Min Balance",
list: addressesAboveAmount,
}),
...addressesAboveAmount.map(mapAddressCohorts),
...addressesAboveAmount.map(createAddressCohortFolder),
],
},
// Range
{
name: "Range",
tree: [
createAddressCohortFolder(ctx, {
createAddressCohortFolder({
name: "Compare",
title: "Balance Ranges",
list: addressesAmountRanges,
}),
...addressesAmountRanges.map(mapAddressCohorts),
...addressesAmountRanges.map(createAddressCohortFolder),
],
},
],
@@ -256,13 +232,13 @@ export function createPartialOptions({ brk }) {
{
name: "Script Types",
tree: [
createCohortFolderAddress(ctx, {
createCohortFolderAddress({
name: "Compare",
title: "Script Types",
list: typeAddressable,
}),
...typeAddressable.map(mapAddress),
...typeOther.map(mapWithoutRelative),
...typeAddressable.map(createCohortFolderAddress),
...typeOther.map(createCohortFolderWithoutRelative),
],
},
@@ -270,12 +246,12 @@ export function createPartialOptions({ brk }) {
{
name: "Epochs",
tree: [
createCohortFolderBasicWithoutMarketCap(ctx, {
createCohortFolderBasicWithoutMarketCap({
name: "Compare",
title: "Epochs",
list: epoch,
}),
...epoch.map(mapBasicWithoutMarketCap),
...epoch.map(createCohortFolderBasicWithoutMarketCap),
],
},
@@ -283,12 +259,12 @@ export function createPartialOptions({ brk }) {
{
name: "Years",
tree: [
createCohortFolderBasicWithoutMarketCap(ctx, {
createCohortFolderBasicWithoutMarketCap({
name: "Compare",
title: "Years",
list: year,
}),
...year.map(mapBasicWithoutMarketCap),
...year.map(createCohortFolderBasicWithoutMarketCap),
],
},
],
@@ -297,11 +273,11 @@ export function createPartialOptions({ brk }) {
// Frameworks section
{
name: "Frameworks",
tree: [createCointimeSection(ctx)],
tree: [createCointimeSection()],
},
// Investing section
createInvestingSection(ctx),
createInvestingSection(),
],
},

View File

@@ -1,6 +1,6 @@
/** Series helpers for creating chart series blueprints */
import { colors } from "../chart/colors.js";
import { colors } from "../utils/colors.js";
import { Unit } from "../utils/units.js";
// ============================================================================
@@ -47,13 +47,12 @@ export function price({
/**
* Create percentile series (max/min/median/pct75/pct25/pct90/pct10) from any stats pattern
* @param {Colors} colors
* @param {StatsPattern<any> | BaseStatsPattern<any> | FullStatsPattern<any> | AnyStatsPattern} pattern
* @param {Unit} unit
* @param {string} title
* @returns {AnyFetchedSeriesBlueprint[]}
*/
function percentileSeries(colors, pattern, unit, title) {
function percentileSeries(pattern, unit, title) {
const { stat } = colors;
return [
dots({
@@ -212,7 +211,7 @@ export function candlestick({
metric,
name,
key,
colors,
defaultActive,
unit,
options,
@@ -222,7 +221,7 @@ export function candlestick({
metric,
title: name,
key,
colors,
unit,
defaultActive,
options,
@@ -306,7 +305,6 @@ export function histogram({
/**
* Create series from a BaseStatsPattern (base + avg + percentiles, NO sum)
* @param {Colors} colors
* @param {Object} args
* @param {BaseStatsPattern<any>} args.pattern
* @param {Unit} args.unit
@@ -315,10 +313,13 @@ export function histogram({
* @param {boolean} [args.avgActive]
* @returns {AnyFetchedSeriesBlueprint[]}
*/
export function fromBaseStatsPattern(
colors,
{ pattern, unit, title = "", baseColor, avgActive = true },
) {
export function fromBaseStatsPattern({
pattern,
unit,
title = "",
baseColor,
avgActive = true,
}) {
const { stat } = colors;
return [
dots({
@@ -334,20 +335,19 @@ export function fromBaseStatsPattern(
unit,
defaultActive: avgActive,
}),
...percentileSeries(colors, pattern, unit, title),
...percentileSeries(pattern, unit, title),
];
}
/**
* Create series from any pattern with avg + percentiles (works with StatsPattern, SumStatsPattern, etc.)
* @param {Colors} colors
* @param {Object} args
* @param {StatsPattern<any> | BaseStatsPattern<any> | FullStatsPattern<any> | AnyStatsPattern} args.pattern
* @param {Unit} args.unit
* @param {string} [args.title]
* @returns {AnyFetchedSeriesBlueprint[]}
*/
export function fromStatsPattern(colors, { pattern, unit, title = "" }) {
export function fromStatsPattern({ pattern, unit, title = "" }) {
return [
{
type: "Dots",
@@ -355,7 +355,7 @@ export function fromStatsPattern(colors, { pattern, unit, title = "" }) {
title: `${title} avg`.trim(),
unit,
},
...percentileSeries(colors, pattern, unit, title),
...percentileSeries(pattern, unit, title),
];
}
@@ -365,9 +365,9 @@ export function fromStatsPattern(colors, { pattern, unit, title = "" }) {
* @returns {AnyFetchedSeriesBlueprint[]}
*/
export const distributionBtcSatsUsd = (source) => [
...fromStatsPattern(colors, { pattern: source.bitcoin, unit: Unit.btc }),
...fromStatsPattern(colors, { pattern: source.sats, unit: Unit.sats }),
...fromStatsPattern(colors, { pattern: source.dollars, unit: Unit.usd }),
...fromStatsPattern({ pattern: source.bitcoin, unit: Unit.btc }),
...fromStatsPattern({ pattern: source.sats, unit: Unit.sats }),
...fromStatsPattern({ pattern: source.dollars, unit: Unit.usd }),
];
/**
@@ -408,12 +408,11 @@ export function fromSupplyPattern({ pattern, title, color }) {
/**
* Create distribution series (avg + percentiles)
* @param {Colors} colors
* @param {StatsPattern<any> | BaseStatsPattern<any> | FullStatsPattern<any> | AnyStatsPattern} pattern
* @param {Unit} unit
* @returns {AnyFetchedSeriesBlueprint[]}
*/
function distributionSeries(colors, pattern, unit) {
function distributionSeries(pattern, unit) {
const { stat } = colors;
return [
dots({ metric: pattern.average, name: "avg", color: stat.avg, unit }),
@@ -506,14 +505,13 @@ function btcSatsUsdSeries({ metrics, name, color, defaultActive }) {
/**
* Split pattern with base + sum + distribution + cumulative into 3 charts
* @param {Colors} colors
* @param {Object} args
* @param {FullStatsPattern<any>} args.pattern
* @param {string} args.title
* @param {Unit} args.unit
* @returns {PartialOptionsTree}
*/
export function chartsFromFull(colors, { pattern, title, unit }) {
export function chartsFromFull({ pattern, title, unit }) {
return [
{
name: "Sum",
@@ -526,7 +524,7 @@ export function chartsFromFull(colors, { pattern, title, unit }) {
{
name: "Distribution",
title: `${title} Distribution`,
bottom: distributionSeries(colors, pattern, unit),
bottom: distributionSeries(pattern, unit),
},
{
name: "Cumulative",
@@ -538,14 +536,13 @@ export function chartsFromFull(colors, { pattern, title, unit }) {
/**
* Split pattern with sum + distribution + cumulative into 3 charts (no base)
* @param {Colors} colors
* @param {Object} args
* @param {AnyStatsPattern} args.pattern
* @param {string} args.title
* @param {Unit} args.unit
* @returns {PartialOptionsTree}
*/
export function chartsFromSum(colors, { pattern, title, unit }) {
export function chartsFromSum({ pattern, title, unit }) {
const { stat } = colors;
return [
{
@@ -556,7 +553,7 @@ export function chartsFromSum(colors, { pattern, title, unit }) {
{
name: "Distribution",
title: `${title} Distribution`,
bottom: distributionSeries(colors, pattern, unit),
bottom: distributionSeries(pattern, unit),
},
{
name: "Cumulative",
@@ -631,13 +628,12 @@ export function chartsFromValue({ pattern, title, color }) {
/**
* Split btc/sats/usd pattern with full stats into 3 charts
* @param {Colors} colors
* @param {Object} args
* @param {CoinbasePattern} args.pattern
* @param {string} args.title
* @returns {PartialOptionsTree}
*/
export function chartsFromValueFull(colors, { pattern, title }) {
export function chartsFromValueFull({ pattern, title }) {
return [
{
name: "Sum",
@@ -665,9 +661,9 @@ export function chartsFromValueFull(colors, { pattern, title }) {
name: "Distribution",
title: `${title} Distribution`,
bottom: [
...distributionSeries(colors, pattern.bitcoin, Unit.btc),
...distributionSeries(colors, pattern.sats, Unit.sats),
...distributionSeries(colors, pattern.dollars, Unit.usd),
...distributionSeries(pattern.bitcoin, Unit.btc),
...distributionSeries(pattern.sats, Unit.sats),
...distributionSeries(pattern.dollars, Unit.usd),
],
},
{

View File

@@ -3,14 +3,15 @@
import { Unit } from "../utils/units.js";
import { line, baseline, price } from "./series.js";
import { priceLine, priceLines } from "./constants.js";
import { colors } from "../utils/colors.js";
/**
* Create a title formatter for chart titles
* @param {string} [cohortTitle]
* @returns {(metric: string) => string}
*/
export const formatCohortTitle = (cohortTitle) =>
(metric) => cohortTitle ? `${metric}: ${cohortTitle}` : metric;
export const formatCohortTitle = (cohortTitle) => (metric) =>
cohortTitle ? `${metric}: ${cohortTitle}` : metric;
/**
* Create sats/btc/usd line series from a pattern with .sats/.bitcoin/.dollars
@@ -23,9 +24,21 @@ export const formatCohortTitle = (cohortTitle) =>
*/
export function satsBtcUsd({ pattern, name, color, defaultActive }) {
return [
line({ metric: pattern.bitcoin, name, color, unit: Unit.btc, defaultActive }),
line({
metric: pattern.bitcoin,
name,
color,
unit: Unit.btc,
defaultActive,
}),
line({ metric: pattern.sats, name, color, unit: Unit.sats, defaultActive }),
line({ metric: pattern.dollars, name, color, unit: Unit.usd, defaultActive }),
line({
metric: pattern.dollars,
name,
color,
unit: Unit.usd,
defaultActive,
}),
];
}
@@ -41,7 +54,11 @@ export function satsBtcUsd({ pattern, name, color, defaultActive }) {
*/
export function satsBtcUsdFrom({ source, key, name, color, defaultActive }) {
return satsBtcUsd({
pattern: { bitcoin: source.bitcoin[key], sats: source.sats[key], dollars: source.dollars[key] },
pattern: {
bitcoin: source.bitcoin[key],
sats: source.sats[key],
dollars: source.dollars[key],
},
name,
color,
defaultActive,
@@ -58,9 +75,19 @@ export function satsBtcUsdFrom({ source, key, name, color, defaultActive }) {
* @param {boolean} [args.defaultActive]
* @returns {FetchedLineSeriesBlueprint[]}
*/
export function satsBtcUsdFromFull({ source, key, name, color, defaultActive }) {
export function satsBtcUsdFromFull({
source,
key,
name,
color,
defaultActive,
}) {
return satsBtcUsd({
pattern: { bitcoin: source.bitcoin[key], sats: source.sats[key], dollars: source.dollars[key] },
pattern: {
bitcoin: source.bitcoin[key],
sats: source.sats[key],
dollars: source.dollars[key],
},
name,
color,
defaultActive,
@@ -69,7 +96,6 @@ export function satsBtcUsdFromFull({ source, key, name, color, defaultActive })
/**
* Create coinbase/subsidy/fee series from separate sources
* @param {Colors} colors
* @param {Object} args
* @param {AnyValuePatternType} args.coinbase
* @param {AnyValuePatternType} args.subsidy
@@ -77,20 +103,29 @@ export function satsBtcUsdFromFull({ source, key, name, color, defaultActive })
* @param {'sum' | 'cumulative'} args.key
* @returns {FetchedLineSeriesBlueprint[]}
*/
export function revenueBtcSatsUsd(colors, { coinbase, subsidy, fee, key }) {
export function revenueBtcSatsUsd({ coinbase, subsidy, fee, key }) {
return [
...satsBtcUsdFrom({ source: coinbase, key, name: "Coinbase", color: colors.orange }),
...satsBtcUsdFrom({ source: subsidy, key, name: "Subsidy", color: colors.lime }),
...satsBtcUsdFrom({
source: coinbase,
key,
name: "Coinbase",
color: colors.orange,
}),
...satsBtcUsdFrom({
source: subsidy,
key,
name: "Subsidy",
color: colors.lime,
}),
...satsBtcUsdFrom({ source: fee, key, name: "Fees", color: colors.cyan }),
];
}
/**
* Build percentile USD mappings from a ratio pattern
* @param {Colors} colors
* @param {AnyRatioPattern} ratio
*/
export function percentileUsdMap(colors, ratio) {
export function percentileUsdMap(ratio) {
return /** @type {const} */ ([
{ name: "pct95", prop: ratio.ratioPct95Usd, color: colors.fuchsia },
{ name: "pct5", prop: ratio.ratioPct5Usd, color: colors.cyan },
@@ -103,10 +138,9 @@ export function percentileUsdMap(colors, ratio) {
/**
* Build percentile ratio mappings from a ratio pattern
* @param {Colors} colors
* @param {AnyRatioPattern} ratio
*/
export function percentileMap(colors, ratio) {
export function percentileMap(ratio) {
return /** @type {const} */ ([
{ name: "pct95", prop: ratio.ratioPct95, color: colors.fuchsia },
{ name: "pct5", prop: ratio.ratioPct5, color: colors.cyan },
@@ -132,10 +166,9 @@ export function sdPatterns(ratio) {
/**
* Build SD band mappings from an SD pattern
* @param {Colors} colors
* @param {Ratio1ySdPattern} sd
*/
export function sdBandsUsd(colors, sd) {
export function sdBandsUsd(sd) {
return /** @type {const} */ ([
{ name: "0σ", prop: sd._0sdUsd, color: colors.lime },
{ name: "+0.5σ", prop: sd.p05sdUsd, color: colors.yellow },
@@ -155,10 +188,9 @@ export function sdBandsUsd(colors, sd) {
/**
* Build SD band mappings (ratio) from an SD pattern
* @param {Colors} colors
* @param {Ratio1ySdPattern} sd
*/
export function sdBandsRatio(colors, sd) {
export function sdBandsRatio(sd) {
return /** @type {const} */ ([
{ name: "0σ", prop: sd.sma, color: colors.lime },
{ name: "+0.5σ", prop: sd.p05sd, color: colors.yellow },
@@ -178,10 +210,9 @@ export function sdBandsRatio(colors, sd) {
/**
* Build ratio SMA series from a ratio pattern
* @param {Colors} colors
* @param {AnyRatioPattern} ratio
*/
export function ratioSmas(colors, ratio) {
export function ratioSmas(ratio) {
return /** @type {const} */ ([
{ name: "1w SMA", metric: ratio.ratio1wSma, color: colors.lime },
{ name: "1m SMA", metric: ratio.ratio1mSma, color: colors.teal },
@@ -194,7 +225,6 @@ export function ratioSmas(colors, ratio) {
/**
* Create ratio chart from ActivePriceRatioPattern
* @param {PartialContext} ctx
* @param {Object} args
* @param {(metric: string) => string} args.title
* @param {AnyPricePattern} args.pricePattern - The price pattern to show in top pane
@@ -203,15 +233,13 @@ export function ratioSmas(colors, ratio) {
* @param {string} [args.name] - Optional name override (default: "ratio")
* @returns {PartialChartOption}
*/
export function createRatioChart(ctx, { title, pricePattern, ratio, color, name }) {
const { colors } = ctx;
export function createRatioChart({ title, pricePattern, ratio, color, name }) {
return {
name: name ?? "ratio",
title: title(name ?? "Ratio"),
top: [
price({ metric: pricePattern, name: "Price", color }),
...percentileUsdMap(colors, ratio).map(({ name, prop, color }) =>
...percentileUsdMap(ratio).map(({ name, prop, color }) =>
price({
metric: prop,
name,
@@ -228,10 +256,10 @@ export function createRatioChart(ctx, { title, pricePattern, ratio, color, name
unit: Unit.ratio,
base: 1,
}),
...ratioSmas(colors, ratio).map(({ name, metric, color }) =>
...ratioSmas(ratio).map(({ name, metric, color }) =>
line({ metric, name, color, unit: Unit.ratio, defaultActive: false }),
),
...percentileMap(colors, ratio).map(({ name, prop, color }) =>
...percentileMap(ratio).map(({ name, prop, color }) =>
line({
metric: prop,
name,
@@ -247,7 +275,6 @@ export function createRatioChart(ctx, { title, pricePattern, ratio, color, name
/**
* Create ZScores folder from ActivePriceRatioPattern
* @param {PartialContext} ctx
* @param {Object} args
* @param {(suffix: string) => string} args.formatTitle - Function that takes metric suffix and returns full title
* @param {string} args.legend
@@ -256,11 +283,13 @@ export function createRatioChart(ctx, { title, pricePattern, ratio, color, name
* @param {Color} args.color
* @returns {PartialOptionsGroup}
*/
export function createZScoresFolder(
ctx,
{ formatTitle, legend, pricePattern, ratio, color },
) {
const { colors } = ctx;
export function createZScoresFolder({
formatTitle,
legend,
pricePattern,
ratio,
color,
}) {
const sdPats = sdPatterns(ratio);
return {
@@ -322,7 +351,6 @@ export function createZScoresFolder(
unit: Unit.sd,
}),
...priceLines({
ctx,
unit: Unit.sd,
numbers: [0, 1, -1, 2, -2, 3, -3],
defaultActive: false,
@@ -334,14 +362,13 @@ export function createZScoresFolder(
title: formatTitle(`${titleAddon ? `${titleAddon} ` : ""}Z-Score`),
top: [
price({ metric: pricePattern, name: legend, color }),
...sdBandsUsd(colors, sd).map(
({ name: bandName, prop, color: bandColor }) =>
price({
metric: prop,
name: bandName,
color: bandColor,
defaultActive: false,
}),
...sdBandsUsd(sd).map(({ name: bandName, prop, color: bandColor }) =>
price({
metric: prop,
name: bandName,
color: bandColor,
defaultActive: false,
}),
),
],
bottom: [
@@ -362,7 +389,7 @@ export function createZScoresFolder(
color: colors.gray,
unit: Unit.percentage,
}),
...sdBandsRatio(colors, sd).map(
...sdBandsRatio(sd).map(
({ name: bandName, prop, color: bandColor }) =>
line({
metric: prop,
@@ -373,11 +400,9 @@ export function createZScoresFolder(
}),
),
priceLine({
ctx,
unit: Unit.sd,
}),
...priceLines({
ctx,
unit: Unit.sd,
numbers: [1, -1, 2, -2, 3, -3],
defaultActive: false,
@@ -391,7 +416,6 @@ export function createZScoresFolder(
/**
* Create price + ratio + z-scores charts - flat array
* Unified helper for averages, distribution, and other price-based metrics
* @param {PartialContext} ctx
* @param {Object} args
* @param {string} args.context - Context string for ratio/z-scores titles (e.g., "1 Week SMA", "STH")
* @param {string} args.legend - Legend name for the price series
@@ -404,23 +428,38 @@ export function createZScoresFolder(
* @param {FetchedPriceSeriesBlueprint[]} [args.priceReferences] - Optional additional price series to show in Price chart
* @returns {PartialOptionsTree}
*/
export function createPriceRatioCharts(ctx, { context, legend, pricePattern, ratio, color, ratioName, priceTitle, zScoresSuffix, priceReferences }) {
export function createPriceRatioCharts({
context,
legend,
pricePattern,
ratio,
color,
ratioName,
priceTitle,
zScoresSuffix,
priceReferences,
}) {
const titleFn = formatCohortTitle(context);
const zScoresTitleFn = zScoresSuffix ? formatCohortTitle(`${context} ${zScoresSuffix}`) : titleFn;
const zScoresTitleFn = zScoresSuffix
? formatCohortTitle(`${context} ${zScoresSuffix}`)
: titleFn;
return [
{
name: "Price",
title: priceTitle ?? context,
top: [price({ metric: pricePattern, name: legend, color }), ...(priceReferences ?? [])],
top: [
price({ metric: pricePattern, name: legend, color }),
...(priceReferences ?? []),
],
},
createRatioChart(ctx, {
createRatioChart({
title: titleFn,
pricePattern,
ratio,
color,
name: ratioName,
}),
createZScoresFolder(ctx, {
createZScoresFolder({
formatTitle: zScoresTitleFn,
legend,
pricePattern,

View File

@@ -3,8 +3,9 @@ import { chartElement } from "../utils/elements.js";
import { serdeChartableIndex } from "../utils/serde.js";
import { Unit } from "../utils/units.js";
import { createChart } from "../chart/index.js";
import { colors } from "../chart/colors.js";
import { colors } from "../utils/colors.js";
import { webSockets } from "../utils/ws.js";
import { brk } from "../client.js";
const ONE_BTC_IN_SATS = 100_000_000;
@@ -19,10 +20,7 @@ export function setOption(opt) {
_setOption(opt);
}
/**
* @param {BrkClient} brk
*/
export function init(brk) {
export function init() {
chartElement.append(createShadow("left"));
chartElement.append(createShadow("right"));

View File

@@ -10,11 +10,11 @@
*
* @import { SingleValueData, CandlestickData, Series, AnySeries, ISeries, HistogramData, LineData, BaselineData, LineSeriesPartialOptions, BaselineSeriesPartialOptions, HistogramSeriesPartialOptions, CandlestickSeriesPartialOptions, Chart, Legend } from "./chart/index.js"
*
* @import { Color, ColorName, Colors } from "./chart/colors.js"
* @import { Color, ColorName, Colors } from "./utils/colors.js"
*
* @import { WebSockets } from "./utils/ws.js"
*
* @import { Option, PartialChartOption, ChartOption, AnyPartialOption, ProcessedOptionAddons, OptionsTree, SimulationOption, AnySeriesBlueprint, SeriesType, AnyFetchedSeriesBlueprint, TableOption, ExplorerOption, UrlOption, PartialOptionsGroup, OptionsGroup, PartialOptionsTree, UtxoCohortObject, AddressCohortObject, CohortObject, CohortGroupObject, FetchedLineSeriesBlueprint, FetchedBaselineSeriesBlueprint, FetchedHistogramSeriesBlueprint, PartialContext, PatternAll, PatternFull, PatternWithAdjusted, PatternWithPercentiles, PatternBasic, PatternBasicWithMarketCap, PatternBasicWithoutMarketCap, PatternWithoutRelative, CohortAll, CohortFull, CohortWithAdjusted, CohortWithPercentiles, CohortBasic, CohortBasicWithMarketCap, CohortBasicWithoutMarketCap, CohortWithoutRelative, CohortAddress, CohortLongTerm, CohortAgeRange, CohortMinAge, CohortGroupFull, CohortGroupWithAdjusted, CohortGroupWithPercentiles, CohortGroupLongTerm, CohortGroupAgeRange, CohortGroupBasic, CohortGroupBasicWithMarketCap, CohortGroupBasicWithoutMarketCap, CohortGroupWithoutRelative, CohortGroupMinAge, CohortGroupAddress, UtxoCohortGroupObject, AddressCohortGroupObject, FetchedDotsSeriesBlueprint, FetchedCandlestickSeriesBlueprint, FetchedPriceSeriesBlueprint, AnyPricePattern, AnyValuePattern } from "./options/partial.js"
* @import { Option, PartialChartOption, ChartOption, AnyPartialOption, ProcessedOptionAddons, OptionsTree, SimulationOption, AnySeriesBlueprint, SeriesType, AnyFetchedSeriesBlueprint, TableOption, ExplorerOption, UrlOption, PartialOptionsGroup, OptionsGroup, PartialOptionsTree, UtxoCohortObject, AddressCohortObject, CohortObject, CohortGroupObject, FetchedLineSeriesBlueprint, FetchedBaselineSeriesBlueprint, FetchedHistogramSeriesBlueprint, PatternAll, PatternFull, PatternWithAdjusted, PatternWithPercentiles, PatternBasic, PatternBasicWithMarketCap, PatternBasicWithoutMarketCap, PatternWithoutRelative, CohortAll, CohortFull, CohortWithAdjusted, CohortWithPercentiles, CohortBasic, CohortBasicWithMarketCap, CohortBasicWithoutMarketCap, CohortWithoutRelative, CohortAddress, CohortLongTerm, CohortAgeRange, CohortMinAge, CohortGroupFull, CohortGroupWithAdjusted, CohortGroupWithPercentiles, CohortGroupLongTerm, CohortGroupAgeRange, CohortGroupBasic, CohortGroupBasicWithMarketCap, CohortGroupBasicWithoutMarketCap, CohortGroupWithoutRelative, CohortGroupMinAge, CohortGroupAddress, UtxoCohortGroupObject, AddressCohortGroupObject, FetchedDotsSeriesBlueprint, FetchedCandlestickSeriesBlueprint, FetchedPriceSeriesBlueprint, AnyPricePattern, AnyValuePattern } from "./options/partial.js"
*
*
* @import { UnitObject as Unit } from "./utils/units.js"

View File

@@ -1,5 +1,5 @@
import { oklchToRgba } from "./oklch.js";
import { dark } from "../utils/theme.js";
import { oklchToRgba } from "../chart/oklch.js";
import { dark } from "./theme.js";
/** @type {Map<string, string>} */
const rgbaCache = new Map();
@@ -117,7 +117,6 @@ const baseColors = {
export const colors = {
...baseColors,
/** Semantic stat colors for pattern helpers */
stat: {
sum: blue,
cumulative: indigo,
@@ -131,7 +130,6 @@ export const colors = {
min: red,
},
/** Common time period colors */
time: {
_24h: pink,
_1w: red,
@@ -140,45 +138,121 @@ export const colors = {
all: teal,
},
/** DCA period colors by term */
dcaPeriods: {
// Short term
_1w: red,
_1m: orange,
_3m: yellow,
_6m: lime,
// Medium term
_1y: green,
_2y: teal,
_3y: cyan,
// Long term
_4y: sky,
_5y: blue,
_6y: indigo,
_8y: violet,
_10y: purple,
term: {
short: yellow,
long: fuchsia,
},
/** DCA year colors by halving epoch */
dcaYears: {
// Epoch 5 (2024+)
_2026: rose,
_2025: fuchsia,
_2024: purple,
// Epoch 4 (2020-2023)
_2023: violet,
_2022: blue,
_2021: sky,
_2020: teal,
// Epoch 3 (2016-2019)
_2019: green,
_2018: yellow,
_2017: orange,
_2016: red,
_2015: pink,
age: {
_1d: red,
_1w: orange,
_1m: yellow,
_2m: lime,
_3m: green,
_4m: teal,
_5m: cyan,
_6m: blue,
_1y: indigo,
_2y: violet,
_3y: purple,
_4y: fuchsia,
_5y: pink,
_6y: rose,
_7y: red,
_8y: orange,
_10y: yellow,
_12y: lime,
_15y: green,
},
ageRange: {
upTo1h: rose,
_1hTo1d: pink,
_1dTo1w: red,
_1wTo1m: orange,
_1mTo2m: yellow,
_2mTo3m: yellow,
_3mTo4m: lime,
_4mTo5m: lime,
_5mTo6m: lime,
_6mTo1y: green,
_1yTo2y: cyan,
_2yTo3y: blue,
_3yTo4y: indigo,
_4yTo5y: violet,
_5yTo6y: purple,
_6yTo7y: purple,
_7yTo8y: fuchsia,
_8yTo10y: fuchsia,
_10yTo12y: pink,
_12yTo15y: red,
from15y: orange,
},
amount: {
_1sat: orange,
_10sats: orange,
_100sats: yellow,
_1kSats: lime,
_10kSats: green,
_100kSats: cyan,
_1mSats: blue,
_10mSats: indigo,
_1btc: purple,
_10btc: violet,
_100btc: fuchsia,
_1kBtc: pink,
_10kBtc: red,
_100kBtc: orange,
},
amountRange: {
_0sats: red,
_1satTo10sats: orange,
_10satsTo100sats: yellow,
_100satsTo1kSats: lime,
_1kSatsTo10kSats: green,
_10kSatsTo100kSats: cyan,
_100kSatsTo1mSats: blue,
_1mSatsTo10mSats: indigo,
_10mSatsTo1btc: purple,
_1btcTo10btc: violet,
_10btcTo100btc: fuchsia,
_100btcTo1kBtc: pink,
_1kBtcTo10kBtc: red,
_10kBtcTo100kBtc: orange,
_100kBtcOrMore: yellow,
},
epoch: {
_0: red,
_1: yellow,
_2: orange,
_3: lime,
_4: green,
},
year: {
_2009: red,
_2010: orange,
_2011: amber,
_2012: yellow,
_2013: lime,
_2014: green,
_2015: teal,
_2016: cyan,
_2017: sky,
_2018: blue,
_2019: indigo,
_2020: violet,
_2021: purple,
_2022: fuchsia,
_2023: pink,
_2024: rose,
_2025: red,
_2026: orange,
},
/** Returns/lookback colors by period */
returns: {
_1d: red,
_1w: orange,
@@ -195,29 +269,42 @@ export const colors = {
_10y: fuchsia,
},
/** Moving average colors by period */
ma: {
_1w: red,
_8d: orange,
_12d: amber,
_13d: amber,
_21d: yellow,
_13d: yellow,
_21d: avocado,
_26d: lime,
_1m: lime,
_34d: green,
_55d: emerald,
_89d: teal,
_111d: cyan,
_144d: sky,
_200d: blue,
_350d: indigo,
_1y: violet,
_2y: purple,
_200w: fuchsia,
_4y: pink,
_1m: green,
_34d: emerald,
_55d: teal,
_89d: cyan,
_111d: sky,
_144d: blue,
_200d: indigo,
_350d: violet,
_1y: purple,
_2y: fuchsia,
_200w: pink,
_4y: rose,
},
dca: {
_1w: red,
_1m: orange,
_3m: yellow,
_6m: lime,
_1y: green,
_2y: teal,
_3y: cyan,
_4y: sky,
_5y: blue,
_6y: indigo,
_8y: violet,
_10y: purple,
},
/** Script type colors (oldest to newest) */
scriptType: {
p2pk65: red,
p2pk33: orange,