global: snapshot

This commit is contained in:
nym21
2026-01-14 16:38:53 +01:00
parent ddb1db7a8e
commit d75c2a881b
226 changed files with 7776 additions and 20942 deletions

View File

@@ -0,0 +1,237 @@
/** Moving averages section */
import { Unit } from "../../utils/units.js";
import { periodIdToName } from "./utils.js";
/**
* Build averages data array from market patterns
* @param {Colors} colors
* @param {MarketMovingAverage} ma
*/
export function buildAverages(colors, ma) {
return /** @type {const} */ ([
["1w", 7, "red", ma.price1wSma, ma.price1wEma],
["8d", 8, "orange", ma.price8dSma, ma.price8dEma],
["13d", 13, "amber", ma.price13dSma, ma.price13dEma],
["21d", 21, "yellow", ma.price21dSma, ma.price21dEma],
["1m", 30, "lime", ma.price1mSma, ma.price1mEma],
["34d", 34, "green", ma.price34dSma, ma.price34dEma],
["55d", 55, "emerald", ma.price55dSma, ma.price55dEma],
["89d", 89, "teal", ma.price89dSma, ma.price89dEma],
["144d", 144, "cyan", ma.price144dSma, ma.price144dEma],
["200d", 200, "sky", ma.price200dSma, ma.price200dEma],
["1y", 365, "blue", ma.price1ySma, ma.price1yEma],
["2y", 730, "indigo", ma.price2ySma, ma.price2yEma],
["200w", 1400, "violet", ma.price200wSma, ma.price200wEma],
["4y", 1460, "purple", ma.price4ySma, ma.price4yEma],
]).map(([id, days, colorKey, sma, ema]) => ({
id,
name: periodIdToName(id, true),
days,
color: colors[colorKey],
sma,
ema,
}));
}
/**
* Create price with ratio options (for moving averages)
* @param {PartialContext} ctx
* @param {Object} args
* @param {string} args.title
* @param {string} args.legend
* @param {EmaRatioPattern} args.ratio
* @param {Color} [args.color]
* @returns {PartialOptionsTree}
*/
export function createPriceWithRatioOptions(
ctx,
{ title, legend, ratio, color },
) {
const { line, colors, createPriceLine } = ctx;
const priceMetric = ratio.price;
const percentileUsdMap = [
{ name: "pct99", prop: ratio.ratioPct99Usd, color: colors.rose },
{ name: "pct98", prop: ratio.ratioPct98Usd, color: colors.pink },
{ name: "pct95", prop: ratio.ratioPct95Usd, color: colors.fuchsia },
{ name: "pct5", prop: ratio.ratioPct5Usd, color: colors.cyan },
{ name: "pct2", prop: ratio.ratioPct2Usd, color: colors.sky },
{ name: "pct1", prop: ratio.ratioPct1Usd, color: colors.blue },
];
const percentileMap = [
{ name: "pct99", prop: ratio.ratioPct99, color: colors.rose },
{ name: "pct98", prop: ratio.ratioPct98, color: colors.pink },
{ name: "pct95", prop: ratio.ratioPct95, color: colors.fuchsia },
{ name: "pct5", prop: ratio.ratioPct5, color: colors.cyan },
{ name: "pct2", prop: ratio.ratioPct2, color: colors.sky },
{ name: "pct1", prop: ratio.ratioPct1, color: colors.blue },
];
const sdPatterns = [
{ nameAddon: "all", titleAddon: "", sd: ratio.ratioSd },
{ nameAddon: "4y", titleAddon: "4y", sd: ratio.ratio4ySd },
{ nameAddon: "2y", titleAddon: "2y", sd: ratio.ratio2ySd },
{ nameAddon: "1y", titleAddon: "1y", sd: ratio.ratio1ySd },
];
/** @param {Ratio1ySdPattern} sd */
const getSdBands = (sd) => [
{ name: "0σ", prop: sd._0sdUsd, color: colors.lime },
{ name: "+0.5σ", prop: sd.p05sdUsd, color: colors.yellow },
{ name: "+1σ", prop: sd.p1sdUsd, color: colors.amber },
{ name: "+1.5σ", prop: sd.p15sdUsd, color: colors.orange },
{ name: "+2σ", prop: sd.p2sdUsd, color: colors.red },
{ name: "+2.5σ", prop: sd.p25sdUsd, color: colors.rose },
{ name: "+3σ", prop: sd.p3sd, color: colors.pink },
{ name: "0.5σ", prop: sd.m05sdUsd, color: colors.teal },
{ name: "1σ", prop: sd.m1sdUsd, color: colors.cyan },
{ name: "1.5σ", prop: sd.m15sdUsd, color: colors.sky },
{ name: "2σ", prop: sd.m2sdUsd, color: colors.blue },
{ name: "2.5σ", prop: sd.m25sdUsd, color: colors.indigo },
{ name: "3σ", prop: sd.m3sd, color: colors.violet },
];
return [
{
name: "price",
title,
top: [line({ metric: priceMetric, name: legend, color, unit: Unit.usd })],
},
{
name: "Ratio",
title: `${title} Ratio`,
top: [
line({ metric: priceMetric, name: legend, color, unit: Unit.usd }),
...percentileUsdMap.map(({ name: pctName, prop, color: pctColor }) =>
line({
metric: prop,
name: pctName,
color: pctColor,
defaultActive: false,
unit: Unit.usd,
options: { lineStyle: 1 },
}),
),
],
bottom: [
line({ metric: ratio.ratio, name: "Ratio", color, unit: Unit.ratio }),
line({
metric: ratio.ratio1wSma,
name: "1w SMA",
color: colors.lime,
unit: Unit.ratio,
}),
line({
metric: ratio.ratio1mSma,
name: "1m SMA",
color: colors.teal,
unit: Unit.ratio,
}),
line({
metric: ratio.ratio1ySd.sma,
name: "1y SMA",
color: colors.sky,
unit: Unit.ratio,
}),
line({
metric: ratio.ratio2ySd.sma,
name: "2y SMA",
color: colors.indigo,
unit: Unit.ratio,
}),
line({
metric: ratio.ratio4ySd.sma,
name: "4y SMA",
color: colors.purple,
unit: Unit.ratio,
}),
line({
metric: ratio.ratioSd.sma,
name: "All SMA",
color: colors.rose,
unit: Unit.ratio,
}),
...percentileMap.map(({ name: pctName, prop, color: pctColor }) =>
line({
metric: prop,
name: pctName,
color: pctColor,
defaultActive: false,
unit: Unit.ratio,
options: { lineStyle: 1 },
}),
),
createPriceLine({ unit: Unit.ratio, number: 1 }),
],
},
{
name: "ZScores",
tree: sdPatterns.map(({ nameAddon, titleAddon, sd }) => ({
name: nameAddon,
title: `${title} ${titleAddon} Z-Score`,
top: getSdBands(sd).map(({ name: bandName, prop, color: bandColor }) =>
line({
metric: prop,
name: bandName,
color: bandColor,
unit: Unit.usd,
}),
),
bottom: [
line({ metric: sd.zscore, name: "Z-Score", color, unit: Unit.sd }),
createPriceLine({ unit: Unit.sd, number: 3 }),
createPriceLine({ unit: Unit.sd, number: 2 }),
createPriceLine({ unit: Unit.sd, number: 1 }),
createPriceLine({ unit: Unit.sd, number: 0 }),
createPriceLine({ unit: Unit.sd, number: -1 }),
createPriceLine({ unit: Unit.sd, number: -2 }),
createPriceLine({ unit: Unit.sd, number: -3 }),
],
})),
},
];
}
/**
* Create Averages section
* @param {PartialContext} ctx
* @param {ReturnType<typeof buildAverages>} averages
*/
export function createAveragesSection(ctx, averages) {
const { line } = ctx;
return {
name: "Averages",
tree: [
{ nameAddon: "Simple", metricAddon: /** @type {const} */ ("sma") },
{ nameAddon: "Exponential", metricAddon: /** @type {const} */ ("ema") },
].map(({ nameAddon, metricAddon }) => ({
name: nameAddon,
tree: [
{
name: "Compare",
title: `Market Price ${nameAddon} Moving Averages`,
top: averages.map(({ id, color, sma, ema }) =>
line({
metric: (metricAddon === "sma" ? sma : ema).price,
name: id,
color,
unit: Unit.usd,
}),
),
},
...averages.map(({ name, color, sma, ema }) => ({
name,
tree: createPriceWithRatioOptions(ctx, {
ratio: metricAddon === "sma" ? sma : ema,
title: `${name} Market Price ${nameAddon} Moving Average`,
legend: "average",
color,
}),
})),
],
})),
};
}

