mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-24 06:39:58 -07:00
website: snapshot
This commit is contained in:
@@ -191,6 +191,7 @@ export function createChainSection(ctx) {
|
||||
bottom: fromFullStatsPattern({
|
||||
pattern: distribution.newAddrCount[key],
|
||||
unit: Unit.count,
|
||||
cumulativeUnit: Unit.countCumulative,
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -316,20 +317,17 @@ export function createChainSection(ctx) {
|
||||
...fromValuePattern({
|
||||
pattern: pool.coinbase,
|
||||
title: "coinbase",
|
||||
sumColor: colors.orange,
|
||||
cumulativeColor: colors.red,
|
||||
color: colors.orange,
|
||||
}),
|
||||
...fromValuePattern({
|
||||
pattern: pool.subsidy,
|
||||
title: "subsidy",
|
||||
sumColor: colors.lime,
|
||||
cumulativeColor: colors.emerald,
|
||||
color: colors.lime,
|
||||
}),
|
||||
...fromValuePattern({
|
||||
pattern: pool.fee,
|
||||
title: "fee",
|
||||
sumColor: colors.cyan,
|
||||
cumulativeColor: colors.indigo,
|
||||
color: colors.cyan,
|
||||
}),
|
||||
],
|
||||
},
|
||||
@@ -367,6 +365,7 @@ export function createChainSection(ctx) {
|
||||
...fromCountPattern({
|
||||
pattern: blocks.count.blockCount,
|
||||
unit: Unit.count,
|
||||
cumulativeUnit: Unit.countCumulative,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.count.blockCountTarget,
|
||||
@@ -424,6 +423,7 @@ export function createChainSection(ctx) {
|
||||
...fromSumStatsPattern({
|
||||
pattern: blocks.size,
|
||||
unit: Unit.bytes,
|
||||
cumulativeUnit: Unit.bytesCumulative,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.totalSize,
|
||||
@@ -440,20 +440,6 @@ export function createChainSection(ctx) {
|
||||
pattern: blocks.weight,
|
||||
unit: Unit.wu,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.weight.sum,
|
||||
name: "Sum",
|
||||
color: colors.stat.sum,
|
||||
unit: Unit.wu,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.weight.cumulative,
|
||||
name: "Cumulative",
|
||||
color: colors.stat.cumulative,
|
||||
unit: Unit.wu,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -477,6 +463,7 @@ export function createChainSection(ctx) {
|
||||
bottom: fromFullStatsPattern({
|
||||
pattern: transactions.count.txCount,
|
||||
unit: Unit.count,
|
||||
cumulativeUnit: Unit.countCumulative,
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -541,23 +528,23 @@ export function createChainSection(ctx) {
|
||||
...fromCountPattern({
|
||||
pattern: transactions.versions.v1,
|
||||
unit: Unit.count,
|
||||
cumulativeUnit: Unit.countCumulative,
|
||||
title: "v1",
|
||||
sumColor: colors.orange,
|
||||
cumulativeColor: colors.red,
|
||||
color: colors.orange,
|
||||
}),
|
||||
...fromCountPattern({
|
||||
pattern: transactions.versions.v2,
|
||||
unit: Unit.count,
|
||||
cumulativeUnit: Unit.countCumulative,
|
||||
title: "v2",
|
||||
sumColor: colors.cyan,
|
||||
cumulativeColor: colors.blue,
|
||||
color: colors.cyan,
|
||||
}),
|
||||
...fromCountPattern({
|
||||
pattern: transactions.versions.v3,
|
||||
unit: Unit.count,
|
||||
cumulativeUnit: Unit.countCumulative,
|
||||
title: "v3",
|
||||
sumColor: colors.lime,
|
||||
cumulativeColor: colors.green,
|
||||
color: colors.lime,
|
||||
}),
|
||||
],
|
||||
},
|
||||
@@ -592,6 +579,7 @@ export function createChainSection(ctx) {
|
||||
...fromSumStatsPattern({
|
||||
pattern: inputs.count,
|
||||
unit: Unit.count,
|
||||
cumulativeUnit: Unit.countCumulative,
|
||||
}),
|
||||
],
|
||||
},
|
||||
@@ -602,6 +590,7 @@ export function createChainSection(ctx) {
|
||||
...fromSumStatsPattern({
|
||||
pattern: outputs.count.totalCount,
|
||||
unit: Unit.count,
|
||||
cumulativeUnit: Unit.countCumulative,
|
||||
}),
|
||||
],
|
||||
},
|
||||
@@ -658,6 +647,7 @@ export function createChainSection(ctx) {
|
||||
bottom: fromFullStatsPattern({
|
||||
pattern: scripts.count.p2pkh,
|
||||
unit: Unit.count,
|
||||
cumulativeUnit: Unit.countCumulative,
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -666,6 +656,7 @@ export function createChainSection(ctx) {
|
||||
bottom: fromFullStatsPattern({
|
||||
pattern: scripts.count.p2pk33,
|
||||
unit: Unit.count,
|
||||
cumulativeUnit: Unit.countCumulative,
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -674,6 +665,7 @@ export function createChainSection(ctx) {
|
||||
bottom: fromFullStatsPattern({
|
||||
pattern: scripts.count.p2pk65,
|
||||
unit: Unit.count,
|
||||
cumulativeUnit: Unit.countCumulative,
|
||||
}),
|
||||
},
|
||||
],
|
||||
@@ -688,6 +680,7 @@ export function createChainSection(ctx) {
|
||||
bottom: fromFullStatsPattern({
|
||||
pattern: scripts.count.p2sh,
|
||||
unit: Unit.count,
|
||||
cumulativeUnit: Unit.countCumulative,
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -696,6 +689,7 @@ export function createChainSection(ctx) {
|
||||
bottom: fromFullStatsPattern({
|
||||
pattern: scripts.count.p2ms,
|
||||
unit: Unit.count,
|
||||
cumulativeUnit: Unit.countCumulative,
|
||||
}),
|
||||
},
|
||||
],
|
||||
@@ -710,6 +704,7 @@ export function createChainSection(ctx) {
|
||||
bottom: fromFullStatsPattern({
|
||||
pattern: scripts.count.segwit,
|
||||
unit: Unit.count,
|
||||
cumulativeUnit: Unit.countCumulative,
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -718,6 +713,7 @@ export function createChainSection(ctx) {
|
||||
bottom: fromFullStatsPattern({
|
||||
pattern: scripts.count.p2wpkh,
|
||||
unit: Unit.count,
|
||||
cumulativeUnit: Unit.countCumulative,
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -726,6 +722,7 @@ export function createChainSection(ctx) {
|
||||
bottom: fromFullStatsPattern({
|
||||
pattern: scripts.count.p2wsh,
|
||||
unit: Unit.count,
|
||||
cumulativeUnit: Unit.countCumulative,
|
||||
}),
|
||||
},
|
||||
],
|
||||
@@ -740,6 +737,7 @@ export function createChainSection(ctx) {
|
||||
bottom: fromFullStatsPattern({
|
||||
pattern: scripts.count.p2tr,
|
||||
unit: Unit.count,
|
||||
cumulativeUnit: Unit.countCumulative,
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -748,6 +746,7 @@ export function createChainSection(ctx) {
|
||||
bottom: fromFullStatsPattern({
|
||||
pattern: scripts.count.p2a,
|
||||
unit: Unit.count,
|
||||
cumulativeUnit: Unit.countCumulative,
|
||||
}),
|
||||
},
|
||||
],
|
||||
@@ -762,6 +761,7 @@ export function createChainSection(ctx) {
|
||||
bottom: fromFullStatsPattern({
|
||||
pattern: scripts.count.opreturn,
|
||||
unit: Unit.count,
|
||||
cumulativeUnit: Unit.countCumulative,
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -770,6 +770,7 @@ export function createChainSection(ctx) {
|
||||
bottom: fromFullStatsPattern({
|
||||
pattern: scripts.count.emptyoutput,
|
||||
unit: Unit.count,
|
||||
cumulativeUnit: Unit.countCumulative,
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -778,6 +779,7 @@ export function createChainSection(ctx) {
|
||||
bottom: fromFullStatsPattern({
|
||||
pattern: scripts.count.unknownoutput,
|
||||
unit: Unit.count,
|
||||
cumulativeUnit: Unit.countCumulative,
|
||||
}),
|
||||
},
|
||||
],
|
||||
@@ -799,15 +801,13 @@ export function createChainSection(ctx) {
|
||||
line({
|
||||
metric: scripts.count.segwitAdoption.sum,
|
||||
name: "Sum",
|
||||
color: colors.stat.sum,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
line({
|
||||
metric: scripts.count.segwitAdoption.cumulative,
|
||||
name: "Cumulative",
|
||||
color: colors.stat.cumulative,
|
||||
color: colors.red,
|
||||
unit: Unit.percentage,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
@@ -823,15 +823,13 @@ export function createChainSection(ctx) {
|
||||
line({
|
||||
metric: scripts.count.taprootAdoption.sum,
|
||||
name: "Sum",
|
||||
color: colors.stat.sum,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
line({
|
||||
metric: scripts.count.taprootAdoption.cumulative,
|
||||
name: "Cumulative",
|
||||
color: colors.stat.cumulative,
|
||||
color: colors.red,
|
||||
unit: Unit.percentage,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
@@ -926,14 +924,17 @@ export function createChainSection(ctx) {
|
||||
...fromSumStatsPattern({
|
||||
pattern: transactions.fees.fee.bitcoin,
|
||||
unit: Unit.btc,
|
||||
cumulativeUnit: Unit.btcCumulative,
|
||||
}),
|
||||
...fromSumStatsPattern({
|
||||
pattern: transactions.fees.fee.sats,
|
||||
unit: Unit.sats,
|
||||
cumulativeUnit: Unit.satsCumulative,
|
||||
}),
|
||||
...fromSumStatsPattern({
|
||||
pattern: transactions.fees.fee.dollars,
|
||||
unit: Unit.usd,
|
||||
cumulativeUnit: Unit.usdCumulative,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.rewards.feeDominance,
|
||||
@@ -949,7 +950,6 @@ export function createChainSection(ctx) {
|
||||
title: "Unclaimed Rewards",
|
||||
bottom: fromValuePattern({
|
||||
pattern: blocks.rewards.unclaimedRewards,
|
||||
title: "Unclaimed",
|
||||
}),
|
||||
},
|
||||
],
|
||||
|
||||
@@ -147,6 +147,7 @@ export const spendableTypeColors = {
|
||||
p2wsh: "blue",
|
||||
p2tr: "indigo",
|
||||
p2a: "purple",
|
||||
opreturn: "pink",
|
||||
unknown: "violet",
|
||||
empty: "fuchsia",
|
||||
};
|
||||
|
||||
@@ -43,9 +43,9 @@ export function createContext({ brk }) {
|
||||
fromFullStatsPattern: bind(fromFullStatsPattern),
|
||||
fromStatsPattern: bind(fromStatsPattern),
|
||||
fromCoinbasePattern: bind(fromCoinbasePattern),
|
||||
fromValuePattern: bind(fromValuePattern),
|
||||
fromBitcoinPatternWithUnit: bind(fromBitcoinPatternWithUnit),
|
||||
fromCountPattern: bind(fromCountPattern),
|
||||
fromValuePattern,
|
||||
fromBitcoinPatternWithUnit,
|
||||
fromCountPattern,
|
||||
fromSupplyPattern,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -994,8 +994,9 @@ function createSingleRealizedPnlSection(
|
||||
...fromCountPattern({
|
||||
pattern: tree.realized.realizedProfit,
|
||||
unit: Unit.usd,
|
||||
cumulativeUnit: Unit.usdCumulative,
|
||||
title: "Profit",
|
||||
sumColor: colors.green,
|
||||
color: colors.green,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.realizedProfit7dEma,
|
||||
@@ -1006,8 +1007,9 @@ function createSingleRealizedPnlSection(
|
||||
...fromCountPattern({
|
||||
pattern: tree.realized.realizedLoss,
|
||||
unit: Unit.usd,
|
||||
cumulativeUnit: Unit.usdCumulative,
|
||||
title: "Loss",
|
||||
sumColor: colors.red,
|
||||
color: colors.red,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.realizedLoss7dEma,
|
||||
@@ -1018,8 +1020,9 @@ function createSingleRealizedPnlSection(
|
||||
...fromBitcoinPatternWithUnit({
|
||||
pattern: tree.realized.negRealizedLoss,
|
||||
unit: Unit.usd,
|
||||
cumulativeUnit: Unit.usdCumulative,
|
||||
title: "Negative Loss",
|
||||
sumColor: colors.red,
|
||||
color: colors.red,
|
||||
defaultActive: false,
|
||||
}),
|
||||
...extra,
|
||||
@@ -1067,6 +1070,7 @@ function createSingleRealizedPnlSection(
|
||||
...fromCountPattern({
|
||||
pattern: tree.realized.netRealizedPnl,
|
||||
unit: Unit.usd,
|
||||
cumulativeUnit: Unit.usdCumulative,
|
||||
title: "Net",
|
||||
}),
|
||||
baseline({
|
||||
@@ -2904,17 +2908,20 @@ function createActivitySection({ ctx, cohort, title, valueMetrics = [] }) {
|
||||
...fromCountPattern({
|
||||
pattern: tree.activity.sent.sats,
|
||||
unit: Unit.sats,
|
||||
sumColor: color,
|
||||
cumulativeUnit: Unit.satsCumulative,
|
||||
color: color,
|
||||
}),
|
||||
...fromBitcoinPatternWithUnit({
|
||||
pattern: tree.activity.sent.bitcoin,
|
||||
unit: Unit.btc,
|
||||
sumColor: color,
|
||||
cumulativeUnit: Unit.btcCumulative,
|
||||
color: color,
|
||||
}),
|
||||
...fromCountPattern({
|
||||
pattern: tree.activity.sent.dollars,
|
||||
unit: Unit.usd,
|
||||
sumColor: color,
|
||||
cumulativeUnit: Unit.usdCumulative,
|
||||
color: color,
|
||||
}),
|
||||
line({
|
||||
metric: tree.activity.sent14dEma.sats,
|
||||
|
||||
@@ -1,11 +1,72 @@
|
||||
/** Investing section - Investment strategy tools and analysis */
|
||||
|
||||
import { Unit } from "../utils/units.js";
|
||||
import { priceLine } from "./constants.js";
|
||||
import { line, baseline, price, dotted } from "./series.js";
|
||||
import { satsBtcUsd } from "./shared.js";
|
||||
import { periodIdToName } from "./utils.js";
|
||||
|
||||
const SHORT_PERIODS = /** @type {const} */ (["_1w", "_1m", "_3m", "_6m", "_1y"]);
|
||||
const LONG_PERIODS = /** @type {const} */ (["_2y", "_3y", "_4y", "_5y", "_6y", "_8y", "_10y"]);
|
||||
|
||||
/** @typedef {typeof SHORT_PERIODS[number]} ShortPeriodKey */
|
||||
/** @typedef {typeof LONG_PERIODS[number]} LongPeriodKey */
|
||||
/** @typedef {ShortPeriodKey | LongPeriodKey} AllPeriodKey */
|
||||
|
||||
/**
|
||||
* Add CAGR to a base entry item
|
||||
* @param {BaseEntryItem} entry
|
||||
* @param {AnyMetricPattern} cagr
|
||||
* @returns {LongEntryItem}
|
||||
*/
|
||||
const withCagr = (entry, cagr) => ({ ...entry, cagr });
|
||||
|
||||
const YEARS_2020S = /** @type {const} */ ([2026, 2025, 2024, 2023, 2022, 2021, 2020]);
|
||||
const YEARS_2010S = /** @type {const} */ ([2019, 2018, 2017, 2016, 2015]);
|
||||
|
||||
/** @param {AllPeriodKey} key */
|
||||
const periodName = (key) => periodIdToName(key.slice(1), true);
|
||||
|
||||
/**
|
||||
* Base entry item for compare and single-entry charts
|
||||
* @typedef {Object} BaseEntryItem
|
||||
* @property {string} name - Display name
|
||||
* @property {Color} color - Item color
|
||||
* @property {AnyPricePattern} costBasis - Cost basis metric
|
||||
* @property {AnyMetricPattern} returns - Returns metric
|
||||
* @property {AnyMetricPattern} minReturn - Min return metric
|
||||
* @property {AnyMetricPattern} maxReturn - Max return metric
|
||||
* @property {AnyMetricPattern} daysInProfit - Days in profit metric
|
||||
* @property {AnyMetricPattern} daysInLoss - Days in loss metric
|
||||
* @property {AnyValuePattern} stack - Stack pattern
|
||||
*/
|
||||
|
||||
/**
|
||||
* Long-term entry item with CAGR
|
||||
* @typedef {BaseEntryItem & { cagr: AnyMetricPattern }} LongEntryItem
|
||||
*/
|
||||
|
||||
/**
|
||||
* Build DCA class entry from year
|
||||
* @param {Colors} colors
|
||||
* @param {MarketDca} dca
|
||||
* @param {number} year
|
||||
* @returns {BaseEntryItem}
|
||||
*/
|
||||
function buildYearEntry(colors, dca, year) {
|
||||
const key = /** @type {keyof Colors["dcaYears"]} */ (`_${year}`);
|
||||
return {
|
||||
name: `${year}`,
|
||||
color: colors.dcaYears[key],
|
||||
costBasis: dca.classAveragePrice[key],
|
||||
returns: dca.classReturns[key],
|
||||
minReturn: dca.classMinReturn[key],
|
||||
maxReturn: dca.classMaxReturn[key],
|
||||
daysInProfit: dca.classDaysInProfit[key],
|
||||
daysInLoss: dca.classDaysInLoss[key],
|
||||
stack: dca.classStack[key],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Investing section
|
||||
* @param {PartialContext} ctx
|
||||
@@ -20,99 +81,17 @@ export function createInvestingSection(ctx) {
|
||||
name: "Investing",
|
||||
tree: [
|
||||
createDcaVsLumpSumSection(ctx, { dca, lookback, returns }),
|
||||
createDcaByPeriodSection(ctx, { dca }),
|
||||
createLumpSumByPeriodSection(ctx, { dca, lookback }),
|
||||
createDcaByPeriodSection(ctx, { dca, returns }),
|
||||
createLumpSumByPeriodSection(ctx, { dca, lookback, returns }),
|
||||
createDcaByStartYearSection(ctx, { dca }),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/** Period configuration by term group */
|
||||
const PERIODS = {
|
||||
short: [
|
||||
{ id: "1w", key: /** @type {const} */ ("_1w") },
|
||||
{ id: "1m", key: /** @type {const} */ ("_1m") },
|
||||
{ id: "3m", key: /** @type {const} */ ("_3m") },
|
||||
{ id: "6m", key: /** @type {const} */ ("_6m") },
|
||||
],
|
||||
medium: [
|
||||
{ id: "1y", key: /** @type {const} */ ("_1y") },
|
||||
{ id: "2y", key: /** @type {const} */ ("_2y") },
|
||||
{ id: "3y", key: /** @type {const} */ ("_3y") },
|
||||
],
|
||||
long: [
|
||||
{ id: "4y", key: /** @type {const} */ ("_4y") },
|
||||
{ id: "5y", key: /** @type {const} */ ("_5y") },
|
||||
{ id: "6y", key: /** @type {const} */ ("_6y") },
|
||||
{ id: "8y", key: /** @type {const} */ ("_8y") },
|
||||
{ id: "10y", key: /** @type {const} */ ("_10y") },
|
||||
],
|
||||
};
|
||||
|
||||
const ALL_PERIODS = [...PERIODS.short, ...PERIODS.medium, ...PERIODS.long];
|
||||
|
||||
/** DCA year classes by decade */
|
||||
const YEAR_GROUPS = {
|
||||
_2020s: /** @type {const} */ ([2026, 2025, 2024, 2023, 2022, 2021, 2020]),
|
||||
_2010s: /** @type {const} */ ([2019, 2018, 2017, 2016, 2015]),
|
||||
};
|
||||
|
||||
const ALL_YEARS = [...YEAR_GROUPS._2020s, ...YEAR_GROUPS._2010s];
|
||||
|
||||
/** @typedef {ReturnType<typeof buildYearClass>} YearClass */
|
||||
|
||||
/**
|
||||
* Build DCA class data from year
|
||||
* @param {Colors} colors
|
||||
* @param {MarketDca} dca
|
||||
* @param {number} year
|
||||
*/
|
||||
function buildYearClass(colors, dca, year) {
|
||||
const key = /** @type {keyof Colors["dcaYears"]} */ (`_${year}`);
|
||||
return {
|
||||
year,
|
||||
color: colors.dcaYears[key],
|
||||
costBasis: dca.classAveragePrice[key],
|
||||
returns: dca.classReturns[key],
|
||||
stack: dca.classStack[key],
|
||||
daysInProfit: dca.classDaysInProfit[key],
|
||||
daysInLoss: dca.classDaysInLoss[key],
|
||||
minReturn: dca.classMinReturn[key],
|
||||
maxReturn: dca.classMaxReturn[key],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Pattern for creating a single entry (period or year)
|
||||
* @typedef {Object} SingleEntryPattern
|
||||
* @property {string} name - Display name
|
||||
* @property {string} [titlePrefix] - Prefix for chart titles (defaults to name)
|
||||
* @property {Color} color - Primary color
|
||||
* @property {AnyPricePattern} costBasis - Cost basis metric
|
||||
* @property {AnyMetricPattern} returns - Returns metric
|
||||
* @property {AnyMetricPattern} minReturn - Min return metric
|
||||
* @property {AnyMetricPattern} maxReturn - Max return metric
|
||||
* @property {AnyMetricPattern} daysInProfit - Days in profit metric
|
||||
* @property {AnyMetricPattern} daysInLoss - Days in loss metric
|
||||
* @property {AnyValuePattern} stack - Stack pattern
|
||||
*/
|
||||
|
||||
/**
|
||||
* Item for compare charts
|
||||
* @typedef {Object} CompareItem
|
||||
* @property {string} name - Display name
|
||||
* @property {Color} color - Item color
|
||||
* @property {AnyPricePattern} costBasis - Cost basis metric
|
||||
* @property {AnyMetricPattern} returns - Returns metric
|
||||
* @property {AnyMetricPattern} daysInProfit - Days in profit metric
|
||||
* @property {AnyMetricPattern} daysInLoss - Days in loss metric
|
||||
* @property {AnyValuePattern} stack - Stack pattern
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create profitability folder for compare charts
|
||||
* @param {string} context
|
||||
* @param {CompareItem[]} items
|
||||
* @param {Pick<BaseEntryItem, 'name' | 'color' | 'costBasis' | 'daysInProfit' | 'daysInLoss'>[]} items
|
||||
*/
|
||||
function createProfitabilityFolder(context, items) {
|
||||
const top = items.map(({ name, color, costBasis }) =>
|
||||
@@ -144,7 +123,7 @@ function createProfitabilityFolder(context, items) {
|
||||
/**
|
||||
* Create compare folder from items
|
||||
* @param {string} context
|
||||
* @param {CompareItem[]} items
|
||||
* @param {Pick<BaseEntryItem, 'name' | 'color' | 'costBasis' | 'returns' | 'daysInProfit' | 'daysInLoss' | 'stack'>[]} items
|
||||
*/
|
||||
function createCompareFolder(context, items) {
|
||||
const topPane = items.map(({ name, color, costBasis }) =>
|
||||
@@ -163,12 +142,7 @@ function createCompareFolder(context, items) {
|
||||
title: `Returns: ${context}`,
|
||||
top: topPane,
|
||||
bottom: items.map(({ name, color, returns }) =>
|
||||
baseline({
|
||||
metric: returns,
|
||||
name,
|
||||
color: [color, color],
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
baseline({ metric: returns, name, color: [color, color], unit: Unit.percentage }),
|
||||
),
|
||||
},
|
||||
createProfitabilityFolder(context, items),
|
||||
@@ -185,79 +159,62 @@ function createCompareFolder(context, items) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a single entry from a pattern
|
||||
* Create single entry tree structure
|
||||
* @param {Colors} colors
|
||||
* @param {SingleEntryPattern} pattern
|
||||
* @param {BaseEntryItem & { titlePrefix?: string }} item
|
||||
* @param {object[]} returnsBottom - Bottom pane items for returns chart
|
||||
*/
|
||||
function createSingleEntry(colors, pattern) {
|
||||
const {
|
||||
name,
|
||||
titlePrefix = name,
|
||||
color,
|
||||
costBasis,
|
||||
returns,
|
||||
minReturn,
|
||||
maxReturn,
|
||||
daysInProfit,
|
||||
daysInLoss,
|
||||
stack,
|
||||
} = pattern;
|
||||
function createSingleEntryTree(colors, item, returnsBottom) {
|
||||
const { name, titlePrefix = name, color, costBasis, daysInProfit, daysInLoss, stack } = item;
|
||||
const top = [price({ metric: costBasis, name: "Cost Basis", color })];
|
||||
return {
|
||||
name,
|
||||
tree: [
|
||||
{ name: "Cost Basis", title: `Cost Basis: ${titlePrefix}`, top },
|
||||
{
|
||||
name: "Returns",
|
||||
title: `Returns: ${titlePrefix}`,
|
||||
top,
|
||||
bottom: [
|
||||
baseline({ metric: returns, name: "Current", unit: Unit.percentage }),
|
||||
dotted({
|
||||
metric: maxReturn,
|
||||
name: "Max",
|
||||
color: colors.green,
|
||||
unit: Unit.percentage,
|
||||
defaultActive: false,
|
||||
}),
|
||||
dotted({
|
||||
metric: minReturn,
|
||||
name: "Min",
|
||||
color: colors.red,
|
||||
unit: Unit.percentage,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{ name: "Returns", title: `Returns: ${titlePrefix}`, top, bottom: returnsBottom },
|
||||
{
|
||||
name: "Profitability",
|
||||
title: `Profitability: ${titlePrefix}`,
|
||||
top,
|
||||
bottom: [
|
||||
line({
|
||||
metric: daysInProfit,
|
||||
name: "Days in Profit",
|
||||
color: colors.green,
|
||||
unit: Unit.days,
|
||||
}),
|
||||
line({
|
||||
metric: daysInLoss,
|
||||
name: "Days in Loss",
|
||||
color: colors.red,
|
||||
unit: Unit.days,
|
||||
}),
|
||||
line({ metric: daysInProfit, name: "Days in Profit", color: colors.green, unit: Unit.days }),
|
||||
line({ metric: daysInLoss, name: "Days in Loss", color: colors.red, unit: Unit.days }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Accumulated",
|
||||
title: `Accumulated Value: ${titlePrefix}`,
|
||||
top,
|
||||
bottom: satsBtcUsd({ pattern: stack, name: "Value" }),
|
||||
},
|
||||
{ name: "Accumulated", title: `Accumulated Value: ${titlePrefix}`, top, bottom: satsBtcUsd({ pattern: stack, name: "Value" }) },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a single entry from a base item (no CAGR)
|
||||
* @param {Colors} colors
|
||||
* @param {BaseEntryItem & { titlePrefix?: string }} item
|
||||
*/
|
||||
function createShortSingleEntry(colors, item) {
|
||||
const { returns, minReturn, maxReturn } = item;
|
||||
return createSingleEntryTree(colors, item, [
|
||||
baseline({ metric: returns, name: "Current", unit: Unit.percentage }),
|
||||
dotted({ metric: maxReturn, name: "Max", color: colors.green, unit: Unit.percentage, defaultActive: false }),
|
||||
dotted({ metric: minReturn, name: "Min", color: colors.red, unit: Unit.percentage, defaultActive: false }),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a single entry from a long item (with CAGR)
|
||||
* @param {Colors} colors
|
||||
* @param {LongEntryItem & { titlePrefix?: string }} item
|
||||
*/
|
||||
function createLongSingleEntry(colors, item) {
|
||||
const { returns, minReturn, maxReturn, cagr } = item;
|
||||
return createSingleEntryTree(colors, item, [
|
||||
baseline({ metric: returns, name: "Current", unit: Unit.percentage }),
|
||||
baseline({ metric: cagr, name: "CAGR", unit: Unit.cagr }),
|
||||
dotted({ metric: maxReturn, name: "Max", color: colors.green, unit: Unit.percentage, defaultActive: false }),
|
||||
dotted({ metric: minReturn, name: "Min", color: colors.red, unit: Unit.percentage, defaultActive: false }),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create DCA vs Lump Sum section
|
||||
* @param {PartialContext} ctx
|
||||
@@ -269,14 +226,9 @@ function createSingleEntry(colors, pattern) {
|
||||
export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) {
|
||||
const { colors } = ctx;
|
||||
|
||||
// Chart builders
|
||||
/** @param {AllPeriodKey} key */
|
||||
const topPane = (key) => [
|
||||
price({
|
||||
metric: dca.periodAveragePrice[key],
|
||||
name: "DCA",
|
||||
color: colors.green,
|
||||
}),
|
||||
price({ metric: dca.periodAveragePrice[key], name: "DCA", color: colors.green }),
|
||||
price({ metric: lookback[key], name: "Lump Sum", color: colors.orange }),
|
||||
];
|
||||
|
||||
@@ -288,7 +240,29 @@ export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) {
|
||||
});
|
||||
|
||||
/** @param {string} name @param {AllPeriodKey} key */
|
||||
const returnsFolder = (name, key) => ({
|
||||
const returnsMinMax = (name, key) => [
|
||||
{
|
||||
name: "Max",
|
||||
title: `Max Return: ${name} DCA vs Lump Sum`,
|
||||
top: topPane(key),
|
||||
bottom: [
|
||||
baseline({ metric: dca.periodMaxReturn[key], name: "DCA", unit: Unit.percentage }),
|
||||
baseline({ metric: dca.periodLumpSumMaxReturn[key], name: "Lump Sum", color: [colors.cyan, colors.orange], unit: Unit.percentage }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Min",
|
||||
title: `Min Return: ${name} DCA vs Lump Sum`,
|
||||
top: topPane(key),
|
||||
bottom: [
|
||||
baseline({ metric: dca.periodMinReturn[key], name: "DCA", unit: Unit.percentage }),
|
||||
baseline({ metric: dca.periodLumpSumMinReturn[key], name: "Lump Sum", color: [colors.cyan, colors.orange], unit: Unit.percentage }),
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
/** @param {string} name @param {ShortPeriodKey} key */
|
||||
const shortReturnsFolder = (name, key) => ({
|
||||
name: "Returns",
|
||||
tree: [
|
||||
{
|
||||
@@ -296,60 +270,16 @@ export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) {
|
||||
title: `Returns: ${name} DCA vs Lump Sum`,
|
||||
top: topPane(key),
|
||||
bottom: [
|
||||
baseline({
|
||||
metric: dca.periodReturns[key],
|
||||
name: "DCA",
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
baseline({
|
||||
metric: dca.periodLumpSumReturns[key],
|
||||
name: "Lump Sum",
|
||||
color: [colors.cyan, colors.orange],
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Max",
|
||||
title: `Max Return: ${name} DCA vs Lump Sum`,
|
||||
top: topPane(key),
|
||||
bottom: [
|
||||
baseline({
|
||||
metric: dca.periodMaxReturn[key],
|
||||
name: "DCA",
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
baseline({
|
||||
metric: dca.periodLumpSumMaxReturn[key],
|
||||
name: "Lump Sum",
|
||||
color: [colors.cyan, colors.orange],
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Min",
|
||||
title: `Min Return: ${name} DCA vs Lump Sum`,
|
||||
top: topPane(key),
|
||||
bottom: [
|
||||
baseline({
|
||||
metric: dca.periodMinReturn[key],
|
||||
name: "DCA",
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
baseline({
|
||||
metric: dca.periodLumpSumMinReturn[key],
|
||||
name: "Lump Sum",
|
||||
color: [colors.cyan, colors.orange],
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
baseline({ metric: dca.periodReturns[key], name: "DCA", unit: Unit.percentage }),
|
||||
baseline({ metric: dca.periodLumpSumReturns[key], name: "Lump Sum", color: [colors.cyan, colors.orange], unit: Unit.percentage }),
|
||||
],
|
||||
},
|
||||
...returnsMinMax(name, key),
|
||||
],
|
||||
});
|
||||
|
||||
/** @param {string} name @param {LongPeriodKey} key */
|
||||
const returnsFolderWithCagr = (name, key) => ({
|
||||
const longReturnsFolder = (name, key) => ({
|
||||
name: "Returns",
|
||||
tree: [
|
||||
{
|
||||
@@ -357,72 +287,13 @@ export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) {
|
||||
title: `Returns: ${name} DCA vs Lump Sum`,
|
||||
top: topPane(key),
|
||||
bottom: [
|
||||
baseline({
|
||||
metric: dca.periodReturns[key],
|
||||
name: "DCA",
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
baseline({
|
||||
metric: dca.periodLumpSumReturns[key],
|
||||
name: "Lump Sum",
|
||||
color: [colors.cyan, colors.orange],
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
line({
|
||||
metric: dca.periodCagr[key],
|
||||
name: "DCA CAGR",
|
||||
color: colors.purple,
|
||||
unit: Unit.percentage,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: returns.cagr[key],
|
||||
name: "Lump Sum CAGR",
|
||||
color: colors.indigo,
|
||||
unit: Unit.percentage,
|
||||
defaultActive: false,
|
||||
}),
|
||||
priceLine({ ctx, unit: Unit.percentage }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Max",
|
||||
title: `Max Return: ${name} DCA vs Lump Sum`,
|
||||
top: topPane(key),
|
||||
bottom: [
|
||||
line({
|
||||
metric: dca.periodMaxReturn[key],
|
||||
name: "DCA",
|
||||
color: colors.green,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
line({
|
||||
metric: dca.periodLumpSumMaxReturn[key],
|
||||
name: "Lump Sum",
|
||||
color: colors.orange,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Min",
|
||||
title: `Min Return: ${name} DCA vs Lump Sum`,
|
||||
top: topPane(key),
|
||||
bottom: [
|
||||
line({
|
||||
metric: dca.periodMinReturn[key],
|
||||
name: "DCA",
|
||||
color: colors.green,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
line({
|
||||
metric: dca.periodLumpSumMinReturn[key],
|
||||
name: "Lump Sum",
|
||||
color: colors.orange,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
baseline({ metric: dca.periodReturns[key], name: "DCA", unit: Unit.percentage }),
|
||||
baseline({ metric: dca.periodLumpSumReturns[key], name: "Lump Sum", color: [colors.cyan, colors.orange], unit: Unit.percentage }),
|
||||
baseline({ metric: dca.periodCagr[key], name: "DCA CAGR", unit: Unit.cagr }),
|
||||
baseline({ metric: returns.cagr[key], name: "Lump Sum CAGR", color: [colors.cyan, colors.orange], unit: Unit.cagr }),
|
||||
],
|
||||
},
|
||||
...returnsMinMax(name, key),
|
||||
],
|
||||
});
|
||||
|
||||
@@ -435,18 +306,8 @@ export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) {
|
||||
title: `Days in Profit: ${name} DCA vs Lump Sum`,
|
||||
top: topPane(key),
|
||||
bottom: [
|
||||
line({
|
||||
metric: dca.periodDaysInProfit[key],
|
||||
name: "DCA",
|
||||
color: colors.green,
|
||||
unit: Unit.days,
|
||||
}),
|
||||
line({
|
||||
metric: dca.periodLumpSumDaysInProfit[key],
|
||||
name: "Lump Sum",
|
||||
color: colors.orange,
|
||||
unit: Unit.days,
|
||||
}),
|
||||
line({ metric: dca.periodDaysInProfit[key], name: "DCA", color: colors.green, unit: Unit.days }),
|
||||
line({ metric: dca.periodLumpSumDaysInProfit[key], name: "Lump Sum", color: colors.orange, unit: Unit.days }),
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -454,18 +315,8 @@ export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) {
|
||||
title: `Days in Loss: ${name} DCA vs Lump Sum`,
|
||||
top: topPane(key),
|
||||
bottom: [
|
||||
line({
|
||||
metric: dca.periodDaysInLoss[key],
|
||||
name: "DCA",
|
||||
color: colors.green,
|
||||
unit: Unit.days,
|
||||
}),
|
||||
line({
|
||||
metric: dca.periodLumpSumDaysInLoss[key],
|
||||
name: "Lump Sum",
|
||||
color: colors.orange,
|
||||
unit: Unit.days,
|
||||
}),
|
||||
line({ metric: dca.periodDaysInLoss[key], name: "DCA", color: colors.green, unit: Unit.days }),
|
||||
line({ metric: dca.periodLumpSumDaysInLoss[key], name: "Lump Sum", color: colors.orange, unit: Unit.days }),
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -474,210 +325,125 @@ export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) {
|
||||
/** @param {string} name @param {AllPeriodKey} key */
|
||||
const stackChart = (name, key) => ({
|
||||
name: "Accumulated",
|
||||
title: `Accumulated Value: ${name} DCA vs Lump Sum`,
|
||||
title: `Accumulated Value ($100/day): ${name} DCA vs Lump Sum`,
|
||||
top: topPane(key),
|
||||
bottom: [
|
||||
...satsBtcUsd({
|
||||
pattern: dca.periodStack[key],
|
||||
name: "DCA",
|
||||
color: colors.green,
|
||||
}),
|
||||
...satsBtcUsd({
|
||||
pattern: dca.periodLumpSumStack[key],
|
||||
name: "Lump Sum",
|
||||
color: colors.orange,
|
||||
}),
|
||||
...satsBtcUsd({ pattern: dca.periodStack[key], name: "DCA", color: colors.green }),
|
||||
...satsBtcUsd({ pattern: dca.periodLumpSumStack[key], name: "Lump Sum", color: colors.orange }),
|
||||
],
|
||||
});
|
||||
|
||||
/**
|
||||
* Check if a period key has CAGR data
|
||||
* @param {AllPeriodKey} key
|
||||
* @returns {key is LongPeriodKey}
|
||||
*/
|
||||
const hasCagr = (key) => key in dca.periodCagr;
|
||||
|
||||
/**
|
||||
* Create individual period entry
|
||||
* @param {{ id: string, key: AllPeriodKey }} period
|
||||
*/
|
||||
const createPeriodEntry = ({ id, key }) => {
|
||||
const name = periodIdToName(id, true);
|
||||
/** @param {ShortPeriodKey} key */
|
||||
const createShortPeriodEntry = (key) => {
|
||||
const name = periodName(key);
|
||||
return {
|
||||
name,
|
||||
tree: [
|
||||
costBasisChart(name, key),
|
||||
hasCagr(key)
|
||||
? returnsFolderWithCagr(name, key)
|
||||
: returnsFolder(name, key),
|
||||
profitabilityFolder(name, key),
|
||||
stackChart(name, key),
|
||||
],
|
||||
tree: [costBasisChart(name, key), shortReturnsFolder(name, key), profitabilityFolder(name, key), stackChart(name, key)],
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Create term group
|
||||
* @param {string} name
|
||||
* @param {string} title
|
||||
* @param {{ id: string, key: AllPeriodKey }[]} periods
|
||||
*/
|
||||
const createTermGroup = (name, title, periods) => ({
|
||||
name,
|
||||
title,
|
||||
tree: periods.map(createPeriodEntry),
|
||||
});
|
||||
/** @param {LongPeriodKey} key */
|
||||
const createLongPeriodEntry = (key) => {
|
||||
const name = periodName(key);
|
||||
return {
|
||||
name,
|
||||
tree: [costBasisChart(name, key), longReturnsFolder(name, key), profitabilityFolder(name, key), stackChart(name, key)],
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
name: "DCA vs Lump Sum",
|
||||
title: "Compare Investment Strategies",
|
||||
tree: [
|
||||
createTermGroup("Short Term", "Under 1 Year", PERIODS.short),
|
||||
createTermGroup("Medium Term", "1-3 Years", PERIODS.medium),
|
||||
createTermGroup("Long Term", "4+ Years", PERIODS.long),
|
||||
{ name: "Short Term", title: "Up to 1 Year", tree: SHORT_PERIODS.map(createShortPeriodEntry) },
|
||||
{ name: "Long Term", title: "2+ Years", tree: LONG_PERIODS.map(createLongPeriodEntry) },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create DCA by Period section (DCA only, no Lump Sum comparison)
|
||||
* 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
|
||||
*/
|
||||
export function createDcaByPeriodSection(ctx, { dca }) {
|
||||
function createPeriodSection(ctx, { dca, lookback, returns }) {
|
||||
const { colors } = ctx;
|
||||
const isLumpSum = !!lookback;
|
||||
const suffix = isLumpSum ? "Lump Sum" : "DCA";
|
||||
|
||||
/**
|
||||
* Create compare charts for a set of periods
|
||||
* @param {string} context
|
||||
* @param {{ id: string, key: AllPeriodKey }[]} periods
|
||||
*/
|
||||
const createCompare = (context, periods) =>
|
||||
createCompareFolder(
|
||||
context,
|
||||
periods.map(({ id, key }) => ({
|
||||
name: id,
|
||||
color: colors.dcaPeriods[key],
|
||||
costBasis: dca.periodAveragePrice[key],
|
||||
returns: dca.periodReturns[key],
|
||||
daysInProfit: dca.periodDaysInProfit[key],
|
||||
daysInLoss: dca.periodDaysInLoss[key],
|
||||
stack: dca.periodStack[key],
|
||||
})),
|
||||
);
|
||||
|
||||
/**
|
||||
* Create individual period entry (DCA only)
|
||||
* @param {{ id: string, key: AllPeriodKey }} period
|
||||
*/
|
||||
const createPeriodEntry = ({ id, key }) => {
|
||||
const name = periodIdToName(id, true);
|
||||
return createSingleEntry(colors, {
|
||||
name,
|
||||
titlePrefix: `${name} DCA`,
|
||||
color: colors.dcaPeriods[key],
|
||||
costBasis: dca.periodAveragePrice[key],
|
||||
returns: dca.periodReturns[key],
|
||||
maxReturn: dca.periodMaxReturn[key],
|
||||
minReturn: dca.periodMinReturn[key],
|
||||
daysInProfit: dca.periodDaysInProfit[key],
|
||||
daysInLoss: dca.periodDaysInLoss[key],
|
||||
stack: dca.periodStack[key],
|
||||
});
|
||||
};
|
||||
|
||||
/** @param {string} name @param {string} title @param {{ id: string, key: AllPeriodKey }[]} periods */
|
||||
const createTermGroup = (name, title, periods) => ({
|
||||
name,
|
||||
title,
|
||||
tree: [
|
||||
createCompare(`${name} DCA`, periods),
|
||||
...periods.map(createPeriodEntry),
|
||||
],
|
||||
/** @param {AllPeriodKey} key @returns {BaseEntryItem} */
|
||||
const buildBaseEntry = (key) => ({
|
||||
name: periodName(key),
|
||||
color: colors.dcaPeriods[key],
|
||||
costBasis: isLumpSum ? lookback[key] : dca.periodAveragePrice[key],
|
||||
returns: isLumpSum ? dca.periodLumpSumReturns[key] : dca.periodReturns[key],
|
||||
minReturn: isLumpSum ? dca.periodLumpSumMinReturn[key] : dca.periodMinReturn[key],
|
||||
maxReturn: isLumpSum ? dca.periodLumpSumMaxReturn[key] : dca.periodMaxReturn[key],
|
||||
daysInProfit: isLumpSum ? dca.periodLumpSumDaysInProfit[key] : dca.periodDaysInProfit[key],
|
||||
daysInLoss: isLumpSum ? dca.periodLumpSumDaysInLoss[key] : dca.periodDaysInLoss[key],
|
||||
stack: isLumpSum ? dca.periodLumpSumStack[key] : dca.periodStack[key],
|
||||
});
|
||||
|
||||
/** @param {LongPeriodKey} key @returns {LongEntryItem} */
|
||||
const buildLongEntry = (key) => withCagr(
|
||||
buildBaseEntry(key),
|
||||
isLumpSum ? returns.cagr[key] : dca.periodCagr[key],
|
||||
);
|
||||
|
||||
/** @param {BaseEntryItem} entry */
|
||||
const createShortEntry = (entry) =>
|
||||
createShortSingleEntry(colors, { ...entry, titlePrefix: `${entry.name} ${suffix}` });
|
||||
|
||||
/** @param {LongEntryItem} entry */
|
||||
const createLongEntry = (entry) =>
|
||||
createLongSingleEntry(colors, { ...entry, titlePrefix: `${entry.name} ${suffix}` });
|
||||
|
||||
const shortEntries = SHORT_PERIODS.map(buildBaseEntry);
|
||||
const longEntries = LONG_PERIODS.map(buildLongEntry);
|
||||
|
||||
return {
|
||||
name: "DCA by Period",
|
||||
title: "DCA Performance by Investment Period",
|
||||
name: `${suffix} by Period`,
|
||||
title: `${suffix} Performance by Investment Period`,
|
||||
tree: [
|
||||
createCompare("All Periods DCA", ALL_PERIODS),
|
||||
createTermGroup("Short Term", "Under 1 Year", PERIODS.short),
|
||||
createTermGroup("Medium Term", "1-3 Years", PERIODS.medium),
|
||||
createTermGroup("Long Term", "4+ Years", PERIODS.long),
|
||||
createCompareFolder(`All Periods ${suffix}`, [...shortEntries, ...longEntries]),
|
||||
{
|
||||
name: "Short Term",
|
||||
title: "Up to 1 Year",
|
||||
tree: [createCompareFolder(`Short Term ${suffix}`, shortEntries), ...shortEntries.map(createShortEntry)],
|
||||
},
|
||||
{
|
||||
name: "Long Term",
|
||||
title: "2+ Years",
|
||||
tree: [createCompareFolder(`Long Term ${suffix}`, longEntries), ...longEntries.map(createLongEntry)],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 });
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 }) {
|
||||
const { colors } = ctx;
|
||||
|
||||
/**
|
||||
* Create compare charts for a set of periods
|
||||
* @param {string} context
|
||||
* @param {{ id: string, key: AllPeriodKey }[]} periods
|
||||
*/
|
||||
const createCompare = (context, periods) =>
|
||||
createCompareFolder(
|
||||
context,
|
||||
periods.map(({ id, key }) => ({
|
||||
name: id,
|
||||
color: colors.dcaPeriods[key],
|
||||
costBasis: lookback[key],
|
||||
returns: dca.periodLumpSumReturns[key],
|
||||
daysInProfit: dca.periodLumpSumDaysInProfit[key],
|
||||
daysInLoss: dca.periodLumpSumDaysInLoss[key],
|
||||
stack: dca.periodLumpSumStack[key],
|
||||
})),
|
||||
);
|
||||
|
||||
/**
|
||||
* Create individual period entry (Lump Sum only)
|
||||
* @param {{ id: string, key: AllPeriodKey }} period
|
||||
*/
|
||||
const createPeriodEntry = ({ id, key }) => {
|
||||
const name = periodIdToName(id, true);
|
||||
return createSingleEntry(colors, {
|
||||
name,
|
||||
titlePrefix: `${name} Lump Sum`,
|
||||
color: colors.dcaPeriods[key],
|
||||
costBasis: lookback[key],
|
||||
returns: dca.periodLumpSumReturns[key],
|
||||
maxReturn: dca.periodLumpSumMaxReturn[key],
|
||||
minReturn: dca.periodLumpSumMinReturn[key],
|
||||
daysInProfit: dca.periodLumpSumDaysInProfit[key],
|
||||
daysInLoss: dca.periodLumpSumDaysInLoss[key],
|
||||
stack: dca.periodLumpSumStack[key],
|
||||
});
|
||||
};
|
||||
|
||||
/** @param {string} name @param {string} title @param {{ id: string, key: AllPeriodKey }[]} periods */
|
||||
const createTermGroup = (name, title, periods) => ({
|
||||
name,
|
||||
title,
|
||||
tree: [
|
||||
createCompare(`${name} Lump Sum`, periods),
|
||||
...periods.map(createPeriodEntry),
|
||||
],
|
||||
});
|
||||
|
||||
return {
|
||||
name: "Lump Sum by Period",
|
||||
title: "Lump Sum Performance by Investment Period",
|
||||
tree: [
|
||||
createCompare("All Periods Lump Sum", ALL_PERIODS),
|
||||
createTermGroup("Short Term", "Under 1 Year", PERIODS.short),
|
||||
createTermGroup("Medium Term", "1-3 Years", PERIODS.medium),
|
||||
createTermGroup("Long Term", "4+ Years", PERIODS.long),
|
||||
],
|
||||
};
|
||||
export function createLumpSumByPeriodSection(ctx, { dca, lookback, returns }) {
|
||||
return createPeriodSection(ctx, { dca, lookback, returns });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -689,61 +455,26 @@ export function createLumpSumByPeriodSection(ctx, { dca, lookback }) {
|
||||
export function createDcaByStartYearSection(ctx, { dca }) {
|
||||
const { colors } = ctx;
|
||||
|
||||
/**
|
||||
* Convert YearClass to CompareItem
|
||||
* @param {YearClass} c
|
||||
* @returns {CompareItem}
|
||||
*/
|
||||
const toCompareItem = (c) => ({
|
||||
name: `${c.year}`,
|
||||
color: c.color,
|
||||
costBasis: c.costBasis,
|
||||
returns: c.returns,
|
||||
daysInProfit: c.daysInProfit,
|
||||
daysInLoss: c.daysInLoss,
|
||||
stack: c.stack,
|
||||
});
|
||||
|
||||
/**
|
||||
* Create individual year entry
|
||||
* @param {YearClass} yearClass
|
||||
*/
|
||||
const createYearEntry = (yearClass) =>
|
||||
createSingleEntry(colors, {
|
||||
name: `${yearClass.year}`,
|
||||
titlePrefix: `${yearClass.year} DCA`,
|
||||
color: yearClass.color,
|
||||
costBasis: yearClass.costBasis,
|
||||
returns: yearClass.returns,
|
||||
maxReturn: yearClass.maxReturn,
|
||||
minReturn: yearClass.minReturn,
|
||||
daysInProfit: yearClass.daysInProfit,
|
||||
daysInLoss: yearClass.daysInLoss,
|
||||
stack: yearClass.stack,
|
||||
});
|
||||
|
||||
/** @param {string} name @param {string} title @param {YearClass[]} classes */
|
||||
const createDecadeGroup = (name, title, classes) => ({
|
||||
/** @param {string} name @param {string} title @param {BaseEntryItem[]} entries */
|
||||
const createDecadeGroup = (name, title, entries) => ({
|
||||
name,
|
||||
title,
|
||||
tree: [
|
||||
createCompareFolder(`${name} DCA`, classes.map(toCompareItem)),
|
||||
...classes.map(createYearEntry),
|
||||
createCompareFolder(`${name} DCA`, entries),
|
||||
...entries.map((entry) => createShortSingleEntry(colors, { ...entry, titlePrefix: `${entry.name} DCA` })),
|
||||
],
|
||||
});
|
||||
|
||||
// Build all classes once, then filter by decade
|
||||
const allClasses = ALL_YEARS.map((year) => buildYearClass(colors, dca, year));
|
||||
const classes2020s = allClasses.filter((c) => c.year >= 2020);
|
||||
const classes2010s = allClasses.filter((c) => c.year < 2020);
|
||||
const entries2020s = YEARS_2020S.map((year) => buildYearEntry(colors, dca, year));
|
||||
const entries2010s = YEARS_2010S.map((year) => buildYearEntry(colors, dca, year));
|
||||
|
||||
return {
|
||||
name: "DCA by Start Year",
|
||||
title: "DCA Performance by When You Started",
|
||||
tree: [
|
||||
createCompareFolder("All Years DCA", allClasses.map(toCompareItem)),
|
||||
createDecadeGroup("2020s", "2020-2026", classes2020s),
|
||||
createDecadeGroup("2010s", "2015-2019", classes2010s),
|
||||
createCompareFolder("All Years DCA", [...entries2020s, ...entries2010s]),
|
||||
createDecadeGroup("2020s", "2020-2026", entries2020s),
|
||||
createDecadeGroup("2010s", "2015-2019", entries2010s),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Unit } from "../utils/units.js";
|
||||
import { priceLine } from "./constants.js";
|
||||
import { line, baseline, dots, dotted } from "./series.js";
|
||||
import { satsBtcUsd } from "./shared.js";
|
||||
import { fromCountPattern } from "./series.js";
|
||||
|
||||
/** Major pools to show in Compare section (by current hashrate dominance) */
|
||||
const MAJOR_POOL_IDS = [
|
||||
@@ -103,42 +104,35 @@ export function createMiningSection(ctx) {
|
||||
name: "Blocks Mined",
|
||||
title: `Blocks Mined: ${poolName}`,
|
||||
bottom: [
|
||||
dots({
|
||||
metric: pool.blocksMined.sum,
|
||||
name: "Sum",
|
||||
...fromCountPattern({
|
||||
pattern: pool.blocksMined,
|
||||
unit: Unit.count,
|
||||
}),
|
||||
line({
|
||||
metric: pool.blocksMined.cumulative,
|
||||
name: "Cumulative",
|
||||
color: colors.blue,
|
||||
unit: Unit.count,
|
||||
defaultActive: false,
|
||||
cumulativeUnit: Unit.countCumulative,
|
||||
}),
|
||||
line({
|
||||
metric: pool._24hBlocksMined,
|
||||
name: "24h sum",
|
||||
name: "24h",
|
||||
color: colors.pink,
|
||||
unit: Unit.count,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: pool._1wBlocksMined,
|
||||
name: "1w sum",
|
||||
name: "1w",
|
||||
color: colors.red,
|
||||
unit: Unit.count,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: pool._1mBlocksMined,
|
||||
name: "1m sum",
|
||||
color: colors.pink,
|
||||
name: "1m",
|
||||
color: colors.orange,
|
||||
unit: Unit.count,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: pool._1yBlocksMined,
|
||||
name: "1y sum",
|
||||
name: "1y",
|
||||
color: colors.purple,
|
||||
unit: Unit.count,
|
||||
defaultActive: false,
|
||||
@@ -152,20 +146,17 @@ export function createMiningSection(ctx) {
|
||||
...fromValuePattern({
|
||||
pattern: pool.coinbase,
|
||||
title: "coinbase",
|
||||
sumColor: colors.orange,
|
||||
cumulativeColor: colors.red,
|
||||
color: colors.orange,
|
||||
}),
|
||||
...fromValuePattern({
|
||||
pattern: pool.subsidy,
|
||||
title: "subsidy",
|
||||
sumColor: colors.lime,
|
||||
cumulativeColor: colors.emerald,
|
||||
color: colors.lime,
|
||||
}),
|
||||
...fromValuePattern({
|
||||
pattern: pool.fee,
|
||||
title: "fee",
|
||||
sumColor: colors.cyan,
|
||||
cumulativeColor: colors.indigo,
|
||||
color: colors.cyan,
|
||||
}),
|
||||
],
|
||||
},
|
||||
@@ -342,14 +333,17 @@ export function createMiningSection(ctx) {
|
||||
...fromSumStatsPattern({
|
||||
pattern: transactions.fees.fee.bitcoin,
|
||||
unit: Unit.btc,
|
||||
cumulativeUnit: Unit.btcCumulative,
|
||||
}),
|
||||
...fromSumStatsPattern({
|
||||
pattern: transactions.fees.fee.sats,
|
||||
unit: Unit.sats,
|
||||
cumulativeUnit: Unit.satsCumulative,
|
||||
}),
|
||||
...fromSumStatsPattern({
|
||||
pattern: transactions.fees.fee.dollars,
|
||||
unit: Unit.usd,
|
||||
cumulativeUnit: Unit.usdCumulative,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.rewards.feeDominance,
|
||||
@@ -364,7 +358,6 @@ export function createMiningSection(ctx) {
|
||||
title: "Unclaimed Rewards",
|
||||
bottom: fromValuePattern({
|
||||
pattern: blocks.rewards.unclaimedRewards,
|
||||
title: "Unclaimed",
|
||||
}),
|
||||
},
|
||||
],
|
||||
|
||||
@@ -47,6 +47,23 @@ export function createNetworkSection(ctx) {
|
||||
{ key: "p2a", name: "P2A", color: colors[spendableTypeColors.p2a], defaultActive: false },
|
||||
];
|
||||
|
||||
// Script types for output count comparisons (address types + non-addressable scripts)
|
||||
/** @type {ReadonlyArray<{key: AddressableType | "p2ms" | "opreturn" | "emptyoutput" | "unknownoutput", name: string, color: Color, defaultActive?: boolean}>} */
|
||||
const scriptTypes = [
|
||||
{ key: "p2pkh", name: "P2PKH", color: colors[spendableTypeColors.p2pkh] },
|
||||
{ key: "p2sh", name: "P2SH", color: colors[spendableTypeColors.p2sh] },
|
||||
{ key: "p2wpkh", name: "P2WPKH", color: colors[spendableTypeColors.p2wpkh] },
|
||||
{ key: "p2wsh", name: "P2WSH", color: colors[spendableTypeColors.p2wsh] },
|
||||
{ key: "p2tr", name: "P2TR", color: colors[spendableTypeColors.p2tr] },
|
||||
{ key: "p2pk65", name: "P2PK65", color: colors[spendableTypeColors.p2pk65], defaultActive: false },
|
||||
{ key: "p2pk33", name: "P2PK33", color: colors[spendableTypeColors.p2pk33], defaultActive: false },
|
||||
{ key: "p2a", name: "P2A", color: colors[spendableTypeColors.p2a], defaultActive: false },
|
||||
{ key: "p2ms", name: "P2MS", color: colors[spendableTypeColors.p2ms], defaultActive: false },
|
||||
{ key: "opreturn", name: "OP_RETURN", color: colors[spendableTypeColors.opreturn], defaultActive: false },
|
||||
{ key: "emptyoutput", name: "Empty", color: colors[spendableTypeColors.empty], defaultActive: false },
|
||||
{ key: "unknownoutput", name: "Unknown", color: colors[spendableTypeColors.unknown], defaultActive: false },
|
||||
];
|
||||
|
||||
// Activity types for mapping
|
||||
/** @type {ReadonlyArray<{key: "sending" | "receiving" | "both" | "reactivated" | "balanceIncreased" | "balanceDecreased", name: string, title: string, compareTitle: string}>} */
|
||||
const activityTypes = [
|
||||
@@ -100,7 +117,7 @@ export function createNetworkSection(ctx) {
|
||||
{
|
||||
name: "New",
|
||||
title: `${titlePrefix}New Address Count`,
|
||||
bottom: fromFullStatsPattern({ pattern: distribution.newAddrCount[key], unit: Unit.count }),
|
||||
bottom: fromFullStatsPattern({ pattern: distribution.newAddrCount[key], unit: Unit.count, cumulativeUnit: Unit.countCumulative }),
|
||||
},
|
||||
{
|
||||
name: "Growth Rate",
|
||||
@@ -123,6 +140,39 @@ export function createNetworkSection(ctx) {
|
||||
return {
|
||||
name: "Network",
|
||||
tree: [
|
||||
// Supply
|
||||
{
|
||||
name: "Supply",
|
||||
tree: [
|
||||
{
|
||||
name: "Circulating",
|
||||
title: "Circulating Supply",
|
||||
bottom: fromSupplyPattern({ pattern: supply.circulating, title: "Supply" }),
|
||||
},
|
||||
{
|
||||
name: "Inflation",
|
||||
title: "Inflation Rate",
|
||||
bottom: [
|
||||
dots({
|
||||
metric: supply.inflation,
|
||||
name: "Rate",
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Unspendable",
|
||||
title: "Unspendable Supply",
|
||||
bottom: fromValuePattern({ pattern: supply.burned.unspendable }),
|
||||
},
|
||||
{
|
||||
name: "OP_RETURN",
|
||||
title: "OP_RETURN Burned",
|
||||
bottom: fromCoinbasePattern({ pattern: scripts.value.opreturn }),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// Transactions
|
||||
{
|
||||
name: "Transactions",
|
||||
@@ -130,7 +180,7 @@ export function createNetworkSection(ctx) {
|
||||
{
|
||||
name: "Count",
|
||||
title: "Transaction Count",
|
||||
bottom: fromFullStatsPattern({ pattern: transactions.count.txCount, unit: Unit.count }),
|
||||
bottom: fromFullStatsPattern({ pattern: transactions.count.txCount, unit: Unit.count, cumulativeUnit: Unit.countCumulative }),
|
||||
},
|
||||
{
|
||||
name: "Per Second",
|
||||
@@ -143,6 +193,11 @@ export function createNetworkSection(ctx) {
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Fee Rate",
|
||||
title: "Fee Rate",
|
||||
bottom: fromStatsPattern({ pattern: transactions.fees.feeRate, unit: Unit.feeRate }),
|
||||
},
|
||||
{
|
||||
name: "Volume",
|
||||
title: "Transaction Volume",
|
||||
@@ -177,23 +232,23 @@ export function createNetworkSection(ctx) {
|
||||
...fromCountPattern({
|
||||
pattern: transactions.versions.v1,
|
||||
unit: Unit.count,
|
||||
cumulativeUnit: Unit.countCumulative,
|
||||
title: "v1",
|
||||
sumColor: colors.orange,
|
||||
cumulativeColor: colors.red,
|
||||
color: colors.orange,
|
||||
}),
|
||||
...fromCountPattern({
|
||||
pattern: transactions.versions.v2,
|
||||
unit: Unit.count,
|
||||
cumulativeUnit: Unit.countCumulative,
|
||||
title: "v2",
|
||||
sumColor: colors.cyan,
|
||||
cumulativeColor: colors.blue,
|
||||
color: colors.cyan,
|
||||
}),
|
||||
...fromCountPattern({
|
||||
pattern: transactions.versions.v3,
|
||||
unit: Unit.count,
|
||||
cumulativeUnit: Unit.countCumulative,
|
||||
title: "v3",
|
||||
sumColor: colors.lime,
|
||||
cumulativeColor: colors.green,
|
||||
color: colors.lime,
|
||||
}),
|
||||
],
|
||||
},
|
||||
@@ -217,27 +272,6 @@ export function createNetworkSection(ctx) {
|
||||
],
|
||||
},
|
||||
|
||||
// Fees
|
||||
{
|
||||
name: "Fees",
|
||||
tree: [
|
||||
{
|
||||
name: "Fee Rate",
|
||||
title: "Fee Rate",
|
||||
bottom: fromStatsPattern({ pattern: transactions.fees.feeRate, unit: Unit.feeRate }),
|
||||
},
|
||||
{
|
||||
name: "Total",
|
||||
title: "Total Fees",
|
||||
bottom: [
|
||||
...fromSumStatsPattern({ pattern: transactions.fees.fee.bitcoin, unit: Unit.btc }),
|
||||
...fromSumStatsPattern({ pattern: transactions.fees.fee.sats, unit: Unit.sats }),
|
||||
...fromSumStatsPattern({ pattern: transactions.fees.fee.dollars, unit: Unit.usd }),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// Blocks
|
||||
{
|
||||
name: "Blocks",
|
||||
@@ -246,7 +280,7 @@ export function createNetworkSection(ctx) {
|
||||
name: "Count",
|
||||
title: "Block Count",
|
||||
bottom: [
|
||||
...fromCountPattern({ pattern: blocks.count.blockCount, unit: Unit.count }),
|
||||
...fromCountPattern({ pattern: blocks.count.blockCount, unit: Unit.count, cumulativeUnit: Unit.countCumulative }),
|
||||
line({
|
||||
metric: blocks.count.blockCountTarget,
|
||||
name: "Target",
|
||||
@@ -296,7 +330,7 @@ export function createNetworkSection(ctx) {
|
||||
name: "Size",
|
||||
title: "Block Size",
|
||||
bottom: [
|
||||
...fromSumStatsPattern({ pattern: blocks.size, unit: Unit.bytes }),
|
||||
...fromSumStatsPattern({ pattern: blocks.size, unit: Unit.bytes, cumulativeUnit: Unit.bytesCumulative }),
|
||||
line({
|
||||
metric: blocks.totalSize,
|
||||
name: "Total",
|
||||
@@ -306,20 +340,6 @@ export function createNetworkSection(ctx) {
|
||||
}),
|
||||
...fromBaseStatsPattern({ pattern: blocks.vbytes, unit: Unit.vb }),
|
||||
...fromBaseStatsPattern({ pattern: blocks.weight, unit: Unit.wu }),
|
||||
line({
|
||||
metric: blocks.weight.sum,
|
||||
name: "Sum",
|
||||
color: colors.stat.sum,
|
||||
unit: Unit.wu,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.weight.cumulative,
|
||||
name: "Cumulative",
|
||||
color: colors.stat.cumulative,
|
||||
unit: Unit.wu,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -330,9 +350,9 @@ export function createNetworkSection(ctx) {
|
||||
],
|
||||
},
|
||||
|
||||
// UTXO Set
|
||||
// UTXOs
|
||||
{
|
||||
name: "UTXO Set",
|
||||
name: "UTXOs",
|
||||
tree: [
|
||||
{
|
||||
name: "UTXO Count",
|
||||
@@ -351,7 +371,7 @@ export function createNetworkSection(ctx) {
|
||||
{
|
||||
name: "Count",
|
||||
title: "Input Count",
|
||||
bottom: [...fromSumStatsPattern({ pattern: inputs.count, unit: Unit.count })],
|
||||
bottom: [...fromSumStatsPattern({ pattern: inputs.count, unit: Unit.count, cumulativeUnit: Unit.countCumulative })],
|
||||
},
|
||||
{
|
||||
name: "Rate",
|
||||
@@ -372,7 +392,7 @@ export function createNetworkSection(ctx) {
|
||||
{
|
||||
name: "Count",
|
||||
title: "Output Count",
|
||||
bottom: [...fromSumStatsPattern({ pattern: outputs.count.totalCount, unit: Unit.count })],
|
||||
bottom: [...fromSumStatsPattern({ pattern: outputs.count.totalCount, unit: Unit.count, cumulativeUnit: Unit.countCumulative })],
|
||||
},
|
||||
{
|
||||
name: "Rate",
|
||||
@@ -498,6 +518,20 @@ export function createNetworkSection(ctx) {
|
||||
{
|
||||
name: "Output Counts",
|
||||
tree: [
|
||||
// Compare section
|
||||
{
|
||||
name: "Compare",
|
||||
title: "Output Count by Script Type",
|
||||
bottom: scriptTypes.map((t) =>
|
||||
line({
|
||||
metric: scripts.count[t.key].cumulative,
|
||||
name: t.name,
|
||||
color: t.color,
|
||||
unit: Unit.countCumulative,
|
||||
defaultActive: t.defaultActive,
|
||||
}),
|
||||
),
|
||||
},
|
||||
// Legacy scripts
|
||||
{
|
||||
name: "Legacy",
|
||||
@@ -505,17 +539,17 @@ export function createNetworkSection(ctx) {
|
||||
{
|
||||
name: "P2PKH",
|
||||
title: "P2PKH Output Count",
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.p2pkh, unit: Unit.count }),
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.p2pkh, unit: Unit.count, cumulativeUnit: Unit.countCumulative }),
|
||||
},
|
||||
{
|
||||
name: "P2PK33",
|
||||
title: "P2PK33 Output Count",
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.p2pk33, unit: Unit.count }),
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.p2pk33, unit: Unit.count, cumulativeUnit: Unit.countCumulative }),
|
||||
},
|
||||
{
|
||||
name: "P2PK65",
|
||||
title: "P2PK65 Output Count",
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.p2pk65, unit: Unit.count }),
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.p2pk65, unit: Unit.count, cumulativeUnit: Unit.countCumulative }),
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -526,12 +560,12 @@ export function createNetworkSection(ctx) {
|
||||
{
|
||||
name: "P2SH",
|
||||
title: "P2SH Output Count",
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.p2sh, unit: Unit.count }),
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.p2sh, unit: Unit.count, cumulativeUnit: Unit.countCumulative }),
|
||||
},
|
||||
{
|
||||
name: "P2MS",
|
||||
title: "P2MS Output Count",
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.p2ms, unit: Unit.count }),
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.p2ms, unit: Unit.count, cumulativeUnit: Unit.countCumulative }),
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -542,17 +576,17 @@ export function createNetworkSection(ctx) {
|
||||
{
|
||||
name: "All SegWit",
|
||||
title: "SegWit Output Count",
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.segwit, unit: Unit.count }),
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.segwit, unit: Unit.count, cumulativeUnit: Unit.countCumulative }),
|
||||
},
|
||||
{
|
||||
name: "P2WPKH",
|
||||
title: "P2WPKH Output Count",
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.p2wpkh, unit: Unit.count }),
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.p2wpkh, unit: Unit.count, cumulativeUnit: Unit.countCumulative }),
|
||||
},
|
||||
{
|
||||
name: "P2WSH",
|
||||
title: "P2WSH Output Count",
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.p2wsh, unit: Unit.count }),
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.p2wsh, unit: Unit.count, cumulativeUnit: Unit.countCumulative }),
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -563,12 +597,12 @@ export function createNetworkSection(ctx) {
|
||||
{
|
||||
name: "P2TR",
|
||||
title: "P2TR Output Count",
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.p2tr, unit: Unit.count }),
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.p2tr, unit: Unit.count, cumulativeUnit: Unit.countCumulative }),
|
||||
},
|
||||
{
|
||||
name: "P2A",
|
||||
title: "P2A Output Count",
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.p2a, unit: Unit.count }),
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.p2a, unit: Unit.count, cumulativeUnit: Unit.countCumulative }),
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -579,17 +613,17 @@ export function createNetworkSection(ctx) {
|
||||
{
|
||||
name: "OP_RETURN",
|
||||
title: "OP_RETURN Output Count",
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.opreturn, unit: Unit.count }),
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.opreturn, unit: Unit.count, cumulativeUnit: Unit.countCumulative }),
|
||||
},
|
||||
{
|
||||
name: "Empty",
|
||||
title: "Empty Output Count",
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.emptyoutput, unit: Unit.count }),
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.emptyoutput, unit: Unit.count, cumulativeUnit: Unit.countCumulative }),
|
||||
},
|
||||
{
|
||||
name: "Unknown",
|
||||
title: "Unknown Output Count",
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.unknownoutput, unit: Unit.count }),
|
||||
bottom: fromFullStatsPattern({ pattern: scripts.count.unknownoutput, unit: Unit.count, cumulativeUnit: Unit.countCumulative }),
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -610,15 +644,13 @@ export function createNetworkSection(ctx) {
|
||||
line({
|
||||
metric: scripts.count.segwitAdoption.sum,
|
||||
name: "Sum",
|
||||
color: colors.stat.sum,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
line({
|
||||
metric: scripts.count.segwitAdoption.cumulative,
|
||||
name: "Cumulative",
|
||||
color: colors.stat.cumulative,
|
||||
color: colors.red,
|
||||
unit: Unit.percentage,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
@@ -634,15 +666,13 @@ export function createNetworkSection(ctx) {
|
||||
line({
|
||||
metric: scripts.count.taprootAdoption.sum,
|
||||
name: "Sum",
|
||||
color: colors.stat.sum,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
line({
|
||||
metric: scripts.count.taprootAdoption.cumulative,
|
||||
name: "Cumulative",
|
||||
color: colors.stat.cumulative,
|
||||
color: colors.red,
|
||||
unit: Unit.percentage,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
@@ -651,46 +681,6 @@ export function createNetworkSection(ctx) {
|
||||
],
|
||||
},
|
||||
|
||||
// Supply
|
||||
{
|
||||
name: "Supply",
|
||||
tree: [
|
||||
{
|
||||
name: "Circulating",
|
||||
title: "Circulating Supply",
|
||||
bottom: fromSupplyPattern({ pattern: supply.circulating, title: "Supply" }),
|
||||
},
|
||||
{
|
||||
name: "Inflation",
|
||||
title: "Inflation Rate",
|
||||
bottom: [
|
||||
dots({
|
||||
metric: supply.inflation,
|
||||
name: "Rate",
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Burned",
|
||||
tree: [
|
||||
{
|
||||
name: "Unspendable",
|
||||
title: "Unspendable Supply",
|
||||
bottom: fromValuePattern({ pattern: supply.burned.unspendable }),
|
||||
},
|
||||
{
|
||||
name: "OP_RETURN",
|
||||
title: "OP_RETURN Burned",
|
||||
bottom: [
|
||||
...fromValuePattern({ pattern: supply.burned.opreturn }),
|
||||
...fromCoinbasePattern({ pattern: scripts.value.opreturn }),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -311,26 +311,25 @@ export function histogram({
|
||||
* @param {Object} args
|
||||
* @param {AnyStatsPattern} args.pattern
|
||||
* @param {Unit} args.unit
|
||||
* @param {Unit} args.cumulativeUnit
|
||||
* @param {string} [args.title]
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function fromSumStatsPattern(colors, { pattern, unit, title = "" }) {
|
||||
export function fromSumStatsPattern(colors, { pattern, unit, cumulativeUnit, title = "" }) {
|
||||
const { stat } = colors;
|
||||
return [
|
||||
{ metric: pattern.average, title: `${title} avg`.trim(), unit },
|
||||
{
|
||||
metric: pattern.sum,
|
||||
title: `${title} sum`.trim(),
|
||||
title: title || "sum",
|
||||
color: stat.sum,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.cumulative,
|
||||
title: `${title} cumulative`.trim(),
|
||||
color: stat.cumulative,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
title: title || "cumulative",
|
||||
unit: cumulativeUnit,
|
||||
},
|
||||
...percentileSeries(colors, pattern, unit, title),
|
||||
];
|
||||
@@ -371,25 +370,24 @@ export function fromBaseStatsPattern(
|
||||
* @param {Object} args
|
||||
* @param {FullStatsPattern<any>} args.pattern
|
||||
* @param {Unit} args.unit
|
||||
* @param {Unit} args.cumulativeUnit
|
||||
* @param {string} [args.title]
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function fromFullStatsPattern(colors, { pattern, unit, title = "" }) {
|
||||
export function fromFullStatsPattern(colors, { pattern, unit, cumulativeUnit, title = "" }) {
|
||||
const { stat } = colors;
|
||||
return [
|
||||
{ metric: pattern.base, title: title || "base", unit },
|
||||
{
|
||||
metric: pattern.sum,
|
||||
title: `${title} sum`.trim(),
|
||||
title: title || "sum",
|
||||
color: stat.sum,
|
||||
unit,
|
||||
},
|
||||
{
|
||||
metric: pattern.cumulative,
|
||||
title: `${title} cumulative`.trim(),
|
||||
color: stat.cumulative,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
title: title || "cumulative",
|
||||
unit: cumulativeUnit,
|
||||
},
|
||||
{
|
||||
metric: pattern.average,
|
||||
@@ -429,25 +427,24 @@ export function fromStatsPattern(colors, { pattern, unit, title = "" }) {
|
||||
* @param {Object} args
|
||||
* @param {AnyFullStatsPattern} args.pattern
|
||||
* @param {Unit} args.unit
|
||||
* @param {Unit} args.cumulativeUnit
|
||||
* @param {string} [args.title]
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function fromAnyFullStatsPattern(colors, { pattern, unit, title = "" }) {
|
||||
export function fromAnyFullStatsPattern(colors, { pattern, unit, cumulativeUnit, title = "" }) {
|
||||
const { stat } = colors;
|
||||
return [
|
||||
...fromBaseStatsPattern(colors, { pattern, unit, title }),
|
||||
{
|
||||
metric: pattern.sum,
|
||||
title: `${title} sum`.trim(),
|
||||
title: title || "sum",
|
||||
color: stat.sum,
|
||||
unit,
|
||||
},
|
||||
{
|
||||
metric: pattern.cumulative,
|
||||
title: `${title} cumulative`.trim(),
|
||||
color: stat.cumulative,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
title: title || "cumulative",
|
||||
unit: cumulativeUnit,
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -465,16 +462,19 @@ export function fromCoinbasePattern(colors, { pattern, title = "" }) {
|
||||
...fromAnyFullStatsPattern(colors, {
|
||||
pattern: pattern.bitcoin,
|
||||
unit: Unit.btc,
|
||||
cumulativeUnit: Unit.btcCumulative,
|
||||
title,
|
||||
}),
|
||||
...fromAnyFullStatsPattern(colors, {
|
||||
pattern: pattern.sats,
|
||||
unit: Unit.sats,
|
||||
cumulativeUnit: Unit.satsCumulative,
|
||||
title,
|
||||
}),
|
||||
...fromAnyFullStatsPattern(colors, {
|
||||
pattern: pattern.dollars,
|
||||
unit: Unit.usd,
|
||||
cumulativeUnit: Unit.usdCumulative,
|
||||
title,
|
||||
}),
|
||||
];
|
||||
@@ -482,123 +482,112 @@ export function fromCoinbasePattern(colors, { pattern, title = "" }) {
|
||||
|
||||
/**
|
||||
* Create series from a ValuePattern ({ sats, bitcoin, dollars } each as CountPattern with sum + cumulative)
|
||||
* @param {Colors} colors
|
||||
* @param {Object} args
|
||||
* @param {ValuePattern} args.pattern
|
||||
* @param {string} [args.title]
|
||||
* @param {Color} [args.sumColor]
|
||||
* @param {Color} [args.cumulativeColor]
|
||||
* @param {Color} [args.color]
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function fromValuePattern(
|
||||
colors,
|
||||
{ pattern, title = "", sumColor, cumulativeColor },
|
||||
) {
|
||||
export function fromValuePattern({ pattern, title = "", color }) {
|
||||
return [
|
||||
{
|
||||
metric: pattern.bitcoin.sum,
|
||||
title: title || "sum",
|
||||
color: sumColor,
|
||||
color,
|
||||
unit: Unit.btc,
|
||||
},
|
||||
{
|
||||
metric: pattern.bitcoin.cumulative,
|
||||
title: `${title} cumulative`.trim(),
|
||||
color: cumulativeColor ?? colors.stat.cumulative,
|
||||
title: title || "cumulative",
|
||||
color,
|
||||
unit: Unit.btcCumulative,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.sats.sum,
|
||||
title: title || "sum",
|
||||
color: sumColor,
|
||||
color,
|
||||
unit: Unit.sats,
|
||||
},
|
||||
{
|
||||
metric: pattern.sats.cumulative,
|
||||
title: `${title} cumulative`.trim(),
|
||||
color: cumulativeColor ?? colors.stat.cumulative,
|
||||
title: title || "cumulative",
|
||||
color,
|
||||
unit: Unit.satsCumulative,
|
||||
defaultActive: false,
|
||||
},
|
||||
{
|
||||
metric: pattern.dollars.sum,
|
||||
title: title || "sum",
|
||||
color: sumColor,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
},
|
||||
{
|
||||
metric: pattern.dollars.cumulative,
|
||||
title: `${title} cumulative`.trim(),
|
||||
color: cumulativeColor ?? colors.stat.cumulative,
|
||||
title: title || "cumulative",
|
||||
color,
|
||||
unit: Unit.usdCumulative,
|
||||
defaultActive: false,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create sum/cumulative series from a BitcoinPattern ({ sum, cumulative }) with explicit unit and colors
|
||||
* @param {Colors} colors
|
||||
* @param {Object} args
|
||||
* @param {{ sum: AnyMetricPattern, cumulative: AnyMetricPattern }} args.pattern
|
||||
* @param {Unit} args.unit
|
||||
* @param {Unit} args.cumulativeUnit
|
||||
* @param {string} [args.title]
|
||||
* @param {Color} [args.sumColor]
|
||||
* @param {Color} [args.cumulativeColor]
|
||||
* @param {Color} [args.color]
|
||||
* @param {boolean} [args.defaultActive]
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function fromBitcoinPatternWithUnit(
|
||||
colors,
|
||||
{ pattern, unit, title = "", sumColor, cumulativeColor, defaultActive },
|
||||
) {
|
||||
export function fromBitcoinPatternWithUnit({
|
||||
pattern,
|
||||
unit,
|
||||
cumulativeUnit,
|
||||
title = "",
|
||||
color,
|
||||
defaultActive,
|
||||
}) {
|
||||
return [
|
||||
{
|
||||
metric: pattern.sum,
|
||||
title: `${title} sum`.trim(),
|
||||
color: sumColor,
|
||||
title: title || "sum",
|
||||
color,
|
||||
unit,
|
||||
defaultActive,
|
||||
},
|
||||
{
|
||||
metric: pattern.cumulative,
|
||||
title: `${title} cumulative`.trim(),
|
||||
color: cumulativeColor ?? colors.stat.cumulative,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
title: title || "cumulative",
|
||||
color,
|
||||
unit: cumulativeUnit,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create sum/cumulative series from a CountPattern with explicit unit and colors
|
||||
* @param {Colors} colors
|
||||
* @param {Object} args
|
||||
* @param {CountPattern<any>} args.pattern
|
||||
* @param {Unit} args.unit
|
||||
* @param {Unit} args.cumulativeUnit
|
||||
* @param {string} [args.title]
|
||||
* @param {Color} [args.sumColor]
|
||||
* @param {Color} [args.cumulativeColor]
|
||||
* @param {Color} [args.color]
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function fromCountPattern(
|
||||
colors,
|
||||
{ pattern, unit, title = "", sumColor, cumulativeColor },
|
||||
) {
|
||||
export function fromCountPattern({ pattern, unit, cumulativeUnit, title = "", color }) {
|
||||
return [
|
||||
{
|
||||
metric: pattern.sum,
|
||||
title: `${title} sum`.trim(),
|
||||
color: sumColor,
|
||||
title: title || "sum",
|
||||
color,
|
||||
unit,
|
||||
},
|
||||
{
|
||||
metric: pattern.cumulative,
|
||||
title: `${title} cumulative`.trim(),
|
||||
color: cumulativeColor ?? colors.stat.cumulative,
|
||||
unit,
|
||||
defaultActive: false,
|
||||
title: title || "cumulative",
|
||||
color,
|
||||
unit: cumulativeUnit,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user