Files
brk/website/scripts/options/market.js
2026-02-03 00:08:37 +01:00

482 lines
18 KiB
JavaScript

/** Market section */
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 { createPriceRatioCharts } from "./shared.js";
import { periodIdToName } from "./utils.js";
/**
* @typedef {Object} Period
* @property {string} id
* @property {Color} color
* @property {AnyMetricPattern} returns
* @property {AnyPricePattern} lookback
* @property {boolean} [defaultActive]
*/
/**
* @typedef {Period & { cagr: AnyMetricPattern }} PeriodWithCagr
*/
/**
* @typedef {Object} MaPeriod
* @property {string} id
* @property {Color} color
* @property {ActivePriceRatioPattern} ratio
*/
const commonMaIds = /** @type {const} */ (["1w", "1m", "200d", "1y", "200w", "4y"]);
/**
* @param {PartialContext} ctx
* @param {string} label
* @param {MaPeriod[]} averages
*/
function createMaSubSection(ctx, 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, {
context: `${periodIdToName(a.id, true)} ${label}`,
legend: "average",
pricePattern: a.ratio.price,
ratio: a.ratio,
color: a.color,
}),
});
return {
name: label,
tree: [
{
name: "Compare",
title: `Price ${label}s`,
top: averages.map((a) => price({ metric: a.ratio.price, name: a.id, color: a.color })),
},
...common.map(toFolder),
{ name: "More...", tree: more.map(toFolder) },
],
};
}
/**
* @param {Colors} colors
* @param {string} name
* @param {string} title
* @param {Unit} unit
* @param {{ _1w: AnyMetricPattern, _1m: AnyMetricPattern, _1y: AnyMetricPattern }} metrics
*/
function volatilityChart(colors, name, title, unit, metrics) {
return {
name,
title,
bottom: [
line({ metric: metrics._1w, name: "1w", color: colors.red, unit }),
line({ metric: metrics._1m, name: "1m", color: colors.orange, unit }),
line({ metric: metrics._1y, name: "1y", color: colors.lime, unit }),
],
};
}
/**
* @param {string} name
* @param {Period[]} periods
*/
function returnsSubSection(name, periods) {
return {
name,
tree: [
{
name: "Compare",
title: `${name} Returns`,
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 })],
})),
],
};
}
/**
* @param {string} name
* @param {PeriodWithCagr[]} periods
*/
function returnsSubSectionWithCagr(name, periods) {
return {
name,
tree: [
{
name: "Compare",
title: `${name} Returns`,
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 }),
baseline({ metric: p.cagr, name: "annual", unit: Unit.cagr }),
],
})),
],
};
}
/**
* @param {string} name
* @param {Period[]} periods
*/
function historicalSubSection(name, periods) {
return {
name,
tree: [
{
name: "Compare",
title: `${name} Historical`,
top: periods.map((p) => price({ metric: p.lookback, name: p.id, color: p.color })),
},
...periods.map((p) => ({
name: periodIdToName(p.id, true),
title: `${periodIdToName(p.id, true)} Ago`,
top: [price({ metric: p.lookback, name: "Price" })],
})),
],
};
}
/**
* Create Market section
* @param {PartialContext} ctx
* @returns {PartialOptionsGroup}
*/
export function createMarketSection(ctx) {
const { colors, brk } = ctx;
const { market, supply, price: priceMetrics } = brk.metrics;
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 },
];
/** @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 },
];
/** @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 },
];
/** @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 },
];
// 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 },
];
return {
name: "Market",
tree: [
{ name: "Price", title: "Bitcoin Price" },
{
name: "Sats/$",
title: "Sats per Dollar",
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 })],
},
{
name: "All Time High",
tree: [
{
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 })],
},
{
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 }),
],
},
],
},
{
name: "Returns",
tree: [
{
name: "Compare",
title: "Returns Comparison",
bottom: [...shortPeriods, ...longPeriods].map((p) =>
baseline({
metric: p.returns,
name: p.id,
color: p.color,
unit: Unit.percentage,
defaultActive: p.defaultActive,
}),
),
},
returnsSubSection("Short-term", shortPeriods),
returnsSubSectionWithCagr("Long-term", longPeriods),
],
},
{
name: "Volatility",
tree: [
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 }),
],
},
{
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] }),
],
},
volatilityChart(colors, "Sharpe Ratio", "Sharpe Ratio", Unit.ratio, {
_1w: volatility.sharpe1w,
_1m: volatility.sharpe1m,
_1y: volatility.sharpe1y,
}),
volatilityChart(colors, "Sortino Ratio", "Sortino Ratio", Unit.ratio, {
_1w: volatility.sortino1w,
_1m: volatility.sortino1m,
_1y: volatility.sortino1y,
}),
],
},
{
name: "Moving Averages",
tree: [
{
name: "SMA vs EMA",
tree: [
{
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 }),
]),
},
...smaVsEma.map((p) => ({
name: p.name,
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 }),
],
})),
],
},
createMaSubSection(ctx, "SMA", sma),
createMaSubSection(ctx, "EMA", ema),
],
},
{
name: "Bands",
tree: [
{
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 },
].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 }),
],
})),
},
{
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 }),
],
},
],
},
{
name: "Momentum",
tree: [
{
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 }),
],
},
{
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] }),
],
},
{
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 }),
],
},
],
},
{
name: "Historical",
tree: [
{
name: "Compare",
title: "Historical Comparison",
top: [...shortPeriods, ...longPeriods].map((p) =>
price({
metric: p.lookback,
name: p.id,
color: p.color,
defaultActive: p.defaultActive,
}),
),
},
historicalSubSection("Short-term", shortPeriods),
historicalSubSection("Long-term", longPeriods),
],
},
{
name: "Indicators",
tree: [
{
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 }),
],
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 })],
},
{
name: "NVT",
title: "NVT 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 })],
},
],
},
],
};
}