View File

@@ -0,0 +1,118 @@
/** Market section - Main entry point */
import { localhost } from "../../utils/env.js";
import { Unit } from "../../utils/units.js";
import { buildAverages, createAveragesSection } from "./averages.js";
import { createPerformanceSection } from "./performance.js";
import { createIndicatorsSection } from "./indicators/index.js";
import { createInvestingSection } from "./investing.js";
/**
* Create Market section
* @param {PartialContext} ctx
* @returns {PartialOptionsGroup}
*/
export function createMarketSection(ctx) {
const { colors, brk, line, candlestick } = ctx;
const { market, supply, price } = brk.metrics;
const {
movingAverage,
ath,
returns,
volatility,
range,
dca,
lookback,
indicators,
} = market;
const averages = buildAverages(colors, movingAverage);
return {
name: "Market",
tree: [
// Price
{
name: "Price",
title: "Bitcoin Price",
...(localhost && {
top: [
candlestick({
metric: price.oracle.ohlcDollars,
name: "Oracle",
unit: Unit.usd,
colors: [colors.cyan, colors.purple],
}),
],
}),
},
// Capitalization
{
name: "Capitalization",
title: "Market Capitalization",
bottom: [
line({
metric: supply.marketCap,
name: "Capitalization",
unit: Unit.usd,
}),
],
},
// All Time High
{
name: "All Time High",
title: "All Time High",
top: [line({ metric: ath.priceAth, name: "ATH", unit: Unit.usd })],
bottom: [
line({
metric: ath.priceDrawdown,
name: "Drawdown",
color: colors.red,
unit: Unit.percentage,
}),
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,
}),
],
},
// Averages
createAveragesSection(ctx, averages),
// Performance
createPerformanceSection(ctx, returns),
// Indicators
createIndicatorsSection(ctx, {
volatility,
range,
movingAverage,
indicators,
}),
// Investing
createInvestingSection(ctx, { dca, lookback, returns }),
],
};
}

