mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-27 08:09:58 -07:00
global: snapshot
This commit is contained in:
@@ -9,7 +9,7 @@ export const canCapture = !ios || canShare;
|
||||
* @param {HTMLCanvasElement} args.screenshot
|
||||
* @param {number} args.chartWidth
|
||||
* @param {HTMLElement} args.parent
|
||||
* @param {{ top: { element: HTMLElement }, bottom: { element: HTMLElement } }} args.legends
|
||||
* @param {{ element: HTMLElement }[]} args.legends
|
||||
*/
|
||||
export function capture({ screenshot, chartWidth, parent, legends }) {
|
||||
const dpr = screenshot.width / chartWidth;
|
||||
@@ -22,8 +22,8 @@ export function capture({ screenshot, chartWidth, parent, legends }) {
|
||||
|
||||
const title = (parent.querySelector("h1")?.textContent ?? "").toUpperCase();
|
||||
const hasTitle = title.length > 0;
|
||||
const hasTopLegend = legends.top.element.children.length > 0;
|
||||
const hasBottomLegend = legends.bottom.element.children.length > 0;
|
||||
const hasTopLegend = legends[0].element.children.length > 0;
|
||||
const hasBottomLegend = legends[1].element.children.length > 0;
|
||||
const titleOffset = hasTitle ? titleHeight : 0;
|
||||
const topLegendOffset = hasTopLegend ? legendHeight : 0;
|
||||
const bottomOffset = hasBottomLegend ? legendHeight : 0;
|
||||
@@ -80,7 +80,7 @@ export function capture({ screenshot, chartWidth, parent, legends }) {
|
||||
|
||||
// Top legend
|
||||
if (hasTopLegend) {
|
||||
drawLegend(legends.top.element, pad + titleOffset + topLegendOffset / 2);
|
||||
drawLegend(legends[0].element, pad + titleOffset + topLegendOffset / 2);
|
||||
}
|
||||
|
||||
// Chart
|
||||
@@ -89,7 +89,7 @@ export function capture({ screenshot, chartWidth, parent, legends }) {
|
||||
// Bottom legend
|
||||
if (hasBottomLegend) {
|
||||
drawLegend(
|
||||
legends.bottom.element,
|
||||
legends[1].element,
|
||||
pad +
|
||||
titleOffset +
|
||||
topLegendOffset +
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -9,9 +9,13 @@ export function createLegend() {
|
||||
scroller.append(items);
|
||||
element.append(scroller);
|
||||
|
||||
scroller.addEventListener("wheel", (e) => e.stopPropagation());
|
||||
scroller.addEventListener("touchstart", (e) => e.stopPropagation());
|
||||
scroller.addEventListener("touchmove", (e) => e.stopPropagation());
|
||||
/** @param {HTMLElement} el */
|
||||
function captureScroll(el) {
|
||||
el.addEventListener("wheel", (e) => e.stopPropagation());
|
||||
el.addEventListener("touchstart", (e) => e.stopPropagation());
|
||||
el.addEventListener("touchmove", (e) => e.stopPropagation());
|
||||
}
|
||||
captureScroll(items);
|
||||
|
||||
/** @type {AnySeries | null} */
|
||||
let hoveredSeries = null;
|
||||
@@ -37,6 +41,7 @@ export function createLegend() {
|
||||
let prefix = null;
|
||||
const separator = window.document.createElement("span");
|
||||
separator.textContent = "|";
|
||||
captureScroll(separator);
|
||||
|
||||
return {
|
||||
element,
|
||||
@@ -47,6 +52,7 @@ export function createLegend() {
|
||||
if (prefix) prefix.replaceWith(el);
|
||||
else scroller.insertBefore(el, items);
|
||||
prefix = el;
|
||||
captureScroll(el);
|
||||
el.after(separator);
|
||||
},
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { BrkClient } from "./modules/brk-client/index.js";
|
||||
|
||||
const brk = new BrkClient("https://bitview.space");
|
||||
// const brk = new BrkClient("/");
|
||||
// const brk = new BrkClient("https://bitview.space");
|
||||
const brk = new BrkClient("/");
|
||||
|
||||
console.log(`VERSION = ${brk.VERSION}`);
|
||||
|
||||
|
||||
@@ -396,7 +396,7 @@ export function createCointimeSection() {
|
||||
line({
|
||||
metric: reserveRisk.vocdd365dMedian,
|
||||
name: "365d Median",
|
||||
color: colors.ma._1y,
|
||||
color: colors.time._1y,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
|
||||
@@ -367,21 +367,21 @@ export function createActivitySection({
|
||||
line({
|
||||
metric: tree.activity.sent14dEma.sats,
|
||||
name: "14d EMA",
|
||||
color: colors.ma._14d,
|
||||
color: colors.indicator.main,
|
||||
unit: Unit.sats,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: tree.activity.sent14dEma.bitcoin,
|
||||
name: "14d EMA",
|
||||
color: colors.ma._14d,
|
||||
color: colors.indicator.main,
|
||||
unit: Unit.btc,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: tree.activity.sent14dEma.dollars,
|
||||
name: "14d EMA",
|
||||
color: colors.ma._14d,
|
||||
color: colors.indicator.main,
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
@@ -814,13 +814,13 @@ function createSingleSellSideRiskSeries(tree) {
|
||||
line({
|
||||
metric: tree.realized.sellSideRiskRatio30dEma,
|
||||
name: "30d EMA",
|
||||
color: colors.ma._1m,
|
||||
color: colors.time._1m,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.sellSideRiskRatio7dEma,
|
||||
name: "7d EMA",
|
||||
color: colors.ma._1w,
|
||||
color: colors.time._1w,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
dots({
|
||||
|
||||
@@ -11,118 +11,30 @@
|
||||
*/
|
||||
|
||||
import { colors } from "../../utils/colors.js";
|
||||
import { entries } from "../../utils/array.js";
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { priceLines } from "../constants.js";
|
||||
import { line, price } from "../series.js";
|
||||
import { mapCohortsWithAll } from "../shared.js";
|
||||
|
||||
const ACTIVE_PCTS = new Set(["pct75", "pct50", "pct25"]);
|
||||
|
||||
/**
|
||||
* @param {PercentilesPattern} p
|
||||
* @param {(name: string) => string} [n]
|
||||
* @returns {FetchedPriceSeriesBlueprint[]}
|
||||
*/
|
||||
function createCorePercentileSeries(p, n = (x) => x) {
|
||||
return [
|
||||
price({
|
||||
metric: p.pct95,
|
||||
name: n("p95"),
|
||||
color: colors.pct._95,
|
||||
defaultActive: false,
|
||||
}),
|
||||
price({
|
||||
metric: p.pct90,
|
||||
name: n("p90"),
|
||||
color: colors.pct._90,
|
||||
defaultActive: false,
|
||||
}),
|
||||
price({
|
||||
metric: p.pct85,
|
||||
name: n("p85"),
|
||||
color: colors.pct._85,
|
||||
defaultActive: false,
|
||||
}),
|
||||
price({
|
||||
metric: p.pct80,
|
||||
name: n("p80"),
|
||||
color: colors.pct._80,
|
||||
defaultActive: false,
|
||||
}),
|
||||
price({ metric: p.pct75, name: n("p75"), color: colors.pct._75 }),
|
||||
price({
|
||||
metric: p.pct70,
|
||||
name: n("p70"),
|
||||
color: colors.pct._70,
|
||||
defaultActive: false,
|
||||
}),
|
||||
price({
|
||||
metric: p.pct65,
|
||||
name: n("p65"),
|
||||
color: colors.pct._65,
|
||||
defaultActive: false,
|
||||
}),
|
||||
price({
|
||||
metric: p.pct60,
|
||||
name: n("p60"),
|
||||
color: colors.pct._60,
|
||||
defaultActive: false,
|
||||
}),
|
||||
price({
|
||||
metric: p.pct55,
|
||||
name: n("p55"),
|
||||
color: colors.pct._55,
|
||||
defaultActive: false,
|
||||
}),
|
||||
price({ metric: p.pct50, name: n("p50"), color: colors.pct._50 }),
|
||||
price({
|
||||
metric: p.pct45,
|
||||
name: n("p45"),
|
||||
color: colors.pct._45,
|
||||
defaultActive: false,
|
||||
}),
|
||||
price({
|
||||
metric: p.pct40,
|
||||
name: n("p40"),
|
||||
color: colors.pct._40,
|
||||
defaultActive: false,
|
||||
}),
|
||||
price({
|
||||
metric: p.pct35,
|
||||
name: n("p35"),
|
||||
color: colors.pct._35,
|
||||
defaultActive: false,
|
||||
}),
|
||||
price({
|
||||
metric: p.pct30,
|
||||
name: n("p30"),
|
||||
color: colors.pct._30,
|
||||
defaultActive: false,
|
||||
}),
|
||||
price({ metric: p.pct25, name: n("p25"), color: colors.pct._25 }),
|
||||
price({
|
||||
metric: p.pct20,
|
||||
name: n("p20"),
|
||||
color: colors.pct._20,
|
||||
defaultActive: false,
|
||||
}),
|
||||
price({
|
||||
metric: p.pct15,
|
||||
name: n("p15"),
|
||||
color: colors.pct._15,
|
||||
defaultActive: false,
|
||||
}),
|
||||
price({
|
||||
metric: p.pct10,
|
||||
name: n("p10"),
|
||||
color: colors.pct._10,
|
||||
defaultActive: false,
|
||||
}),
|
||||
price({
|
||||
metric: p.pct05,
|
||||
name: n("p05"),
|
||||
color: colors.pct._05,
|
||||
defaultActive: false,
|
||||
}),
|
||||
];
|
||||
return entries(p)
|
||||
.reverse()
|
||||
.map(([key, metric], i, arr) =>
|
||||
price({
|
||||
metric,
|
||||
name: n(key.replace("pct", "p")),
|
||||
color: colors.at(i, arr.length),
|
||||
...(ACTIVE_PCTS.has(key) ? {} : { defaultActive: false }),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -136,13 +48,13 @@ function createSingleSummarySeriesBasic(cohort) {
|
||||
price({
|
||||
metric: tree.costBasis.max,
|
||||
name: "Max",
|
||||
color: colors.pct._100,
|
||||
color: colors.stat.max,
|
||||
defaultActive: false,
|
||||
}),
|
||||
price({
|
||||
metric: tree.costBasis.min,
|
||||
name: "Min",
|
||||
color: colors.pct._0,
|
||||
color: colors.stat.min,
|
||||
defaultActive: false,
|
||||
}),
|
||||
];
|
||||
@@ -160,26 +72,26 @@ function createSingleSummarySeriesWithPercentiles(cohort) {
|
||||
price({
|
||||
metric: tree.costBasis.max,
|
||||
name: "Max (p100)",
|
||||
color: colors.pct._100,
|
||||
color: colors.stat.max,
|
||||
defaultActive: false,
|
||||
}),
|
||||
price({
|
||||
metric: p.pct75,
|
||||
name: "Q3 (p75)",
|
||||
color: colors.pct._75,
|
||||
color: colors.stat.pct75,
|
||||
defaultActive: false,
|
||||
}),
|
||||
price({ metric: p.pct50, name: "Median (p50)", color: colors.pct._50 }),
|
||||
price({ metric: p.pct50, name: "Median (p50)", color: colors.stat.median }),
|
||||
price({
|
||||
metric: p.pct25,
|
||||
name: "Q1 (p25)",
|
||||
color: colors.pct._25,
|
||||
color: colors.stat.pct25,
|
||||
defaultActive: false,
|
||||
}),
|
||||
price({
|
||||
metric: tree.costBasis.min,
|
||||
name: "Min (p0)",
|
||||
color: colors.pct._0,
|
||||
color: colors.stat.min,
|
||||
defaultActive: false,
|
||||
}),
|
||||
];
|
||||
@@ -208,14 +120,14 @@ function createSingleByCoinSeries(cohort) {
|
||||
price({
|
||||
metric: cb.max,
|
||||
name: "p100",
|
||||
color: colors.pct._100,
|
||||
color: colors.stat.max,
|
||||
defaultActive: false,
|
||||
}),
|
||||
...createCorePercentileSeries(cb.percentiles),
|
||||
price({
|
||||
metric: cb.min,
|
||||
name: "p0",
|
||||
color: colors.pct._0,
|
||||
color: colors.stat.min,
|
||||
defaultActive: false,
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -67,68 +67,72 @@ export function buildCohortData() {
|
||||
};
|
||||
|
||||
// Max age cohorts (up to X time)
|
||||
const upToDate = entries(utxoCohorts.maxAge).map(([key, tree]) => {
|
||||
const upToDate = entries(utxoCohorts.maxAge).map(([key, tree], i, arr) => {
|
||||
const names = MAX_AGE_NAMES[key];
|
||||
return {
|
||||
name: names.short,
|
||||
title: `UTXOs ${names.long}`,
|
||||
color: colors.age[key],
|
||||
color: colors.at(i, arr.length),
|
||||
tree,
|
||||
};
|
||||
});
|
||||
|
||||
// Min age cohorts (from X time)
|
||||
const fromDate = entries(utxoCohorts.minAge).map(([key, tree]) => {
|
||||
const fromDate = entries(utxoCohorts.minAge).map(([key, tree], i, arr) => {
|
||||
const names = MIN_AGE_NAMES[key];
|
||||
return {
|
||||
name: names.short,
|
||||
title: `UTXOs ${names.long}`,
|
||||
color: colors.age[key],
|
||||
color: colors.at(i, arr.length),
|
||||
tree,
|
||||
};
|
||||
});
|
||||
|
||||
// Age range cohorts
|
||||
const dateRange = entries(utxoCohorts.ageRange).map(([key, tree]) => {
|
||||
const names = AGE_RANGE_NAMES[key];
|
||||
return {
|
||||
name: names.short,
|
||||
title: `UTXOs ${names.long}`,
|
||||
color: colors.ageRange[key],
|
||||
tree,
|
||||
};
|
||||
});
|
||||
const dateRange = entries(utxoCohorts.ageRange).map(
|
||||
([key, tree], i, arr) => {
|
||||
const names = AGE_RANGE_NAMES[key];
|
||||
return {
|
||||
name: names.short,
|
||||
title: `UTXOs ${names.long}`,
|
||||
color: colors.at(i, arr.length),
|
||||
tree,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
// Epoch cohorts
|
||||
const epoch = entries(utxoCohorts.epoch).map(([key, tree]) => {
|
||||
const epoch = entries(utxoCohorts.epoch).map(([key, tree], i, arr) => {
|
||||
const names = EPOCH_NAMES[key];
|
||||
return {
|
||||
name: names.short,
|
||||
title: names.long,
|
||||
color: colors.epoch[key],
|
||||
color: colors.at(i, arr.length),
|
||||
tree,
|
||||
};
|
||||
});
|
||||
|
||||
// UTXOs above amount
|
||||
const utxosAboveAmount = entries(utxoCohorts.geAmount).map(([key, tree]) => {
|
||||
const names = GE_AMOUNT_NAMES[key];
|
||||
return {
|
||||
name: names.short,
|
||||
title: `UTXOs ${names.long}`,
|
||||
color: colors.amount[key],
|
||||
tree,
|
||||
};
|
||||
});
|
||||
const utxosAboveAmount = entries(utxoCohorts.geAmount).map(
|
||||
([key, tree], i, arr) => {
|
||||
const names = GE_AMOUNT_NAMES[key];
|
||||
return {
|
||||
name: names.short,
|
||||
title: `UTXOs ${names.long}`,
|
||||
color: colors.at(i, arr.length),
|
||||
tree,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
// Addresses above amount
|
||||
const addressesAboveAmount = entries(addressCohorts.geAmount).map(
|
||||
([key, cohort]) => {
|
||||
([key, cohort], i, arr) => {
|
||||
const names = GE_AMOUNT_NAMES[key];
|
||||
return {
|
||||
name: names.short,
|
||||
title: `Addresses ${names.long}`,
|
||||
color: colors.amount[key],
|
||||
color: colors.at(i, arr.length),
|
||||
tree: cohort,
|
||||
addrCount: {
|
||||
count: cohort.addrCount,
|
||||
@@ -139,24 +143,26 @@ export function buildCohortData() {
|
||||
);
|
||||
|
||||
// UTXOs under amount
|
||||
const utxosUnderAmount = entries(utxoCohorts.ltAmount).map(([key, tree]) => {
|
||||
const names = LT_AMOUNT_NAMES[key];
|
||||
return {
|
||||
name: names.short,
|
||||
title: `UTXOs ${names.long}`,
|
||||
color: colors.amount[key],
|
||||
tree,
|
||||
};
|
||||
});
|
||||
const utxosUnderAmount = entries(utxoCohorts.ltAmount).map(
|
||||
([key, tree], i, arr) => {
|
||||
const names = LT_AMOUNT_NAMES[key];
|
||||
return {
|
||||
name: names.short,
|
||||
title: `UTXOs ${names.long}`,
|
||||
color: colors.at(i, arr.length),
|
||||
tree,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
// Addresses under amount
|
||||
const addressesUnderAmount = entries(addressCohorts.ltAmount).map(
|
||||
([key, cohort]) => {
|
||||
([key, cohort], i, arr) => {
|
||||
const names = LT_AMOUNT_NAMES[key];
|
||||
return {
|
||||
name: names.short,
|
||||
title: `Addresses ${names.long}`,
|
||||
color: colors.amount[key],
|
||||
color: colors.at(i, arr.length),
|
||||
tree: cohort,
|
||||
addrCount: {
|
||||
count: cohort.addrCount,
|
||||
@@ -168,12 +174,12 @@ export function buildCohortData() {
|
||||
|
||||
// UTXOs amount ranges
|
||||
const utxosAmountRanges = entries(utxoCohorts.amountRange).map(
|
||||
([key, tree]) => {
|
||||
([key, tree], i, arr) => {
|
||||
const names = AMOUNT_RANGE_NAMES[key];
|
||||
return {
|
||||
name: names.short,
|
||||
title: `UTXOs ${names.long}`,
|
||||
color: colors.amountRange[key],
|
||||
color: colors.at(i, arr.length),
|
||||
tree,
|
||||
};
|
||||
},
|
||||
@@ -181,12 +187,12 @@ export function buildCohortData() {
|
||||
|
||||
// Addresses amount ranges
|
||||
const addressesAmountRanges = entries(addressCohorts.amountRange).map(
|
||||
([key, cohort]) => {
|
||||
([key, cohort], i, arr) => {
|
||||
const names = AMOUNT_RANGE_NAMES[key];
|
||||
return {
|
||||
name: names.short,
|
||||
title: `Addresses ${names.long}`,
|
||||
color: colors.amountRange[key],
|
||||
color: colors.at(i, arr.length),
|
||||
tree: cohort,
|
||||
addrCount: {
|
||||
count: cohort.addrCount,
|
||||
@@ -197,12 +203,12 @@ export function buildCohortData() {
|
||||
);
|
||||
|
||||
// Spendable type cohorts - split by addressability
|
||||
const typeAddressable = ADDRESSABLE_TYPES.map((key) => {
|
||||
const typeAddressable = ADDRESSABLE_TYPES.map((key, i, arr) => {
|
||||
const names = SPENDABLE_TYPE_NAMES[key];
|
||||
return {
|
||||
name: names.short,
|
||||
title: names.short,
|
||||
color: colors.scriptType[key],
|
||||
color: colors.at(i, arr.length),
|
||||
tree: utxoCohorts.type[key],
|
||||
addrCount: addrCount[key],
|
||||
};
|
||||
@@ -210,26 +216,28 @@ export function buildCohortData() {
|
||||
|
||||
const typeOther = entries(utxoCohorts.type)
|
||||
.filter(([key]) => !isAddressable(key))
|
||||
.map(([key, tree]) => {
|
||||
.map(([key, tree], i, arr) => {
|
||||
const names = SPENDABLE_TYPE_NAMES[key];
|
||||
return {
|
||||
name: names.short,
|
||||
title: names.short,
|
||||
color: colors.scriptType[key],
|
||||
color: colors.at(i, arr.length),
|
||||
tree,
|
||||
};
|
||||
});
|
||||
|
||||
// Year cohorts
|
||||
const year = entries(utxoCohorts.year).map(([key, tree]) => {
|
||||
const names = YEAR_NAMES[key];
|
||||
return {
|
||||
name: names.short,
|
||||
title: names.long,
|
||||
color: colors.year[key],
|
||||
tree,
|
||||
};
|
||||
});
|
||||
const year = entries(utxoCohorts.year)
|
||||
.reverse()
|
||||
.map(([key, tree], i, arr) => {
|
||||
const names = YEAR_NAMES[key];
|
||||
return {
|
||||
name: names.short,
|
||||
title: names.long,
|
||||
color: colors.at(i, arr.length),
|
||||
tree,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
cohortAll,
|
||||
|
||||
@@ -91,7 +91,7 @@ export function createValuationSectionFull({ cohort, title }) {
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createValuationSection({ cohort, title }) {
|
||||
const { tree, color } = cohort;
|
||||
const { tree } = cohort;
|
||||
return {
|
||||
name: "Valuation",
|
||||
tree: [
|
||||
|
||||
@@ -66,17 +66,20 @@ const periodName = (key) => periodIdToName(key.slice(1), true);
|
||||
* @typedef {BaseEntryItem & { cagr: AnyMetricPattern }} LongEntryItem
|
||||
*/
|
||||
|
||||
const ALL_YEARS = /** @type {const} */ ([...YEARS_2020S, ...YEARS_2010S]);
|
||||
|
||||
/**
|
||||
* Build DCA class entry from year
|
||||
* @param {MarketDca} dca
|
||||
* @param {DcaYear} year
|
||||
* @param {number} i
|
||||
* @returns {BaseEntryItem}
|
||||
*/
|
||||
function buildYearEntry(dca, year) {
|
||||
function buildYearEntry(dca, year, i) {
|
||||
const key = /** @type {DcaYearKey} */ (`_${year}`);
|
||||
return {
|
||||
name: `${year}`,
|
||||
color: colors.year[key],
|
||||
color: colors.at(i, ALL_YEARS.length),
|
||||
costBasis: dca.classAveragePrice[key],
|
||||
returns: dca.classReturns[key],
|
||||
minReturn: dca.classMinReturn[key],
|
||||
@@ -536,10 +539,12 @@ function createPeriodSection({ dca, lookback, returns }) {
|
||||
const isLumpSum = !!lookback;
|
||||
const suffix = isLumpSum ? "Lump Sum" : "DCA";
|
||||
|
||||
/** @param {AllPeriodKey} key @returns {BaseEntryItem} */
|
||||
const buildBaseEntry = (key) => ({
|
||||
const allPeriods = /** @type {const} */ ([...SHORT_PERIODS, ...LONG_PERIODS]);
|
||||
|
||||
/** @param {AllPeriodKey} key @param {number} i @returns {BaseEntryItem} */
|
||||
const buildBaseEntry = (key, i) => ({
|
||||
name: periodName(key),
|
||||
color: colors.dca[key],
|
||||
color: colors.at(i, allPeriods.length),
|
||||
costBasis: isLumpSum ? lookback[key] : dca.periodAveragePrice[key],
|
||||
returns: isLumpSum ? dca.periodLumpSumReturns[key] : dca.periodReturns[key],
|
||||
minReturn: isLumpSum
|
||||
@@ -557,10 +562,10 @@ function createPeriodSection({ dca, lookback, returns }) {
|
||||
stack: isLumpSum ? dca.periodLumpSumStack[key] : dca.periodStack[key],
|
||||
});
|
||||
|
||||
/** @param {LongPeriodKey} key @returns {LongEntryItem} */
|
||||
const buildLongEntry = (key) =>
|
||||
/** @param {LongPeriodKey} key @param {number} i @returns {LongEntryItem} */
|
||||
const buildLongEntry = (key, i) =>
|
||||
withCagr(
|
||||
buildBaseEntry(key),
|
||||
buildBaseEntry(key, i),
|
||||
isLumpSum ? returns.cagr[key] : dca.periodCagr[key],
|
||||
);
|
||||
|
||||
@@ -578,8 +583,10 @@ function createPeriodSection({ dca, lookback, returns }) {
|
||||
titlePrefix: `${entry.name} ${suffix}`,
|
||||
});
|
||||
|
||||
const shortEntries = SHORT_PERIODS.map(buildBaseEntry);
|
||||
const longEntries = LONG_PERIODS.map(buildLongEntry);
|
||||
const shortEntries = SHORT_PERIODS.map((key, i) => buildBaseEntry(key, i));
|
||||
const longEntries = LONG_PERIODS.map((key, i) =>
|
||||
buildLongEntry(key, SHORT_PERIODS.length + i),
|
||||
);
|
||||
|
||||
return {
|
||||
name: `${suffix} by Period`,
|
||||
@@ -651,8 +658,12 @@ export function createDcaByStartYearSection({ dca }) {
|
||||
],
|
||||
});
|
||||
|
||||
const entries2020s = YEARS_2020S.map((year) => buildYearEntry(dca, year));
|
||||
const entries2010s = YEARS_2010S.map((year) => buildYearEntry(dca, year));
|
||||
const entries2020s = YEARS_2020S.map((year, i) =>
|
||||
buildYearEntry(dca, year, i),
|
||||
);
|
||||
const entries2010s = YEARS_2010S.map((year, i) =>
|
||||
buildYearEntry(dca, year, YEARS_2020S.length + i),
|
||||
);
|
||||
|
||||
return {
|
||||
name: "DCA by Start Year",
|
||||
|
||||
@@ -5,7 +5,7 @@ import { brk } from "../client.js";
|
||||
import { includes } from "../utils/array.js";
|
||||
import { Unit } from "../utils/units.js";
|
||||
import { priceLine, priceLines } from "./constants.js";
|
||||
import { baseline, histogram, line, price } from "./series.js";
|
||||
import { baseline, candlestick, histogram, line, price } from "./series.js";
|
||||
import { createPriceRatioCharts } from "./shared.js";
|
||||
import { periodIdToName } from "./utils.js";
|
||||
|
||||
@@ -184,7 +184,7 @@ function historicalSubSection(name, periods) {
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createMarketSection() {
|
||||
const { market, supply, price: priceMetrics } = brk.metrics;
|
||||
const { market, supply, distribution, price: priceMetrics } = brk.metrics;
|
||||
const {
|
||||
movingAverage: ma,
|
||||
ath,
|
||||
@@ -195,53 +195,28 @@ export function createMarketSection() {
|
||||
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,
|
||||
},
|
||||
const shortPeriodsBase = [
|
||||
{ id: "1d", returns: returns.priceReturns._1d, lookback: lookback._1d },
|
||||
{ id: "1w", returns: returns.priceReturns._1w, lookback: lookback._1w },
|
||||
{ id: "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: "1y", returns: returns.priceReturns._1y, lookback: lookback._1y },
|
||||
];
|
||||
|
||||
/** @type {PeriodWithCagr[]} */
|
||||
const longPeriods = [
|
||||
const longPeriodsBase = [
|
||||
{
|
||||
id: "2y",
|
||||
color: colors.returns._2y,
|
||||
returns: returns.priceReturns._2y,
|
||||
cagr: returns.cagr._2y,
|
||||
lookback: lookback._2y,
|
||||
@@ -249,7 +224,6 @@ export function createMarketSection() {
|
||||
},
|
||||
{
|
||||
id: "3y",
|
||||
color: colors.returns._3y,
|
||||
returns: returns.priceReturns._3y,
|
||||
cagr: returns.cagr._3y,
|
||||
lookback: lookback._3y,
|
||||
@@ -257,14 +231,12 @@ export function createMarketSection() {
|
||||
},
|
||||
{
|
||||
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,
|
||||
@@ -272,7 +244,6 @@ export function createMarketSection() {
|
||||
},
|
||||
{
|
||||
id: "6y",
|
||||
color: colors.returns._6y,
|
||||
returns: returns.priceReturns._6y,
|
||||
cagr: returns.cagr._6y,
|
||||
lookback: lookback._6y,
|
||||
@@ -280,7 +251,6 @@ export function createMarketSection() {
|
||||
},
|
||||
{
|
||||
id: "8y",
|
||||
color: colors.returns._8y,
|
||||
returns: returns.priceReturns._8y,
|
||||
cagr: returns.cagr._8y,
|
||||
lookback: lookback._8y,
|
||||
@@ -288,7 +258,6 @@ export function createMarketSection() {
|
||||
},
|
||||
{
|
||||
id: "10y",
|
||||
color: colors.returns._10y,
|
||||
returns: returns.priceReturns._10y,
|
||||
cagr: returns.cagr._10y,
|
||||
lookback: lookback._10y,
|
||||
@@ -296,96 +265,117 @@ export function createMarketSection() {
|
||||
},
|
||||
];
|
||||
|
||||
const totalReturnPeriods =
|
||||
shortPeriodsBase.length + longPeriodsBase.length;
|
||||
|
||||
/** @type {Period[]} */
|
||||
const shortPeriods = shortPeriodsBase.map((p, i) => ({
|
||||
...p,
|
||||
color: colors.at(i, totalReturnPeriods),
|
||||
}));
|
||||
|
||||
/** @type {PeriodWithCagr[]} */
|
||||
const longPeriods = longPeriodsBase.map((p, i) => ({
|
||||
...p,
|
||||
color: colors.at(shortPeriodsBase.length + i, totalReturnPeriods),
|
||||
}));
|
||||
|
||||
/** @type {MaPeriod[]} */
|
||||
const sma = [
|
||||
{ id: "1w", color: colors.ma._1w, ratio: ma.price1wSma },
|
||||
{ id: "8d", color: colors.ma._8d, ratio: ma.price8dSma },
|
||||
{ id: "13d", color: colors.ma._13d, ratio: ma.price13dSma },
|
||||
{ id: "21d", color: colors.ma._21d, ratio: ma.price21dSma },
|
||||
{ id: "1m", color: colors.ma._1m, ratio: ma.price1mSma },
|
||||
{ id: "34d", color: colors.ma._34d, ratio: ma.price34dSma },
|
||||
{ id: "55d", color: colors.ma._55d, ratio: ma.price55dSma },
|
||||
{ id: "89d", color: colors.ma._89d, ratio: ma.price89dSma },
|
||||
{ id: "111d", color: colors.ma._111d, ratio: ma.price111dSma },
|
||||
{ id: "144d", color: colors.ma._144d, ratio: ma.price144dSma },
|
||||
{ id: "200d", color: colors.ma._200d, ratio: ma.price200dSma },
|
||||
{ id: "350d", color: colors.ma._350d, ratio: ma.price350dSma },
|
||||
{ id: "1y", color: colors.ma._1y, ratio: ma.price1ySma },
|
||||
{ id: "2y", color: colors.ma._2y, ratio: ma.price2ySma },
|
||||
{ id: "200w", color: colors.ma._200w, ratio: ma.price200wSma },
|
||||
{ id: "4y", color: colors.ma._4y, ratio: ma.price4ySma },
|
||||
];
|
||||
{ id: "1w", ratio: ma.price1wSma },
|
||||
{ id: "8d", ratio: ma.price8dSma },
|
||||
{ id: "13d", ratio: ma.price13dSma },
|
||||
{ id: "21d", ratio: ma.price21dSma },
|
||||
{ id: "1m", ratio: ma.price1mSma },
|
||||
{ id: "34d", ratio: ma.price34dSma },
|
||||
{ id: "55d", ratio: ma.price55dSma },
|
||||
{ id: "89d", ratio: ma.price89dSma },
|
||||
{ id: "111d", ratio: ma.price111dSma },
|
||||
{ id: "144d", ratio: ma.price144dSma },
|
||||
{ id: "200d", ratio: ma.price200dSma },
|
||||
{ id: "350d", ratio: ma.price350dSma },
|
||||
{ id: "1y", ratio: ma.price1ySma },
|
||||
{ id: "2y", ratio: ma.price2ySma },
|
||||
{ id: "200w", ratio: ma.price200wSma },
|
||||
{ id: "4y", ratio: ma.price4ySma },
|
||||
].map((p, i, arr) => ({ ...p, color: colors.at(i, arr.length) }));
|
||||
|
||||
/** @type {MaPeriod[]} */
|
||||
const ema = [
|
||||
{ id: "1w", color: colors.ma._1w, ratio: ma.price1wEma },
|
||||
{ id: "8d", color: colors.ma._8d, ratio: ma.price8dEma },
|
||||
{ id: "12d", color: colors.ma._12d, ratio: ma.price12dEma },
|
||||
{ id: "13d", color: colors.ma._13d, ratio: ma.price13dEma },
|
||||
{ id: "21d", color: colors.ma._21d, ratio: ma.price21dEma },
|
||||
{ id: "26d", color: colors.ma._26d, ratio: ma.price26dEma },
|
||||
{ id: "1m", color: colors.ma._1m, ratio: ma.price1mEma },
|
||||
{ id: "34d", color: colors.ma._34d, ratio: ma.price34dEma },
|
||||
{ id: "55d", color: colors.ma._55d, ratio: ma.price55dEma },
|
||||
{ id: "89d", color: colors.ma._89d, ratio: ma.price89dEma },
|
||||
{ id: "144d", color: colors.ma._144d, ratio: ma.price144dEma },
|
||||
{ id: "200d", color: colors.ma._200d, ratio: ma.price200dEma },
|
||||
{ id: "1y", color: colors.ma._1y, ratio: ma.price1yEma },
|
||||
{ id: "2y", color: colors.ma._2y, ratio: ma.price2yEma },
|
||||
{ id: "200w", color: colors.ma._200w, ratio: ma.price200wEma },
|
||||
{ id: "4y", color: colors.ma._4y, ratio: ma.price4yEma },
|
||||
];
|
||||
{ id: "1w", ratio: ma.price1wEma },
|
||||
{ id: "8d", ratio: ma.price8dEma },
|
||||
{ id: "12d", ratio: ma.price12dEma },
|
||||
{ id: "13d", ratio: ma.price13dEma },
|
||||
{ id: "21d", ratio: ma.price21dEma },
|
||||
{ id: "26d", ratio: ma.price26dEma },
|
||||
{ id: "1m", ratio: ma.price1mEma },
|
||||
{ id: "34d", ratio: ma.price34dEma },
|
||||
{ id: "55d", ratio: ma.price55dEma },
|
||||
{ id: "89d", ratio: ma.price89dEma },
|
||||
{ id: "144d", ratio: ma.price144dEma },
|
||||
{ id: "200d", ratio: ma.price200dEma },
|
||||
{ id: "1y", ratio: ma.price1yEma },
|
||||
{ id: "2y", ratio: ma.price2yEma },
|
||||
{ id: "200w", ratio: ma.price200wEma },
|
||||
{ id: "4y", ratio: ma.price4yEma },
|
||||
].map((p, i, arr) => ({ ...p, color: colors.at(i, arr.length) }));
|
||||
|
||||
// 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,
|
||||
},
|
||||
];
|
||||
].map((p, i, arr) => ({ ...p, color: colors.at(i, arr.length) }));
|
||||
|
||||
return {
|
||||
name: "Market",
|
||||
tree: [
|
||||
{ name: "Price", title: "Bitcoin Price" },
|
||||
{
|
||||
name: "Oracle",
|
||||
title: "On-chain Price",
|
||||
top: [
|
||||
// @ts-ignore
|
||||
candlestick({
|
||||
metric: priceMetrics.oracle.ohlcDollars,
|
||||
name: "Oracle",
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
name: "Sats/$",
|
||||
@@ -401,13 +391,53 @@ export function createMarketSection() {
|
||||
|
||||
{
|
||||
name: "Capitalization",
|
||||
title: "Market Capitalization",
|
||||
bottom: [
|
||||
line({
|
||||
metric: supply.marketCap,
|
||||
name: "Capitalization",
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
tree: [
|
||||
{
|
||||
name: "Market Cap",
|
||||
title: "Market Capitalization",
|
||||
bottom: [
|
||||
line({
|
||||
metric: supply.marketCap,
|
||||
name: "Market Cap",
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Realized Cap",
|
||||
title: "Realized Capitalization",
|
||||
bottom: [
|
||||
line({
|
||||
metric: distribution.utxoCohorts.all.realized.realizedCap,
|
||||
name: "Realized Cap",
|
||||
color: colors.realized,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Growth Rate",
|
||||
title: "Capitalization Growth Rate",
|
||||
bottom: [
|
||||
line({
|
||||
metric: supply.marketCapGrowthRate,
|
||||
name: "Market Cap",
|
||||
color: colors.bitcoin,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
line({
|
||||
metric: supply.realizedCapGrowthRate,
|
||||
name: "Realized Cap",
|
||||
color: colors.usd,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
baseline({
|
||||
metric: supply.capGrowthRateDiff,
|
||||
name: "Difference",
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -632,7 +662,7 @@ export function createMarketSection() {
|
||||
price({
|
||||
metric: ma.price200dSma.price,
|
||||
name: "200d SMA",
|
||||
color: colors.ma._200d,
|
||||
color: colors.indicator.main,
|
||||
}),
|
||||
price({
|
||||
metric: ma.price200dSmaX24,
|
||||
|
||||
@@ -244,7 +244,7 @@ export function createMiningSection() {
|
||||
line({
|
||||
metric: blocks.mining.hashRate2mSma,
|
||||
name: "2m SMA",
|
||||
color: colors.ma._2m,
|
||||
color: colors.indicator.main,
|
||||
unit: Unit.hashRate,
|
||||
defaultActive: false,
|
||||
}),
|
||||
@@ -677,7 +677,7 @@ export function createMiningSection() {
|
||||
line({
|
||||
metric: p.pool._1mDominance,
|
||||
name: p.name,
|
||||
color: colors.at(i),
|
||||
color: colors.at(i, majorPools.length),
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
),
|
||||
@@ -689,7 +689,7 @@ export function createMiningSection() {
|
||||
line({
|
||||
metric: p.pool._1mBlocksMined,
|
||||
name: p.name,
|
||||
color: colors.at(i),
|
||||
color: colors.at(i, majorPools.length),
|
||||
unit: Unit.count,
|
||||
}),
|
||||
),
|
||||
@@ -702,7 +702,7 @@ export function createMiningSection() {
|
||||
source: p.pool.coinbase,
|
||||
key: "sum",
|
||||
name: p.name,
|
||||
color: colors.at(i),
|
||||
color: colors.at(i, majorPools.length),
|
||||
}),
|
||||
),
|
||||
},
|
||||
@@ -719,7 +719,7 @@ export function createMiningSection() {
|
||||
line({
|
||||
metric: p.pool._1mDominance,
|
||||
name: p.name,
|
||||
color: colors.at(i),
|
||||
color: colors.at(i, antpoolFriends.length),
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
),
|
||||
@@ -731,7 +731,7 @@ export function createMiningSection() {
|
||||
line({
|
||||
metric: p.pool._1mBlocksMined,
|
||||
name: p.name,
|
||||
color: colors.at(i),
|
||||
color: colors.at(i, antpoolFriends.length),
|
||||
unit: Unit.count,
|
||||
}),
|
||||
),
|
||||
@@ -744,7 +744,7 @@ export function createMiningSection() {
|
||||
source: p.pool.coinbase,
|
||||
key: "sum",
|
||||
name: p.name,
|
||||
color: colors.at(i),
|
||||
color: colors.at(i, antpoolFriends.length),
|
||||
}),
|
||||
),
|
||||
},
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { colors } from "../utils/colors.js";
|
||||
import { brk } from "../client.js";
|
||||
import { Unit } from "../utils/units.js";
|
||||
import { entries } from "../utils/array.js";
|
||||
import { priceLine } from "./constants.js";
|
||||
import {
|
||||
line,
|
||||
@@ -375,25 +376,13 @@ export function createNetworkSection() {
|
||||
});
|
||||
|
||||
// Script type groups for Output Counts
|
||||
const legacyScripts = /** @type {const} */ ([
|
||||
{ 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: st.p2sh },
|
||||
{ key: "p2ms", name: "P2MS", color: st.p2ms },
|
||||
]);
|
||||
const segwitScripts = /** @type {const} */ ([
|
||||
{ key: "segwit", name: "All SegWit", color: colors.segwit },
|
||||
{ key: "p2wsh", name: "P2WSH", color: st.p2wsh },
|
||||
{ key: "p2wpkh", name: "P2WPKH", color: st.p2wpkh },
|
||||
]);
|
||||
const otherScripts = /** @type {const} */ ([
|
||||
{ key: "opreturn", name: "OP_RETURN", color: st.opreturn },
|
||||
{ key: "emptyoutput", name: "Empty", color: st.empty },
|
||||
{ key: "unknownoutput", name: "Unknown", color: st.unknown },
|
||||
]);
|
||||
const legacyScripts = legacyAddresses.slice(1); // p2pkh, p2pk33, p2pk65
|
||||
const scriptHashScripts = [legacyAddresses[0], nonAddressableTypes[0]]; // p2sh, p2ms
|
||||
const segwitScripts = [
|
||||
/** @type {const} */ ({ key: "segwit", name: "All SegWit", color: colors.segwit }),
|
||||
...segwitAddresses,
|
||||
];
|
||||
const otherScripts = nonAddressableTypes.slice(1); // opreturn, empty, unknown
|
||||
|
||||
/**
|
||||
* Create Compare charts for a script group
|
||||
@@ -558,50 +547,28 @@ export function createNetworkSection() {
|
||||
{
|
||||
name: "Sum",
|
||||
title: "Transaction Versions",
|
||||
bottom: [
|
||||
line({
|
||||
metric: transactions.versions.v1.sum,
|
||||
name: "v1",
|
||||
color: colors.txVersion.v1,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
line({
|
||||
metric: transactions.versions.v2.sum,
|
||||
name: "v2",
|
||||
color: colors.txVersion.v2,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
line({
|
||||
metric: transactions.versions.v3.sum,
|
||||
name: "v3",
|
||||
color: colors.txVersion.v3,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
],
|
||||
bottom: entries(transactions.versions).map(
|
||||
([v, data], i, arr) =>
|
||||
line({
|
||||
metric: data.sum,
|
||||
name: v,
|
||||
color: colors.at(i, arr.length),
|
||||
unit: Unit.count,
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: "Transaction Versions (Total)",
|
||||
bottom: [
|
||||
line({
|
||||
metric: transactions.versions.v1.cumulative,
|
||||
name: "v1",
|
||||
color: colors.txVersion.v1,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
line({
|
||||
metric: transactions.versions.v2.cumulative,
|
||||
name: "v2",
|
||||
color: colors.txVersion.v2,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
line({
|
||||
metric: transactions.versions.v3.cumulative,
|
||||
name: "v3",
|
||||
color: colors.txVersion.v3,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
],
|
||||
bottom: entries(transactions.versions).map(
|
||||
([v, data], i, arr) =>
|
||||
line({
|
||||
metric: data.cumulative,
|
||||
name: v,
|
||||
color: colors.at(i, arr.length),
|
||||
unit: Unit.count,
|
||||
}),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1248,7 +1215,7 @@ export function createNetworkSection() {
|
||||
line({
|
||||
metric: scripts.count.taprootAdoption.cumulative,
|
||||
name: "Taproot",
|
||||
color: colors.scriptType.p2tr,
|
||||
color: taprootAddresses[1].color,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
],
|
||||
|
||||
@@ -321,14 +321,14 @@ export function sdBandsRatio(sd) {
|
||||
* @param {AnyRatioPattern} ratio
|
||||
*/
|
||||
export function ratioSmas(ratio) {
|
||||
return /** @type {const} */ ([
|
||||
{ name: "1w SMA", metric: ratio.ratio1wSma, color: colors.ma._1w },
|
||||
{ name: "1m SMA", metric: ratio.ratio1mSma, color: colors.ma._1m },
|
||||
{ name: "1y SMA", metric: ratio.ratio1ySd.sma, color: colors.ma._1y },
|
||||
{ name: "2y SMA", metric: ratio.ratio2ySd.sma, color: colors.ma._2y },
|
||||
{ name: "4y SMA", metric: ratio.ratio4ySd.sma, color: colors.ma._4y },
|
||||
return [
|
||||
{ name: "1w SMA", metric: ratio.ratio1wSma },
|
||||
{ name: "1m SMA", metric: ratio.ratio1mSma },
|
||||
{ name: "1y SMA", metric: ratio.ratio1ySd.sma },
|
||||
{ name: "2y SMA", metric: ratio.ratio2ySd.sma },
|
||||
{ name: "4y SMA", metric: ratio.ratio4ySd.sma },
|
||||
{ name: "All SMA", metric: ratio.ratioSd.sma, color: colors.time.all },
|
||||
]);
|
||||
].map((s, i, arr) => ({ color: colors.at(i, arr.length), ...s }));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -400,6 +400,13 @@ export function createZScoresFolder({
|
||||
}) {
|
||||
const sdPats = sdPatterns(ratio);
|
||||
|
||||
const zscorePeriods = [
|
||||
{ name: "1y", sd: ratio.ratio1ySd },
|
||||
{ name: "2y", sd: ratio.ratio2ySd },
|
||||
{ name: "4y", sd: ratio.ratio4ySd },
|
||||
{ name: "all", sd: ratio.ratioSd, color: colors.time.all },
|
||||
].map((s, i, arr) => ({ color: colors.at(i, arr.length), ...s }));
|
||||
|
||||
return {
|
||||
name: "Z-Scores",
|
||||
tree: [
|
||||
@@ -408,56 +415,24 @@ export function createZScoresFolder({
|
||||
title: formatTitle("Z-Scores"),
|
||||
top: [
|
||||
price({ metric: pricePattern, name: legend, color }),
|
||||
price({
|
||||
metric: ratio.ratio1ySd._0sdUsd,
|
||||
name: "1y 0σ",
|
||||
color: colors.ma._1y,
|
||||
defaultActive: false,
|
||||
}),
|
||||
price({
|
||||
metric: ratio.ratio2ySd._0sdUsd,
|
||||
name: "2y 0σ",
|
||||
color: colors.ma._2y,
|
||||
defaultActive: false,
|
||||
}),
|
||||
price({
|
||||
metric: ratio.ratio4ySd._0sdUsd,
|
||||
name: "4y 0σ",
|
||||
color: colors.ma._4y,
|
||||
defaultActive: false,
|
||||
}),
|
||||
price({
|
||||
metric: ratio.ratioSd._0sdUsd,
|
||||
name: "all 0σ",
|
||||
color: colors.time.all,
|
||||
defaultActive: false,
|
||||
}),
|
||||
...zscorePeriods.map((p) =>
|
||||
price({
|
||||
metric: p.sd._0sdUsd,
|
||||
name: `${p.name} 0σ`,
|
||||
color: p.color,
|
||||
defaultActive: false,
|
||||
}),
|
||||
),
|
||||
],
|
||||
bottom: [
|
||||
line({
|
||||
metric: ratio.ratioSd.zscore,
|
||||
name: "All",
|
||||
color: colors.time.all,
|
||||
unit: Unit.sd,
|
||||
}),
|
||||
line({
|
||||
metric: ratio.ratio4ySd.zscore,
|
||||
name: "4y",
|
||||
color: colors.ma._4y,
|
||||
unit: Unit.sd,
|
||||
}),
|
||||
line({
|
||||
metric: ratio.ratio2ySd.zscore,
|
||||
name: "2y",
|
||||
color: colors.ma._2y,
|
||||
unit: Unit.sd,
|
||||
}),
|
||||
line({
|
||||
metric: ratio.ratio1ySd.zscore,
|
||||
name: "1y",
|
||||
color: colors.ma._1y,
|
||||
unit: Unit.sd,
|
||||
}),
|
||||
...zscorePeriods.reverse().map((p) =>
|
||||
line({
|
||||
metric: p.sd.zscore,
|
||||
name: p.name,
|
||||
color: p.color,
|
||||
unit: Unit.sd,
|
||||
}),
|
||||
),
|
||||
...priceLines({
|
||||
unit: Unit.sd,
|
||||
numbers: [0, 1, -1, 2, -2, 3, -3],
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createShadow, createHeader } from "../utils/dom.js";
|
||||
import { createHeader } from "../utils/dom.js";
|
||||
import { chartElement } from "../utils/elements.js";
|
||||
import { serdeChartableIndex } from "../utils/serde.js";
|
||||
import { Unit } from "../utils/units.js";
|
||||
@@ -21,15 +21,11 @@ export function setOption(opt) {
|
||||
}
|
||||
|
||||
export function init() {
|
||||
chartElement.append(createShadow("left"));
|
||||
chartElement.append(createShadow("right"));
|
||||
|
||||
const { headerElement, headingElement } = createHeader();
|
||||
chartElement.append(headerElement);
|
||||
|
||||
const chart = createChart({
|
||||
parent: chartElement,
|
||||
id: "charts",
|
||||
brk,
|
||||
});
|
||||
|
||||
@@ -66,15 +62,12 @@ export function init() {
|
||||
return result;
|
||||
}
|
||||
|
||||
/** @type {ReturnType<typeof chart.setBlueprints> | null} */
|
||||
let blueprints = null;
|
||||
|
||||
function updatePriceWithLatest() {
|
||||
const latest = webSockets.kraken1dCandle.latest();
|
||||
if (!latest || !blueprints) return;
|
||||
if (!latest) return;
|
||||
|
||||
const priceSeries = blueprints.panes[0].series[0];
|
||||
const unit = blueprints.panes[0].unit;
|
||||
const priceSeries = chart.panes[0].series[0];
|
||||
const unit = chart.panes[0].unit;
|
||||
if (!priceSeries?.hasData() || !unit) return;
|
||||
|
||||
const last = /** @type {CandlestickData | undefined} */ (
|
||||
@@ -96,7 +89,7 @@ export function init() {
|
||||
headingElement.innerHTML = opt.title;
|
||||
|
||||
// Set blueprints first so storageId is correct before any index change
|
||||
blueprints = chart.setBlueprints({
|
||||
chart.setBlueprints({
|
||||
name: opt.title,
|
||||
top: buildTopBlueprints(opt.top),
|
||||
bottom: opt.bottom,
|
||||
|
||||
@@ -51,20 +51,32 @@ function createColor(getter) {
|
||||
|
||||
const globalComputedStyle = getComputedStyle(window.document.documentElement);
|
||||
|
||||
/**
|
||||
* Resolve a light-dark() value based on current theme
|
||||
* @param {string} value
|
||||
*/
|
||||
function resolveLightDark(value) {
|
||||
if (value.startsWith("light-dark(")) {
|
||||
const [light, _dark] = value.slice(11, -1).split(", ");
|
||||
return dark ? _dark : light;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
*/
|
||||
function getColor(name) {
|
||||
return globalComputedStyle.getPropertyValue(`--${name}`);
|
||||
return globalComputedStyle.getPropertyValue(`--${name}`).trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} property
|
||||
*/
|
||||
function getLightDarkValue(property) {
|
||||
const value = globalComputedStyle.getPropertyValue(property);
|
||||
const [light, _dark] = value.slice(11, -1).split(", ");
|
||||
return dark ? _dark : light;
|
||||
return resolveLightDark(
|
||||
globalComputedStyle.getPropertyValue(property).trim(),
|
||||
);
|
||||
}
|
||||
|
||||
const palette = {
|
||||
@@ -88,6 +100,29 @@ const palette = {
|
||||
rose: createColor(() => getColor("rose")),
|
||||
};
|
||||
|
||||
const paletteArr = Object.values(palette);
|
||||
|
||||
/**
|
||||
* Get a palette color by index, spreading small groups for better separation
|
||||
* @param {number} index
|
||||
* @param {number} [length]
|
||||
*/
|
||||
function at(index, length) {
|
||||
const n = paletteArr.length;
|
||||
if (length && length <= n / 2) {
|
||||
return paletteArr[Math.round((index * n) / length) % n];
|
||||
}
|
||||
return paletteArr[index % n];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a named color map from keys, using position-based palette assignment
|
||||
* @param {readonly string[]} keys
|
||||
*/
|
||||
function seq(keys) {
|
||||
return Object.fromEntries(keys.map((key, i) => [key, at(i, keys.length)]));
|
||||
}
|
||||
|
||||
export const colors = {
|
||||
default: createColor(() => getLightDarkValue("--color")),
|
||||
gray: createColor(() => getColor("gray")),
|
||||
@@ -138,21 +173,13 @@ export const colors = {
|
||||
plRatio: palette.yellow,
|
||||
|
||||
// Mining
|
||||
mining: {
|
||||
coinbase: palette.red,
|
||||
subsidy: palette.orange,
|
||||
fee: palette.yellow,
|
||||
},
|
||||
mining: seq(["coinbase", "subsidy", "fee"]),
|
||||
|
||||
// Network
|
||||
segwit: palette.cyan,
|
||||
|
||||
// Entity (transactions, inputs, outputs)
|
||||
entity: {
|
||||
tx: palette.red,
|
||||
input: palette.orange,
|
||||
output: palette.yellow,
|
||||
},
|
||||
entity: seq(["tx", "input", "output"]),
|
||||
|
||||
// Technical indicators
|
||||
indicator: {
|
||||
@@ -182,9 +209,9 @@ export const colors = {
|
||||
_99: palette.rose,
|
||||
_98: palette.pink,
|
||||
_95: palette.fuchsia,
|
||||
_5: palette.cyan,
|
||||
_5: palette.teal,
|
||||
_2: palette.sky,
|
||||
_1: palette.blue,
|
||||
_1: palette.indigo,
|
||||
},
|
||||
|
||||
// Standard deviation bands (warm = positive, cool = negative)
|
||||
@@ -204,37 +231,6 @@ export const colors = {
|
||||
m3: palette.violet,
|
||||
},
|
||||
|
||||
// Transaction versions
|
||||
txVersion: {
|
||||
v1: palette.red,
|
||||
v2: palette.orange,
|
||||
v3: palette.yellow,
|
||||
},
|
||||
|
||||
pct: {
|
||||
_100: palette.red,
|
||||
_95: palette.orange,
|
||||
_90: palette.amber,
|
||||
_85: palette.yellow,
|
||||
_80: palette.avocado,
|
||||
_75: palette.lime,
|
||||
_70: palette.green,
|
||||
_65: palette.emerald,
|
||||
_60: palette.teal,
|
||||
_55: palette.cyan,
|
||||
_50: palette.sky,
|
||||
_45: palette.blue,
|
||||
_40: palette.indigo,
|
||||
_35: palette.violet,
|
||||
_30: palette.purple,
|
||||
_25: palette.fuchsia,
|
||||
_20: palette.pink,
|
||||
_15: palette.rose,
|
||||
_10: palette.red,
|
||||
_05: palette.orange,
|
||||
_0: palette.amber,
|
||||
},
|
||||
|
||||
time: {
|
||||
_24h: palette.red,
|
||||
_1w: palette.yellow,
|
||||
@@ -248,192 +244,22 @@ export const colors = {
|
||||
long: palette.fuchsia,
|
||||
},
|
||||
|
||||
age: {
|
||||
_1d: palette.red,
|
||||
_1w: palette.orange,
|
||||
_1m: palette.yellow,
|
||||
_2m: palette.lime,
|
||||
_3m: palette.green,
|
||||
_4m: palette.teal,
|
||||
_5m: palette.cyan,
|
||||
_6m: palette.blue,
|
||||
_1y: palette.indigo,
|
||||
_2y: palette.violet,
|
||||
_3y: palette.purple,
|
||||
_4y: palette.fuchsia,
|
||||
_5y: palette.pink,
|
||||
_6y: palette.rose,
|
||||
_7y: palette.red,
|
||||
_8y: palette.orange,
|
||||
_10y: palette.yellow,
|
||||
_12y: palette.lime,
|
||||
_15y: palette.green,
|
||||
},
|
||||
scriptType: seq([
|
||||
"p2pk65",
|
||||
"p2pk33",
|
||||
"p2pkh",
|
||||
"p2ms",
|
||||
"p2sh",
|
||||
"p2wpkh",
|
||||
"p2wsh",
|
||||
"p2tr",
|
||||
"p2a",
|
||||
"opreturn",
|
||||
"unknown",
|
||||
"empty",
|
||||
]),
|
||||
|
||||
ageRange: {
|
||||
upTo1h: palette.red,
|
||||
_1hTo1d: palette.orange,
|
||||
_1dTo1w: palette.amber,
|
||||
_1wTo1m: palette.yellow,
|
||||
_1mTo2m: palette.avocado,
|
||||
_2mTo3m: palette.lime,
|
||||
_3mTo4m: palette.green,
|
||||
_4mTo5m: palette.emerald,
|
||||
_5mTo6m: palette.teal,
|
||||
_6mTo1y: palette.cyan,
|
||||
_1yTo2y: palette.sky,
|
||||
_2yTo3y: palette.blue,
|
||||
_3yTo4y: palette.indigo,
|
||||
_4yTo5y: palette.violet,
|
||||
_5yTo6y: palette.purple,
|
||||
_6yTo7y: palette.fuchsia,
|
||||
_7yTo8y: palette.pink,
|
||||
_8yTo10y: palette.rose,
|
||||
_10yTo12y: palette.red,
|
||||
_12yTo15y: palette.orange,
|
||||
from15y: palette.amber,
|
||||
},
|
||||
arr: paletteArr,
|
||||
|
||||
amount: {
|
||||
_1sat: palette.red,
|
||||
_10sats: palette.orange,
|
||||
_100sats: palette.yellow,
|
||||
_1kSats: palette.lime,
|
||||
_10kSats: palette.green,
|
||||
_100kSats: palette.teal,
|
||||
_1mSats: palette.cyan,
|
||||
_10mSats: palette.blue,
|
||||
_1btc: palette.indigo,
|
||||
_10btc: palette.violet,
|
||||
_100btc: palette.purple,
|
||||
_1kBtc: palette.fuchsia,
|
||||
_10kBtc: palette.pink,
|
||||
_100kBtc: palette.rose,
|
||||
},
|
||||
|
||||
amountRange: {
|
||||
_0sats: palette.red,
|
||||
_1satTo10sats: palette.orange,
|
||||
_10satsTo100sats: palette.yellow,
|
||||
_100satsTo1kSats: palette.lime,
|
||||
_1kSatsTo10kSats: palette.green,
|
||||
_10kSatsTo100kSats: palette.teal,
|
||||
_100kSatsTo1mSats: palette.cyan,
|
||||
_1mSatsTo10mSats: palette.blue,
|
||||
_10mSatsTo1btc: palette.indigo,
|
||||
_1btcTo10btc: palette.violet,
|
||||
_10btcTo100btc: palette.purple,
|
||||
_100btcTo1kBtc: palette.fuchsia,
|
||||
_1kBtcTo10kBtc: palette.pink,
|
||||
_10kBtcTo100kBtc: palette.rose,
|
||||
_100kBtcOrMore: palette.red,
|
||||
},
|
||||
|
||||
epoch: {
|
||||
_0: palette.red,
|
||||
_1: palette.orange,
|
||||
_2: palette.yellow,
|
||||
_3: palette.lime,
|
||||
_4: palette.green,
|
||||
},
|
||||
|
||||
year: {
|
||||
_2009: palette.red,
|
||||
_2010: palette.orange,
|
||||
_2011: palette.amber,
|
||||
_2012: palette.yellow,
|
||||
_2013: palette.lime,
|
||||
_2014: palette.green,
|
||||
_2015: palette.teal,
|
||||
_2016: palette.cyan,
|
||||
_2017: palette.sky,
|
||||
_2018: palette.blue,
|
||||
_2019: palette.indigo,
|
||||
_2020: palette.violet,
|
||||
_2021: palette.purple,
|
||||
_2022: palette.fuchsia,
|
||||
_2023: palette.pink,
|
||||
_2024: palette.rose,
|
||||
_2025: palette.red,
|
||||
_2026: palette.orange,
|
||||
},
|
||||
|
||||
returns: {
|
||||
_1d: palette.red,
|
||||
_1w: palette.orange,
|
||||
_1m: palette.yellow,
|
||||
_3m: palette.lime,
|
||||
_6m: palette.green,
|
||||
_1y: palette.teal,
|
||||
_2y: palette.cyan,
|
||||
_3y: palette.sky,
|
||||
_4y: palette.blue,
|
||||
_5y: palette.indigo,
|
||||
_6y: palette.violet,
|
||||
_8y: palette.purple,
|
||||
_10y: palette.fuchsia,
|
||||
},
|
||||
|
||||
ma: {
|
||||
_1w: palette.red,
|
||||
_8d: palette.orange,
|
||||
_12d: palette.amber,
|
||||
_13d: palette.yellow,
|
||||
_14d: palette.avocado,
|
||||
_21d: palette.avocado,
|
||||
_26d: palette.lime,
|
||||
_1m: palette.green,
|
||||
_34d: palette.emerald,
|
||||
_55d: palette.teal,
|
||||
_2m: palette.cyan,
|
||||
_89d: palette.sky,
|
||||
_111d: palette.blue,
|
||||
_144d: palette.indigo,
|
||||
_200d: palette.violet,
|
||||
_350d: palette.purple,
|
||||
_1y: palette.fuchsia,
|
||||
_2y: palette.pink,
|
||||
_200w: palette.rose,
|
||||
_4y: palette.red,
|
||||
},
|
||||
|
||||
dca: {
|
||||
_1w: palette.red,
|
||||
_1m: palette.orange,
|
||||
_3m: palette.yellow,
|
||||
_6m: palette.lime,
|
||||
_1y: palette.green,
|
||||
_2y: palette.teal,
|
||||
_3y: palette.cyan,
|
||||
_4y: palette.sky,
|
||||
_5y: palette.blue,
|
||||
_6y: palette.indigo,
|
||||
_8y: palette.violet,
|
||||
_10y: palette.purple,
|
||||
},
|
||||
|
||||
scriptType: {
|
||||
p2pk65: palette.red,
|
||||
p2pk33: palette.orange,
|
||||
p2pkh: palette.yellow,
|
||||
p2ms: palette.lime,
|
||||
p2sh: palette.green,
|
||||
p2wpkh: palette.teal,
|
||||
p2wsh: palette.blue,
|
||||
p2tr: palette.indigo,
|
||||
p2a: palette.violet,
|
||||
opreturn: palette.purple,
|
||||
unknown: palette.fuchsia,
|
||||
empty: palette.pink,
|
||||
},
|
||||
|
||||
arr: Object.values(palette),
|
||||
|
||||
/**
|
||||
* Get a color by index (cycles through palette)
|
||||
* @param {number} index
|
||||
*/
|
||||
at(index) {
|
||||
return this.arr[index % this.arr.length];
|
||||
},
|
||||
at,
|
||||
};
|
||||
|
||||
@@ -17,8 +17,8 @@ export const Unit = /** @type {const} */ ({
|
||||
sd: { id: "sd", name: "Std Dev" },
|
||||
|
||||
// Relative percentages
|
||||
pctSupply: { id: "pct-supply", name: "% of circulating Supply" },
|
||||
pctOwn: { id: "pct-own", name: "% of Own Supply" },
|
||||
pctSupply: { id: "pct-supply", name: "% of circulating" },
|
||||
pctOwn: { id: "pct-own", name: "% of Own" },
|
||||
pctMcap: { id: "pct-mcap", name: "% of Market Cap" },
|
||||
pctRcap: { id: "pct-rcap", name: "% of Realized Cap" },
|
||||
pctOwnRcap: { id: "pct-own-rcap", name: "% of Own Realized Cap" },
|
||||
|
||||
Reference in New Issue
Block a user