mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-24 06:39:58 -07:00
website: snapshot
This commit is contained in:
@@ -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) => {
|
||||
|
||||
@@ -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";
|
||||
|
||||
8
website/scripts/client.js
Normal file
8
website/scripts/client.js
Normal 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 };
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
})),
|
||||
],
|
||||
|
||||
@@ -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",
|
||||
};
|
||||
@@ -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";
|
||||
@@ -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],
|
||||
];
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
@@ -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,
|
||||
}),
|
||||
]),
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 }),
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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(),
|
||||
],
|
||||
},
|
||||
|
||||
|
||||
@@ -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),
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"));
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
Reference in New Issue
Block a user