View File

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

View File

@@ -0,0 +1,27 @@
/** Indicators section - Main entry point */
import { createMomentumSection } from "./momentum.js";
import { createVolatilitySection } from "./volatility.js";
import { createBandsSection } from "./bands.js";
import { createOnchainSection } from "./onchain.js";
/**
* Create Indicators section
* @param {PartialContext} ctx
* @param {Object} args
* @param {Market["volatility"]} args.volatility
* @param {Market["range"]} args.range
* @param {Market["movingAverage"]} args.movingAverage
* @param {Market["indicators"]} args.indicators
*/
export function createIndicatorsSection(ctx, { volatility, range, movingAverage, indicators }) {
return {
name: "Indicators",
tree: [
createMomentumSection(ctx, indicators),
createVolatilitySection(ctx, { volatility, range }),
createBandsSection(ctx, { range, movingAverage }),
createOnchainSection(ctx, { indicators, movingAverage }),
],
};
}

View File

@@ -0,0 +1,111 @@
/** Momentum indicators (RSI, StochRSI, Stochastic, MACD) */
import { Unit } from "../../../utils/units.js";
/**
* Create Momentum section
* @param {PartialContext} ctx
* @param {Market["indicators"]} indicators
*/
export function createMomentumSection(ctx, indicators) {
const { line, histogram, colors, createPriceLine } = ctx;
return {
name: "Momentum",
tree: [
{
name: "RSI",
title: "Relative Strength Index (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,
}),
createPriceLine({ unit: Unit.index, number: 70 }),
createPriceLine({
unit: Unit.index,
number: 50,
defaultActive: false,
}),
createPriceLine({ unit: Unit.index, number: 30 }),
],
},
{
name: "StochRSI",
title: "Stochastic RSI",
bottom: [
// line({
// metric: indicators.stochRsi,
// name: "Stoch RSI",
// color: colors.purple,
// unit: Unit.index,
// }),
line({
metric: indicators.stochRsiK,
name: "K",
color: colors.blue,
unit: Unit.index,
}),
line({
metric: indicators.stochRsiD,
name: "D",
color: colors.orange,
unit: Unit.index,
}),
createPriceLine({ unit: Unit.index, number: 80 }),
createPriceLine({ unit: Unit.index, number: 20 }),
],
},
// {
// name: "Stochastic",
// title: "Stochastic Oscillator",
// bottom: [
// line({ metric: indicators.stochK, name: "K", color: colors.blue, unit: Unit.index }),
// line({ metric: indicators.stochD, name: "D", color: colors.orange, unit: Unit.index }),
// createPriceLine({ unit: Unit.index, number: 80 }),
// createPriceLine({ unit: Unit.index, number: 20 }),
// ],
// },
{
name: "MACD",
title: "Moving Average Convergence Divergence",
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,
}),
createPriceLine({ unit: Unit.usd }),
],
},
],
};
}

View File

@@ -0,0 +1,83 @@
/** On-chain indicators (Pi Cycle, Puell, NVT, Gini) */
import { Unit } from "../../../utils/units.js";
/**
* Create On-chain section
* @param {PartialContext} ctx
* @param {Object} args
* @param {Market["indicators"]} args.indicators
* @param {Market["movingAverage"]} args.movingAverage
*/
export function createOnchainSection(ctx, { indicators, movingAverage }) {
const { line, colors, createPriceLine } = ctx;
return {
name: "On-chain",
tree: [
{
name: "Pi Cycle",
title: "Pi Cycle Top Indicator",
top: [
line({
metric: movingAverage.price111dSma.price,
name: "111d SMA",
color: colors.green,
unit: Unit.usd,
}),
line({
metric: movingAverage.price350dSmaX2,
name: "350d SMA x2",
color: colors.red,
unit: Unit.usd,
}),
],
bottom: [
line({
metric: indicators.piCycle,
name: "Pi Cycle",
color: colors.purple,
unit: Unit.ratio,
}),
createPriceLine({ unit: Unit.ratio, number: 1 }),
],
},
{
name: "Puell Multiple",
title: "Puell Multiple",
bottom: [
line({
metric: indicators.puellMultiple,
name: "Puell",
color: colors.green,
unit: Unit.ratio,
}),
],
},
{
name: "NVT",
title: "Network Value to Transactions 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,
}),
],
},
],
};
}

View File

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

View File

@@ -0,0 +1,284 @@
/** Investing section (DCA) */
import { Unit } from "../../utils/units.js";
import { periodIdToName } from "./utils.js";
/**
* Build DCA classes data array
* @param {Colors} colors
* @param {MarketDca} dca
*/
export function buildDcaClasses(colors, dca) {
return /** @type {const} */ ([
[2025, "pink", true],
[2024, "fuchsia", true],
[2023, "purple", true],
[2022, "blue", true],
[2021, "sky", true],
[2020, "teal", true],
[2019, "green", true],
[2018, "yellow", true],
[2017, "orange", true],
[2016, "red", false],
[2015, "pink", false],
]).map(([year, colorKey, defaultActive]) => ({
year,
color: colors[colorKey],
defaultActive,
costBasis: dca.classAveragePrice[`_${year}`],
returns: dca.classReturns[`_${year}`],
stack: dca.classStack[`_${year}`],
}));
}
/**
* Create Investing section
* @param {PartialContext} ctx
* @param {Object} args
* @param {Market["dca"]} args.dca
* @param {Market["lookback"]} args.lookback
* @param {Market["returns"]} args.returns
*/
export function createInvestingSection(ctx, { dca, lookback, returns }) {
const { line, baseline, colors, createPriceLine } = ctx;
const dcaClasses = buildDcaClasses(colors, dca);
return {
name: "Investing",
tree: [
// DCA vs Lump sum
{
name: "DCA vs Lump sum",
tree: /** @type {const} */ ([
["1w", "_1w"],
["1m", "_1m"],
["3m", "_3m"],
["6m", "_6m"],
["1y", "_1y"],
["2y", "_2y"],
["3y", "_3y"],
["4y", "_4y"],
["5y", "_5y"],
["6y", "_6y"],
["8y", "_8y"],
["10y", "_10y"],
]).map(([id, key]) => {
const name = periodIdToName(id, true);
const priceAgo = lookback[key];
const priceReturns = returns.priceReturns[key];
const dcaCostBasis = dca.periodAveragePrice[key];
const dcaReturns = dca.periodReturns[key];
const dcaStack = dca.periodStack[key];
const lumpSumStack = dca.periodLumpSumStack[key];
return {
name,
tree: [
{
name: "Cost basis",
title: `${name} DCA vs Lump Sum (Cost Basis)`,
top: [
line({
metric: dcaCostBasis,
name: "DCA",
color: colors.green,
unit: Unit.usd,
}),
line({
metric: priceAgo,
name: "Lump sum",
color: colors.orange,
unit: Unit.usd,
}),
],
},
{
name: "Returns",
title: `${name} DCA vs Lump Sum (Returns)`,
bottom: [
baseline({
metric: dcaReturns,
name: "DCA",
unit: Unit.percentage,
}),
baseline({
metric: priceReturns,
name: "Lump sum",
color: [colors.lime, colors.red],
unit: Unit.percentage,
}),
createPriceLine({ unit: Unit.percentage }),
],
},
{
name: "Stack",
title: `${name} DCA vs Lump Sum Stack ($100/day)`,
bottom: [
line({
metric: dcaStack.sats,
name: "DCA",
color: colors.green,
unit: Unit.sats,
}),
line({
metric: dcaStack.bitcoin,
name: "DCA",
color: colors.green,
unit: Unit.btc,
}),
line({
metric: dcaStack.dollars,
name: "DCA",
color: colors.green,
unit: Unit.usd,
}),
line({
metric: lumpSumStack.sats,
name: "Lump sum",
color: colors.orange,
unit: Unit.sats,
}),
line({
metric: lumpSumStack.bitcoin,
name: "Lump sum",
color: colors.orange,
unit: Unit.btc,
}),
line({
metric: lumpSumStack.dollars,
name: "Lump sum",
color: colors.orange,
unit: Unit.usd,
}),
],
},
],
};
}),
},
// DCA classes
{
name: "DCA classes",
tree: [
// Comparison charts (all years overlaid)
{
name: "Compare",
tree: [
{
name: "Cost basis",
title: "DCA Cost Basis by Year",
top: dcaClasses.map(
({ year, color, defaultActive, costBasis }) =>
line({
metric: costBasis,
name: `${year}`,
color,
defaultActive,
unit: Unit.usd,
}),
),
},
{
name: "Returns",
title: "DCA Returns by Year",
bottom: dcaClasses.map(
({ year, color, defaultActive, returns }) =>
baseline({
metric: returns,
name: `${year}`,
color,
defaultActive,
unit: Unit.percentage,
}),
),
},
{
name: "Stack",
title: "DCA Stack by Year ($100/day)",
bottom: dcaClasses.flatMap(
({ year, color, defaultActive, stack }) => [
line({
metric: stack.sats,
name: `${year}`,
color,
defaultActive,
unit: Unit.sats,
}),
line({
metric: stack.bitcoin,
name: `${year}`,
color,
defaultActive,
unit: Unit.btc,
}),
line({
metric: stack.dollars,
name: `${year}`,
color,
defaultActive,
unit: Unit.usd,
}),
],
),
},
],
},
// Individual year charts
...dcaClasses.map(({ year, color, costBasis, returns, stack }) => ({
name: `${year}`,
tree: [
{
name: "Cost basis",
title: `DCA Class ${year} Cost Basis`,
top: [
line({
metric: costBasis,
name: "Cost basis",
color,
unit: Unit.usd,
}),
],
},
{
name: "Returns",
title: `DCA Class ${year} Returns`,
bottom: [
baseline({
metric: returns,
name: "Returns",
color,
unit: Unit.percentage,
}),
],
},
{
name: "Stack",
title: `DCA Class ${year} Stack ($100/day)`,
bottom: [
line({
metric: stack.sats,
name: "Stack",
color,
unit: Unit.sats,
}),
line({
metric: stack.bitcoin,
name: "Stack",
color,
unit: Unit.btc,
}),
line({
metric: stack.dollars,
name: "Stack",
color,
unit: Unit.usd,
}),
],
},
],
})),
],
},
],
};
}

View File

@@ -0,0 +1,58 @@
/** Performance section */
import { Unit } from "../../utils/units.js";
import { periodIdToName } from "./utils.js";
/**
* Create Performance section
* @param {PartialContext} ctx
* @param {Market["returns"]} returns
*/
export function createPerformanceSection(ctx, returns) {
const { colors, baseline, createPriceLine } = ctx;
return {
name: "Performance",
tree: /** @type {const} */ ([
["1d", "_1d", undefined],
["1w", "_1w", undefined],
["1m", "_1m", undefined],
["3m", "_3m", undefined],
["6m", "_6m", undefined],
["1y", "_1y", undefined],
["2y", "_2y", "_2y"],
["3y", "_3y", "_3y"],
["4y", "_4y", "_4y"],
["5y", "_5y", "_5y"],
["6y", "_6y", "_6y"],
["8y", "_8y", "_8y"],
["10y", "_10y", "_10y"],
]).map(([id, returnKey, cagrKey]) => {
const priceReturns = returns.priceReturns[returnKey];
const cagr = cagrKey ? returns.cagr[cagrKey] : undefined;
const name = periodIdToName(id, true);
return {
name,
title: `${name} Performance`,
bottom: [
baseline({
metric: priceReturns,
name: "Total",
unit: Unit.percentage,
}),
...(cagr
? [
baseline({
metric: cagr,
name: "CAGR",
color: [colors.lime, colors.pink],
unit: Unit.percentage,
}),
]
: []),
createPriceLine({ unit: Unit.percentage }),
],
};
}),
};
}

View File

@@ -0,0 +1,23 @@
/** Market utilities */
/**
* Convert period ID to readable name
* @param {string} id
* @param {boolean} [compoundAdjective]
*/
export function periodIdToName(id, compoundAdjective) {
const num = parseInt(id);
const s = compoundAdjective || num === 1 ? "" : "s";
switch (id.slice(-1)) {
case "d":
return `${num} day${s}`;
case "w":
return `${num} week${s}`;
case "m":
return `${num} month${s}`;
case "y":
return `${num} year${s}`;
default:
return id;
}
}