mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-24 14:49:58 -07:00
website: snapshot
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { Unit } from "../utils/units.js";
|
||||
import { line, price } from "./series.js";
|
||||
import { dots, line, price } from "./series.js";
|
||||
import { satsBtcUsd, createPriceRatioCharts } from "./shared.js";
|
||||
|
||||
/**
|
||||
@@ -21,160 +21,193 @@ export function createCointimeSection(ctx) {
|
||||
} = cointime;
|
||||
const { all } = distribution.utxoCohorts;
|
||||
|
||||
// Cointime prices data
|
||||
const cointimePrices = [
|
||||
// Reference lines for cap comparisons
|
||||
const capReferenceLines = /** @type {const} */ ([
|
||||
{ metric: supply.marketCap, name: "Market", color: colors.default },
|
||||
{
|
||||
metric: all.realized.realizedCap,
|
||||
name: "Realized",
|
||||
color: colors.orange,
|
||||
},
|
||||
]);
|
||||
|
||||
const prices = /** @type {const} */ ([
|
||||
{
|
||||
pricePattern: pricing.trueMarketMean,
|
||||
ratio: pricing.trueMarketMeanRatio,
|
||||
name: "True Market Mean",
|
||||
title: "True Market Mean",
|
||||
color: colors.blue,
|
||||
},
|
||||
{
|
||||
pricePattern: pricing.vaultedPrice,
|
||||
ratio: pricing.vaultedPriceRatio,
|
||||
name: "Vaulted",
|
||||
title: "Vaulted Price",
|
||||
color: colors.lime,
|
||||
},
|
||||
{
|
||||
pricePattern: pricing.activePrice,
|
||||
ratio: pricing.activePriceRatio,
|
||||
name: "Active",
|
||||
title: "Active Price",
|
||||
color: colors.rose,
|
||||
},
|
||||
{
|
||||
pricePattern: pricing.cointimePrice,
|
||||
ratio: pricing.cointimePriceRatio,
|
||||
name: "Cointime",
|
||||
title: "Cointime Price",
|
||||
color: colors.yellow,
|
||||
},
|
||||
];
|
||||
]);
|
||||
|
||||
// Cointime capitalizations data
|
||||
const cointimeCapitalizations = [
|
||||
const caps = /** @type {const} */ ([
|
||||
{ metric: cap.vaultedCap, name: "Vaulted", color: colors.lime },
|
||||
{ metric: cap.activeCap, name: "Active", color: colors.rose },
|
||||
{ metric: cap.cointimeCap, name: "Cointime", color: colors.yellow },
|
||||
{ metric: cap.investorCap, name: "Investor", color: colors.fuchsia },
|
||||
{ metric: cap.thermoCap, name: "Thermo", color: colors.emerald },
|
||||
]);
|
||||
|
||||
const supplyBreakdown = /** @type {const} */ ([
|
||||
{ pattern: all.supply.total, name: "Total", color: colors.orange },
|
||||
{
|
||||
metric: cap.vaultedCap,
|
||||
pattern: cointimeSupply.vaultedSupply,
|
||||
name: "Vaulted",
|
||||
title: "Vaulted Cap",
|
||||
color: colors.lime,
|
||||
},
|
||||
{
|
||||
metric: cap.activeCap,
|
||||
pattern: cointimeSupply.activeSupply,
|
||||
name: "Active",
|
||||
title: "Active Cap",
|
||||
color: colors.rose,
|
||||
},
|
||||
]);
|
||||
|
||||
const coinblocks = /** @type {const} */ ([
|
||||
{
|
||||
metric: cap.cointimeCap,
|
||||
name: "Cointime",
|
||||
title: "Cointime Cap",
|
||||
color: colors.yellow,
|
||||
pattern: all.activity.coinblocksDestroyed,
|
||||
name: "Destroyed",
|
||||
title: "Coinblocks Destroyed",
|
||||
color: colors.red,
|
||||
},
|
||||
{
|
||||
metric: cap.investorCap,
|
||||
name: "Investor",
|
||||
title: "Investor Cap",
|
||||
color: colors.fuchsia,
|
||||
pattern: activity.coinblocksCreated,
|
||||
name: "Created",
|
||||
title: "Coinblocks Created",
|
||||
color: colors.orange,
|
||||
},
|
||||
{
|
||||
metric: cap.thermoCap,
|
||||
name: "Thermo",
|
||||
title: "Thermo Cap",
|
||||
color: colors.emerald,
|
||||
pattern: activity.coinblocksStored,
|
||||
name: "Stored",
|
||||
title: "Coinblocks Stored",
|
||||
color: colors.green,
|
||||
},
|
||||
];
|
||||
]);
|
||||
|
||||
// Colors aligned with coinblocks: Destroyed=red, Created=orange, Stored=green
|
||||
const cointimeValues = /** @type {const} */ ([
|
||||
{
|
||||
pattern: value.cointimeValueCreated,
|
||||
name: "Created",
|
||||
title: "Cointime Value Created",
|
||||
color: colors.orange,
|
||||
},
|
||||
{
|
||||
pattern: value.cointimeValueDestroyed,
|
||||
name: "Destroyed",
|
||||
title: "Cointime Value Destroyed",
|
||||
color: colors.red,
|
||||
},
|
||||
{
|
||||
pattern: value.cointimeValueStored,
|
||||
name: "Stored",
|
||||
title: "Cointime Value Stored",
|
||||
color: colors.green,
|
||||
},
|
||||
]);
|
||||
|
||||
const vocdd = /** @type {const} */ ({
|
||||
pattern: value.vocdd,
|
||||
name: "VOCDD",
|
||||
title: "Value of Coin Days Destroyed",
|
||||
color: colors.purple,
|
||||
});
|
||||
|
||||
return {
|
||||
name: "Cointime",
|
||||
tree: [
|
||||
// Prices
|
||||
// Prices - the core pricing models
|
||||
{
|
||||
name: "Prices",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: "Cointime Prices",
|
||||
top: cointimePrices.map(({ pricePattern, name, color }) =>
|
||||
price({ metric: pricePattern, name, color }),
|
||||
),
|
||||
top: [
|
||||
price({ metric: all.realized.realizedPrice, name: "Realized", color: colors.orange }),
|
||||
price({ metric: all.realized.investorPrice, name: "Investor", color: colors.fuchsia }),
|
||||
...prices.map(({ pricePattern, name, color }) =>
|
||||
price({ metric: pricePattern, name, color }),
|
||||
),
|
||||
],
|
||||
},
|
||||
...cointimePrices.map(({ pricePattern, ratio, name, color, title }) => ({
|
||||
...prices.map(({ pricePattern, ratio, name, color }) => ({
|
||||
name,
|
||||
tree: createPriceRatioCharts(ctx, {
|
||||
context: title,
|
||||
context: `${name} Price`,
|
||||
legend: name,
|
||||
pricePattern,
|
||||
ratio,
|
||||
color,
|
||||
priceReferences: [price({ metric: all.realized.realizedPrice, name: "Realized", color: colors.orange, defaultActive: false })],
|
||||
}),
|
||||
})),
|
||||
],
|
||||
},
|
||||
|
||||
// Capitalization
|
||||
// Caps - market capitalizations from different models
|
||||
{
|
||||
name: "Capitalization",
|
||||
name: "Caps",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: "Cointime Caps",
|
||||
bottom: [
|
||||
line({
|
||||
metric: supply.marketCap,
|
||||
name: "Market",
|
||||
color: colors.default,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: all.realized.realizedCap,
|
||||
name: "Realized",
|
||||
color: colors.orange,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
...cointimeCapitalizations.map(({ metric, name, color }) =>
|
||||
...capReferenceLines.map(({ metric, name, color }) =>
|
||||
line({ metric, name, color, unit: Unit.usd }),
|
||||
),
|
||||
...caps.map(({ metric, name, color }) =>
|
||||
line({ metric, name, color, unit: Unit.usd }),
|
||||
),
|
||||
],
|
||||
},
|
||||
...cointimeCapitalizations.map(({ metric, name, color, title }) => ({
|
||||
...caps.map(({ metric, name, color }) => ({
|
||||
name,
|
||||
title,
|
||||
title: `${name} Cap`,
|
||||
bottom: [
|
||||
line({ metric, name, color, unit: Unit.usd }),
|
||||
line({
|
||||
metric: supply.marketCap,
|
||||
name: "Market",
|
||||
color: colors.default,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: all.realized.realizedCap,
|
||||
name: "Realized",
|
||||
color: colors.orange,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
...capReferenceLines.map((ref) =>
|
||||
line({
|
||||
metric: ref.metric,
|
||||
name: ref.name,
|
||||
color: ref.color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
],
|
||||
})),
|
||||
],
|
||||
},
|
||||
|
||||
// Supply
|
||||
// Supply - active vs vaulted breakdown
|
||||
{
|
||||
name: "Supply",
|
||||
title: "Cointime Supply",
|
||||
bottom: [
|
||||
...satsBtcUsd({ pattern: all.supply.total, name: "All", color: colors.orange }),
|
||||
...satsBtcUsd({ pattern: cointimeSupply.vaultedSupply, name: "Vaulted", color: colors.lime }),
|
||||
...satsBtcUsd({ pattern: cointimeSupply.activeSupply, name: "Active", color: colors.rose }),
|
||||
],
|
||||
title: "Active vs Vaulted Supply",
|
||||
bottom: supplyBreakdown.flatMap(({ pattern, name, color }) =>
|
||||
satsBtcUsd({ pattern, name, color }),
|
||||
),
|
||||
},
|
||||
|
||||
// Liveliness & Vaultedness
|
||||
// Liveliness - the foundational cointime ratios
|
||||
{
|
||||
name: "Liveliness & Vaultedness",
|
||||
name: "Activity",
|
||||
title: "Liveliness & Vaultedness",
|
||||
bottom: [
|
||||
line({
|
||||
@@ -191,73 +224,196 @@ export function createCointimeSection(ctx) {
|
||||
}),
|
||||
line({
|
||||
metric: activity.activityToVaultednessRatio,
|
||||
name: "Liveliness / Vaultedness",
|
||||
name: "L/V Ratio",
|
||||
color: colors.purple,
|
||||
unit: Unit.ratio,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
|
||||
// Coinblocks
|
||||
// Coinblocks - created, destroyed, stored
|
||||
{
|
||||
name: "Coinblocks",
|
||||
title: "Coinblocks",
|
||||
bottom: [
|
||||
// Destroyed comes from the all cohort's activity
|
||||
line({
|
||||
metric: all.activity.coinblocksDestroyed.sum,
|
||||
name: "Destroyed",
|
||||
color: colors.red,
|
||||
unit: Unit.coinblocks,
|
||||
}),
|
||||
line({
|
||||
metric: all.activity.coinblocksDestroyed.cumulative,
|
||||
name: "Cumulative Destroyed",
|
||||
color: colors.red,
|
||||
defaultActive: false,
|
||||
unit: Unit.coinblocks,
|
||||
}),
|
||||
// Created and stored from cointime
|
||||
line({
|
||||
metric: activity.coinblocksCreated.sum,
|
||||
name: "Created",
|
||||
color: colors.orange,
|
||||
unit: Unit.coinblocks,
|
||||
}),
|
||||
line({
|
||||
metric: activity.coinblocksCreated.cumulative,
|
||||
name: "Cumulative Created",
|
||||
color: colors.orange,
|
||||
defaultActive: false,
|
||||
unit: Unit.coinblocks,
|
||||
}),
|
||||
line({
|
||||
metric: activity.coinblocksStored.sum,
|
||||
name: "Stored",
|
||||
color: colors.green,
|
||||
unit: Unit.coinblocks,
|
||||
}),
|
||||
line({
|
||||
metric: activity.coinblocksStored.cumulative,
|
||||
name: "Cumulative Stored",
|
||||
color: colors.green,
|
||||
defaultActive: false,
|
||||
unit: Unit.coinblocks,
|
||||
}),
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
tree: [
|
||||
{
|
||||
name: "Sum",
|
||||
title: "Coinblocks",
|
||||
bottom: coinblocks.map(({ pattern, name, color }) =>
|
||||
line({
|
||||
metric: pattern.sum,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.coinblocks,
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: "Coinblocks (Total)",
|
||||
bottom: coinblocks.map(({ pattern, name, color }) =>
|
||||
line({
|
||||
metric: pattern.cumulative,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.coinblocks,
|
||||
}),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
...coinblocks.map(({ pattern, name, title, color }) => ({
|
||||
name,
|
||||
tree: [
|
||||
{
|
||||
name: "Sum",
|
||||
title,
|
||||
bottom: [
|
||||
line({
|
||||
metric: pattern.sum,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.coinblocks,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: `${title} (Total)`,
|
||||
bottom: [
|
||||
line({
|
||||
metric: pattern.cumulative,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.coinblocks,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
})),
|
||||
],
|
||||
},
|
||||
|
||||
// Reserve Risk
|
||||
// Value - cointime value flows
|
||||
{
|
||||
name: "Reserve Risk",
|
||||
name: "Value",
|
||||
tree: [
|
||||
{
|
||||
name: "Ratio",
|
||||
name: "Compare",
|
||||
tree: [
|
||||
{
|
||||
name: "Sum",
|
||||
title: "Cointime Value",
|
||||
bottom: [
|
||||
...cointimeValues.map(({ pattern, name, color }) =>
|
||||
line({ metric: pattern.sum, name, color, unit: Unit.usd }),
|
||||
),
|
||||
line({
|
||||
metric: vocdd.pattern.sum,
|
||||
name: vocdd.name,
|
||||
color: vocdd.color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: "Cointime Value (Total)",
|
||||
bottom: [
|
||||
...cointimeValues.map(({ pattern, name, color }) =>
|
||||
line({
|
||||
metric: pattern.cumulative,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
line({
|
||||
metric: vocdd.pattern.cumulative,
|
||||
name: vocdd.name,
|
||||
color: vocdd.color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
...cointimeValues.map(({ pattern, name, title, color }) => ({
|
||||
name,
|
||||
tree: [
|
||||
{
|
||||
name: "Sum",
|
||||
title,
|
||||
bottom: [
|
||||
line({ metric: pattern.sum, name, color, unit: Unit.usd }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: `${title} (Total)`,
|
||||
bottom: [
|
||||
line({
|
||||
metric: pattern.cumulative,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
})),
|
||||
{
|
||||
name: vocdd.name,
|
||||
tree: [
|
||||
{
|
||||
name: "Sum",
|
||||
title: vocdd.title,
|
||||
bottom: [
|
||||
line({
|
||||
metric: vocdd.pattern.sum,
|
||||
name: vocdd.name,
|
||||
color: vocdd.color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: reserveRisk.vocdd365dSma,
|
||||
name: "365d SMA",
|
||||
color: colors.cyan,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: `${vocdd.title} (Total)`,
|
||||
bottom: [
|
||||
line({
|
||||
metric: vocdd.pattern.cumulative,
|
||||
name: vocdd.name,
|
||||
color: vocdd.color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// Indicators - derived decision metrics
|
||||
{
|
||||
name: "Indicators",
|
||||
tree: [
|
||||
{
|
||||
name: "Reserve Risk",
|
||||
title: "Reserve Risk",
|
||||
bottom: [
|
||||
line({
|
||||
metric: reserveRisk.reserveRisk,
|
||||
name: "Reserve Risk",
|
||||
name: "Ratio",
|
||||
color: colors.orange,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
@@ -269,162 +425,76 @@ export function createCointimeSection(ctx) {
|
||||
bottom: [
|
||||
line({
|
||||
metric: reserveRisk.hodlBank,
|
||||
name: "HODL Bank",
|
||||
name: "Value",
|
||||
color: colors.blue,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "VOCDD 365d SMA",
|
||||
title: "VOCDD 365d SMA",
|
||||
bottom: [
|
||||
line({
|
||||
metric: reserveRisk.vocdd365dSma,
|
||||
name: "VOCDD 365d SMA",
|
||||
color: colors.purple,
|
||||
unit: Unit.ratio,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// Cointime Value
|
||||
// Cointime-Adjusted - comparing base vs adjusted metrics
|
||||
{
|
||||
name: "Value",
|
||||
name: "Cointime-Adjusted",
|
||||
tree: [
|
||||
{
|
||||
name: "Created",
|
||||
title: "Cointime Value Created",
|
||||
bottom: [
|
||||
line({
|
||||
metric: value.cointimeValueCreated.sum,
|
||||
name: "Created",
|
||||
color: colors.green,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: value.cointimeValueCreated.cumulative,
|
||||
name: "Cumulative",
|
||||
color: colors.green,
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Destroyed",
|
||||
title: "Cointime Value Destroyed",
|
||||
bottom: [
|
||||
line({
|
||||
metric: value.cointimeValueDestroyed.sum,
|
||||
name: "Destroyed",
|
||||
color: colors.red,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: value.cointimeValueDestroyed.cumulative,
|
||||
name: "Cumulative",
|
||||
color: colors.red,
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Stored",
|
||||
title: "Cointime Value Stored",
|
||||
bottom: [
|
||||
line({
|
||||
metric: value.cointimeValueStored.sum,
|
||||
name: "Stored",
|
||||
color: colors.blue,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: value.cointimeValueStored.cumulative,
|
||||
name: "Cumulative",
|
||||
color: colors.blue,
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "VOCDD",
|
||||
title: "VOCDD (Value of Coin Days Destroyed)",
|
||||
bottom: [
|
||||
line({
|
||||
metric: value.vocdd.sum,
|
||||
name: "VOCDD",
|
||||
color: colors.orange,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: value.vocdd.cumulative,
|
||||
name: "Cumulative",
|
||||
color: colors.orange,
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// Adjusted metrics
|
||||
{
|
||||
name: "Adjusted",
|
||||
tree: [
|
||||
// Inflation
|
||||
{
|
||||
name: "Inflation",
|
||||
title: "Adjusted Inflation",
|
||||
title: "Cointime-Adjusted Inflation",
|
||||
bottom: [
|
||||
line({
|
||||
dots({
|
||||
metric: supply.inflation,
|
||||
name: "Base",
|
||||
color: colors.orange,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
line({
|
||||
dots({
|
||||
metric: adjusted.cointimeAdjInflationRate,
|
||||
name: "Adjusted",
|
||||
name: "Cointime-Adjusted",
|
||||
color: colors.purple,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
],
|
||||
},
|
||||
// Velocity
|
||||
{
|
||||
name: "Velocity",
|
||||
title: "Adjusted Velocity",
|
||||
bottom: [
|
||||
line({
|
||||
metric: supply.velocity.btc,
|
||||
tree: [
|
||||
{
|
||||
name: "BTC",
|
||||
color: colors.orange,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
line({
|
||||
metric: adjusted.cointimeAdjTxBtcVelocity,
|
||||
name: "Adj. BTC",
|
||||
color: colors.red,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
line({
|
||||
metric: supply.velocity.usd,
|
||||
title: "Cointime-Adjusted BTC Velocity",
|
||||
bottom: [
|
||||
line({
|
||||
metric: supply.velocity.btc,
|
||||
name: "Base",
|
||||
color: colors.orange,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
line({
|
||||
metric: adjusted.cointimeAdjTxBtcVelocity,
|
||||
name: "Cointime-Adjusted",
|
||||
color: colors.red,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "USD",
|
||||
color: colors.emerald,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
line({
|
||||
metric: adjusted.cointimeAdjTxUsdVelocity,
|
||||
name: "Adj. USD",
|
||||
color: colors.lime,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
title: "Cointime-Adjusted USD Velocity",
|
||||
bottom: [
|
||||
line({
|
||||
metric: supply.velocity.usd,
|
||||
name: "Base",
|
||||
color: colors.emerald,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
line({
|
||||
metric: adjusted.cointimeAdjTxUsdVelocity,
|
||||
name: "Cointime-Adjusted",
|
||||
color: colors.lime,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -5,8 +5,22 @@ 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"]);
|
||||
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 */
|
||||
@@ -20,7 +34,9 @@ const LONG_PERIODS = /** @type {const} */ (["_2y", "_3y", "_4y", "_5y", "_6y", "
|
||||
*/
|
||||
const withCagr = (entry, cagr) => ({ ...entry, cagr });
|
||||
|
||||
const YEARS_2020S = /** @type {const} */ ([2026, 2025, 2024, 2023, 2022, 2021, 2020]);
|
||||
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 */
|
||||
@@ -142,7 +158,12 @@ 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),
|
||||
@@ -165,23 +186,51 @@ function createCompareFolder(context, items) {
|
||||
* @param {object[]} returnsBottom - Bottom pane items for returns chart
|
||||
*/
|
||||
function createSingleEntryTree(colors, item, returnsBottom) {
|
||||
const { name, titlePrefix = name, color, costBasis, daysInProfit, daysInLoss, stack } = item;
|
||||
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: returnsBottom },
|
||||
{
|
||||
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" }),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -195,8 +244,20 @@ 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 }),
|
||||
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,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -210,8 +271,20 @@ function createLongSingleEntry(colors, 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 }),
|
||||
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,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -228,7 +301,11 @@ export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) {
|
||||
|
||||
/** @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 }),
|
||||
];
|
||||
|
||||
@@ -246,8 +323,17 @@ export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) {
|
||||
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 }),
|
||||
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,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -255,8 +341,17 @@ export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) {
|
||||
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.periodMinReturn[key],
|
||||
name: "DCA",
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
baseline({
|
||||
metric: dca.periodLumpSumMinReturn[key],
|
||||
name: "Lump Sum",
|
||||
color: [colors.cyan, colors.orange],
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -270,8 +365,17 @@ 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 }),
|
||||
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),
|
||||
@@ -287,10 +391,28 @@ 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 }),
|
||||
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 }),
|
||||
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",
|
||||
unit: Unit.cagr,
|
||||
}),
|
||||
baseline({
|
||||
metric: returns.cagr[key],
|
||||
name: "Lump Sum",
|
||||
color: [colors.cyan, colors.orange],
|
||||
unit: Unit.cagr,
|
||||
}),
|
||||
],
|
||||
},
|
||||
...returnsMinMax(name, key),
|
||||
@@ -306,8 +428,18 @@ 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,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -315,8 +447,18 @@ 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,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -328,8 +470,16 @@ export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) {
|
||||
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,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
@@ -338,7 +488,12 @@ export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) {
|
||||
const name = periodName(key);
|
||||
return {
|
||||
name,
|
||||
tree: [costBasisChart(name, key), shortReturnsFolder(name, key), profitabilityFolder(name, key), stackChart(name, key)],
|
||||
tree: [
|
||||
costBasisChart(name, key),
|
||||
shortReturnsFolder(name, key),
|
||||
profitabilityFolder(name, key),
|
||||
stackChart(name, key),
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
@@ -347,7 +502,12 @@ export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) {
|
||||
const name = periodName(key);
|
||||
return {
|
||||
name,
|
||||
tree: [costBasisChart(name, key), longReturnsFolder(name, key), profitabilityFolder(name, key), stackChart(name, key)],
|
||||
tree: [
|
||||
costBasisChart(name, key),
|
||||
longReturnsFolder(name, key),
|
||||
profitabilityFolder(name, key),
|
||||
stackChart(name, key),
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
@@ -355,8 +515,16 @@ export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) {
|
||||
name: "DCA vs Lump Sum",
|
||||
title: "Compare Investment Strategies",
|
||||
tree: [
|
||||
{ name: "Short Term", title: "Up to 1 Year", tree: SHORT_PERIODS.map(createShortPeriodEntry) },
|
||||
{ name: "Long Term", title: "2+ Years", tree: LONG_PERIODS.map(createLongPeriodEntry) },
|
||||
{
|
||||
name: "Short Term",
|
||||
title: "Up to 1 Year",
|
||||
tree: SHORT_PERIODS.map(createShortPeriodEntry),
|
||||
},
|
||||
{
|
||||
name: "Long Term",
|
||||
title: "2+ Years",
|
||||
tree: LONG_PERIODS.map(createLongPeriodEntry),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -380,26 +548,41 @@ function createPeriodSection(ctx, { dca, lookback, returns }) {
|
||||
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],
|
||||
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],
|
||||
);
|
||||
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}` });
|
||||
createShortSingleEntry(colors, {
|
||||
...entry,
|
||||
titlePrefix: `${entry.name} ${suffix}`,
|
||||
});
|
||||
|
||||
/** @param {LongEntryItem} entry */
|
||||
const createLongEntry = (entry) =>
|
||||
createLongSingleEntry(colors, { ...entry, titlePrefix: `${entry.name} ${suffix}` });
|
||||
createLongSingleEntry(colors, {
|
||||
...entry,
|
||||
titlePrefix: `${entry.name} ${suffix}`,
|
||||
});
|
||||
|
||||
const shortEntries = SHORT_PERIODS.map(buildBaseEntry);
|
||||
const longEntries = LONG_PERIODS.map(buildLongEntry);
|
||||
@@ -408,16 +591,25 @@ function createPeriodSection(ctx, { dca, lookback, returns }) {
|
||||
name: `${suffix} by Period`,
|
||||
title: `${suffix} Performance by Investment Period`,
|
||||
tree: [
|
||||
createCompareFolder(`All Periods ${suffix}`, [...shortEntries, ...longEntries]),
|
||||
createCompareFolder(`All Periods ${suffix}`, [
|
||||
...shortEntries,
|
||||
...longEntries,
|
||||
]),
|
||||
{
|
||||
name: "Short Term",
|
||||
title: "Up to 1 Year",
|
||||
tree: [createCompareFolder(`Short Term ${suffix}`, shortEntries), ...shortEntries.map(createShortEntry)],
|
||||
tree: [
|
||||
createCompareFolder(`Short Term ${suffix}`, shortEntries),
|
||||
...shortEntries.map(createShortEntry),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Long Term",
|
||||
title: "2+ Years",
|
||||
tree: [createCompareFolder(`Long Term ${suffix}`, longEntries), ...longEntries.map(createLongEntry)],
|
||||
tree: [
|
||||
createCompareFolder(`Long Term ${suffix}`, longEntries),
|
||||
...longEntries.map(createLongEntry),
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -461,12 +653,21 @@ export function createDcaByStartYearSection(ctx, { dca }) {
|
||||
title,
|
||||
tree: [
|
||||
createCompareFolder(`${name} DCA`, entries),
|
||||
...entries.map((entry) => createShortSingleEntry(colors, { ...entry, titlePrefix: `${entry.name} DCA` })),
|
||||
...entries.map((entry) =>
|
||||
createShortSingleEntry(colors, {
|
||||
...entry,
|
||||
titlePrefix: `${entry.name} DCA`,
|
||||
}),
|
||||
),
|
||||
],
|
||||
});
|
||||
|
||||
const entries2020s = YEARS_2020S.map((year) => buildYearEntry(colors, dca, year));
|
||||
const entries2010s = YEARS_2010S.map((year) => buildYearEntry(colors, dca, year));
|
||||
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",
|
||||
|
||||
481
website/scripts/options/market.js
Normal file
481
website/scripts/options/market.js
Normal file
@@ -0,0 +1,481 @@
|
||||
/** Market section */
|
||||
|
||||
import { includes } from "../utils/array.js";
|
||||
import { Unit } from "../utils/units.js";
|
||||
import { priceLine, priceLines } from "./constants.js";
|
||||
import { baseline, histogram, line, price } from "./series.js";
|
||||
import { createPriceRatioCharts } from "./shared.js";
|
||||
import { periodIdToName } from "./utils.js";
|
||||
|
||||
/**
|
||||
* @typedef {Object} Period
|
||||
* @property {string} id
|
||||
* @property {Color} color
|
||||
* @property {AnyMetricPattern} returns
|
||||
* @property {AnyPricePattern} lookback
|
||||
* @property {boolean} [defaultActive]
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Period & { cagr: AnyMetricPattern }} PeriodWithCagr
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} MaPeriod
|
||||
* @property {string} id
|
||||
* @property {Color} color
|
||||
* @property {ActivePriceRatioPattern} ratio
|
||||
*/
|
||||
|
||||
const commonMaIds = /** @type {const} */ (["1w", "1m", "200d", "1y", "200w", "4y"]);
|
||||
|
||||
/**
|
||||
* @param {PartialContext} ctx
|
||||
* @param {string} label
|
||||
* @param {MaPeriod[]} averages
|
||||
*/
|
||||
function createMaSubSection(ctx, label, averages) {
|
||||
const common = averages.filter((a) => includes(commonMaIds, a.id));
|
||||
const more = averages.filter((a) => !includes(commonMaIds, a.id));
|
||||
|
||||
/** @param {MaPeriod} a */
|
||||
const toFolder = (a) => ({
|
||||
name: periodIdToName(a.id, true),
|
||||
tree: createPriceRatioCharts(ctx, {
|
||||
context: `${periodIdToName(a.id, true)} ${label}`,
|
||||
legend: "average",
|
||||
pricePattern: a.ratio.price,
|
||||
ratio: a.ratio,
|
||||
color: a.color,
|
||||
}),
|
||||
});
|
||||
|
||||
return {
|
||||
name: label,
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: `Price ${label}s`,
|
||||
top: averages.map((a) => price({ metric: a.ratio.price, name: a.id, color: a.color })),
|
||||
},
|
||||
...common.map(toFolder),
|
||||
{ name: "More...", tree: more.map(toFolder) },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Colors} colors
|
||||
* @param {string} name
|
||||
* @param {string} title
|
||||
* @param {Unit} unit
|
||||
* @param {{ _1w: AnyMetricPattern, _1m: AnyMetricPattern, _1y: AnyMetricPattern }} metrics
|
||||
*/
|
||||
function volatilityChart(colors, name, title, unit, metrics) {
|
||||
return {
|
||||
name,
|
||||
title,
|
||||
bottom: [
|
||||
line({ metric: metrics._1w, name: "1w", color: colors.red, unit }),
|
||||
line({ metric: metrics._1m, name: "1m", color: colors.orange, unit }),
|
||||
line({ metric: metrics._1y, name: "1y", color: colors.lime, unit }),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {Period[]} periods
|
||||
*/
|
||||
function returnsSubSection(name, periods) {
|
||||
return {
|
||||
name,
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: `${name} Returns`,
|
||||
bottom: periods.map((p) => baseline({ metric: p.returns, name: p.id, color: p.color, unit: Unit.percentage })),
|
||||
},
|
||||
...periods.map((p) => ({
|
||||
name: periodIdToName(p.id, true),
|
||||
title: `${periodIdToName(p.id, true)} Returns`,
|
||||
bottom: [baseline({ metric: p.returns, name: "Total", unit: Unit.percentage })],
|
||||
})),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {PeriodWithCagr[]} periods
|
||||
*/
|
||||
function returnsSubSectionWithCagr(name, periods) {
|
||||
return {
|
||||
name,
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: `${name} Returns`,
|
||||
bottom: periods.map((p) => baseline({ metric: p.returns, name: p.id, color: p.color, unit: Unit.percentage })),
|
||||
},
|
||||
...periods.map((p) => ({
|
||||
name: periodIdToName(p.id, true),
|
||||
title: `${periodIdToName(p.id, true)} Returns`,
|
||||
bottom: [
|
||||
baseline({ metric: p.returns, name: "Total", unit: Unit.percentage }),
|
||||
baseline({ metric: p.cagr, name: "annual", unit: Unit.cagr }),
|
||||
],
|
||||
})),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {Period[]} periods
|
||||
*/
|
||||
function historicalSubSection(name, periods) {
|
||||
return {
|
||||
name,
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: `${name} Historical`,
|
||||
top: periods.map((p) => price({ metric: p.lookback, name: p.id, color: p.color })),
|
||||
},
|
||||
...periods.map((p) => ({
|
||||
name: periodIdToName(p.id, true),
|
||||
title: `${periodIdToName(p.id, true)} Ago`,
|
||||
top: [price({ metric: p.lookback, name: "Price" })],
|
||||
})),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Market section
|
||||
* @param {PartialContext} ctx
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createMarketSection(ctx) {
|
||||
const { colors, brk } = ctx;
|
||||
const { market, supply, price: priceMetrics } = brk.metrics;
|
||||
const { movingAverage: ma, ath, returns, volatility, range, indicators, lookback } = market;
|
||||
|
||||
/** @type {Period[]} */
|
||||
const shortPeriods = [
|
||||
{ id: "1d", color: colors.returns._1d, returns: returns.priceReturns._1d, lookback: lookback._1d },
|
||||
{ id: "1w", color: colors.returns._1w, returns: returns.priceReturns._1w, lookback: lookback._1w },
|
||||
{ id: "1m", color: colors.returns._1m, returns: returns.priceReturns._1m, lookback: lookback._1m },
|
||||
{ id: "3m", color: colors.returns._3m, returns: returns.priceReturns._3m, lookback: lookback._3m, defaultActive: false },
|
||||
{ id: "6m", color: colors.returns._6m, returns: returns.priceReturns._6m, lookback: lookback._6m, defaultActive: false },
|
||||
{ id: "1y", color: colors.returns._1y, returns: returns.priceReturns._1y, lookback: lookback._1y },
|
||||
];
|
||||
|
||||
/** @type {PeriodWithCagr[]} */
|
||||
const longPeriods = [
|
||||
{ id: "2y", color: colors.returns._2y, returns: returns.priceReturns._2y, cagr: returns.cagr._2y, lookback: lookback._2y, defaultActive: false },
|
||||
{ id: "3y", color: colors.returns._3y, returns: returns.priceReturns._3y, cagr: returns.cagr._3y, lookback: lookback._3y, defaultActive: false },
|
||||
{ id: "4y", color: colors.returns._4y, returns: returns.priceReturns._4y, cagr: returns.cagr._4y, lookback: lookback._4y },
|
||||
{ id: "5y", color: colors.returns._5y, returns: returns.priceReturns._5y, cagr: returns.cagr._5y, lookback: lookback._5y, defaultActive: false },
|
||||
{ id: "6y", color: colors.returns._6y, returns: returns.priceReturns._6y, cagr: returns.cagr._6y, lookback: lookback._6y, defaultActive: false },
|
||||
{ id: "8y", color: colors.returns._8y, returns: returns.priceReturns._8y, cagr: returns.cagr._8y, lookback: lookback._8y, defaultActive: false },
|
||||
{ id: "10y", color: colors.returns._10y, returns: returns.priceReturns._10y, cagr: returns.cagr._10y, lookback: lookback._10y, defaultActive: false },
|
||||
];
|
||||
|
||||
/** @type {MaPeriod[]} */
|
||||
const sma = [
|
||||
{ id: "1w", color: colors.ma._1w, ratio: ma.price1wSma },
|
||||
{ id: "8d", color: colors.ma._8d, ratio: ma.price8dSma },
|
||||
{ id: "13d", color: colors.ma._13d, ratio: ma.price13dSma },
|
||||
{ id: "21d", color: colors.ma._21d, ratio: ma.price21dSma },
|
||||
{ id: "1m", color: colors.ma._1m, ratio: ma.price1mSma },
|
||||
{ id: "34d", color: colors.ma._34d, ratio: ma.price34dSma },
|
||||
{ id: "55d", color: colors.ma._55d, ratio: ma.price55dSma },
|
||||
{ id: "89d", color: colors.ma._89d, ratio: ma.price89dSma },
|
||||
{ id: "111d", color: colors.ma._111d, ratio: ma.price111dSma },
|
||||
{ id: "144d", color: colors.ma._144d, ratio: ma.price144dSma },
|
||||
{ id: "200d", color: colors.ma._200d, ratio: ma.price200dSma },
|
||||
{ id: "350d", color: colors.ma._350d, ratio: ma.price350dSma },
|
||||
{ id: "1y", color: colors.ma._1y, ratio: ma.price1ySma },
|
||||
{ id: "2y", color: colors.ma._2y, ratio: ma.price2ySma },
|
||||
{ id: "200w", color: colors.ma._200w, ratio: ma.price200wSma },
|
||||
{ id: "4y", color: colors.ma._4y, ratio: ma.price4ySma },
|
||||
];
|
||||
|
||||
/** @type {MaPeriod[]} */
|
||||
const ema = [
|
||||
{ id: "1w", color: colors.ma._1w, ratio: ma.price1wEma },
|
||||
{ id: "8d", color: colors.ma._8d, ratio: ma.price8dEma },
|
||||
{ id: "12d", color: colors.ma._12d, ratio: ma.price12dEma },
|
||||
{ id: "13d", color: colors.ma._13d, ratio: ma.price13dEma },
|
||||
{ id: "21d", color: colors.ma._21d, ratio: ma.price21dEma },
|
||||
{ id: "26d", color: colors.ma._26d, ratio: ma.price26dEma },
|
||||
{ id: "1m", color: colors.ma._1m, ratio: ma.price1mEma },
|
||||
{ id: "34d", color: colors.ma._34d, ratio: ma.price34dEma },
|
||||
{ id: "55d", color: colors.ma._55d, ratio: ma.price55dEma },
|
||||
{ id: "89d", color: colors.ma._89d, ratio: ma.price89dEma },
|
||||
{ id: "144d", color: colors.ma._144d, ratio: ma.price144dEma },
|
||||
{ id: "200d", color: colors.ma._200d, ratio: ma.price200dEma },
|
||||
{ id: "1y", color: colors.ma._1y, ratio: ma.price1yEma },
|
||||
{ id: "2y", color: colors.ma._2y, ratio: ma.price2yEma },
|
||||
{ id: "200w", color: colors.ma._200w, ratio: ma.price200wEma },
|
||||
{ id: "4y", color: colors.ma._4y, ratio: ma.price4yEma },
|
||||
];
|
||||
|
||||
// SMA vs EMA comparison periods (common periods only)
|
||||
const smaVsEma = [
|
||||
{ id: "1w", name: "1 Week", color: colors.ma._1w, sma: ma.price1wSma, ema: ma.price1wEma },
|
||||
{ id: "1m", name: "1 Month", color: colors.ma._1m, sma: ma.price1mSma, ema: ma.price1mEma },
|
||||
{ id: "200d", name: "200 Day", color: colors.ma._200d, sma: ma.price200dSma, ema: ma.price200dEma },
|
||||
{ id: "1y", name: "1 Year", color: colors.ma._1y, sma: ma.price1ySma, ema: ma.price1yEma },
|
||||
{ id: "200w", name: "200 Week", color: colors.ma._200w, sma: ma.price200wSma, ema: ma.price200wEma },
|
||||
{ id: "4y", name: "4 Year", color: colors.ma._4y, sma: ma.price4ySma, ema: ma.price4yEma },
|
||||
];
|
||||
|
||||
return {
|
||||
name: "Market",
|
||||
tree: [
|
||||
{ name: "Price", title: "Bitcoin Price" },
|
||||
|
||||
{
|
||||
name: "Sats/$",
|
||||
title: "Sats per Dollar",
|
||||
bottom: [line({ metric: priceMetrics.sats.split.close, name: "Sats/$", unit: Unit.sats })],
|
||||
},
|
||||
|
||||
{
|
||||
name: "Capitalization",
|
||||
title: "Market Capitalization",
|
||||
bottom: [line({ metric: supply.marketCap, name: "Capitalization", unit: Unit.usd })],
|
||||
},
|
||||
|
||||
{
|
||||
name: "All Time High",
|
||||
tree: [
|
||||
{
|
||||
name: "Drawdown",
|
||||
title: "ATH Drawdown",
|
||||
top: [price({ metric: ath.priceAth, name: "ATH" })],
|
||||
bottom: [line({ metric: ath.priceDrawdown, name: "Drawdown", color: colors.red, unit: Unit.percentage })],
|
||||
},
|
||||
{
|
||||
name: "Time Since",
|
||||
title: "Time Since ATH",
|
||||
top: [price({ metric: ath.priceAth, name: "ATH" })],
|
||||
bottom: [
|
||||
line({ metric: ath.daysSincePriceAth, name: "Since", unit: Unit.days }),
|
||||
line({ metric: ath.yearsSincePriceAth, name: "Since", unit: Unit.years }),
|
||||
line({ metric: ath.maxDaysBetweenPriceAths, name: "Max", color: colors.red, unit: Unit.days }),
|
||||
line({ metric: ath.maxYearsBetweenPriceAths, name: "Max", color: colors.red, unit: Unit.years }),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
name: "Returns",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: "Returns Comparison",
|
||||
bottom: [...shortPeriods, ...longPeriods].map((p) =>
|
||||
baseline({
|
||||
metric: p.returns,
|
||||
name: p.id,
|
||||
color: p.color,
|
||||
unit: Unit.percentage,
|
||||
defaultActive: p.defaultActive,
|
||||
}),
|
||||
),
|
||||
},
|
||||
returnsSubSection("Short-term", shortPeriods),
|
||||
returnsSubSectionWithCagr("Long-term", longPeriods),
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
name: "Volatility",
|
||||
tree: [
|
||||
volatilityChart(colors, "Index", "Volatility Index", Unit.percentage, {
|
||||
_1w: volatility.price1wVolatility,
|
||||
_1m: volatility.price1mVolatility,
|
||||
_1y: volatility.price1yVolatility,
|
||||
}),
|
||||
{
|
||||
name: "True Range",
|
||||
title: "True Range",
|
||||
bottom: [
|
||||
line({ metric: range.priceTrueRange, name: "Daily", color: colors.yellow, unit: Unit.usd }),
|
||||
line({ metric: range.priceTrueRange2wSum, name: "2w Sum", color: colors.orange, unit: Unit.usd, defaultActive: false }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Choppiness",
|
||||
title: "Choppiness Index",
|
||||
bottom: [
|
||||
line({ metric: range.price2wChoppinessIndex, name: "2w", color: colors.red, unit: Unit.index }),
|
||||
...priceLines({ ctx, unit: Unit.index, numbers: [61.8, 38.2] }),
|
||||
],
|
||||
},
|
||||
volatilityChart(colors, "Sharpe Ratio", "Sharpe Ratio", Unit.ratio, {
|
||||
_1w: volatility.sharpe1w,
|
||||
_1m: volatility.sharpe1m,
|
||||
_1y: volatility.sharpe1y,
|
||||
}),
|
||||
volatilityChart(colors, "Sortino Ratio", "Sortino Ratio", Unit.ratio, {
|
||||
_1w: volatility.sortino1w,
|
||||
_1m: volatility.sortino1m,
|
||||
_1y: volatility.sortino1y,
|
||||
}),
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
name: "Moving Averages",
|
||||
tree: [
|
||||
{
|
||||
name: "SMA vs EMA",
|
||||
tree: [
|
||||
{
|
||||
name: "All Periods",
|
||||
title: "SMA vs EMA Comparison",
|
||||
top: smaVsEma.flatMap((p) => [
|
||||
price({ metric: p.sma.price, name: `${p.id} SMA`, color: p.color }),
|
||||
price({ metric: p.ema.price, name: `${p.id} EMA`, color: p.color, style: 1 }),
|
||||
]),
|
||||
},
|
||||
...smaVsEma.map((p) => ({
|
||||
name: p.name,
|
||||
title: `${p.name} SMA vs EMA`,
|
||||
top: [
|
||||
price({ metric: p.sma.price, name: "SMA", color: p.color }),
|
||||
price({ metric: p.ema.price, name: "EMA", color: p.color, style: 1 }),
|
||||
],
|
||||
})),
|
||||
],
|
||||
},
|
||||
createMaSubSection(ctx, "SMA", sma),
|
||||
createMaSubSection(ctx, "EMA", ema),
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
name: "Bands",
|
||||
tree: [
|
||||
{
|
||||
name: "MinMax",
|
||||
tree: [
|
||||
{ id: "1w", name: "1 Week", min: range.price1wMin, max: range.price1wMax },
|
||||
{ id: "2w", name: "2 Week", min: range.price2wMin, max: range.price2wMax },
|
||||
{ id: "1m", name: "1 Month", min: range.price1mMin, max: range.price1mMax },
|
||||
{ id: "1y", name: "1 Year", min: range.price1yMin, max: range.price1yMax },
|
||||
].map((p) => ({
|
||||
name: p.id,
|
||||
title: `${p.name} MinMax`,
|
||||
top: [
|
||||
price({ metric: p.min, name: "Min", key: "price-min", color: colors.red }),
|
||||
price({ metric: p.max, name: "Max", key: "price-max", color: colors.green }),
|
||||
],
|
||||
})),
|
||||
},
|
||||
{
|
||||
name: "Mayer Multiple",
|
||||
title: "Mayer Multiple",
|
||||
top: [
|
||||
price({ metric: ma.price200dSma.price, name: "200d SMA", color: colors.yellow }),
|
||||
price({ metric: ma.price200dSmaX24, name: "200d SMA x2.4", color: colors.green }),
|
||||
price({ metric: ma.price200dSmaX08, name: "200d SMA x0.8", color: colors.red }),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
name: "Momentum",
|
||||
tree: [
|
||||
{
|
||||
name: "RSI",
|
||||
title: "RSI (14d)",
|
||||
bottom: [
|
||||
line({ metric: indicators.rsi14d, name: "RSI", color: colors.indigo, unit: Unit.index }),
|
||||
line({ metric: indicators.rsi14dMin, name: "Min", color: colors.red, defaultActive: false, unit: Unit.index }),
|
||||
line({ metric: indicators.rsi14dMax, name: "Max", color: colors.green, defaultActive: false, unit: Unit.index }),
|
||||
priceLine({ ctx, unit: Unit.index, number: 70 }),
|
||||
priceLine({ ctx, unit: Unit.index, number: 50, defaultActive: false }),
|
||||
priceLine({ ctx, unit: Unit.index, number: 30 }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "StochRSI",
|
||||
title: "Stochastic RSI",
|
||||
bottom: [
|
||||
line({ metric: indicators.stochRsiK, name: "K", color: colors.blue, unit: Unit.index }),
|
||||
line({ metric: indicators.stochRsiD, name: "D", color: colors.orange, unit: Unit.index }),
|
||||
...priceLines({ ctx, unit: Unit.index, numbers: [80, 20] }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "MACD",
|
||||
title: "MACD",
|
||||
bottom: [
|
||||
line({ metric: indicators.macdLine, name: "MACD", color: colors.blue, unit: Unit.usd }),
|
||||
line({ metric: indicators.macdSignal, name: "Signal", color: colors.orange, unit: Unit.usd }),
|
||||
histogram({ metric: indicators.macdHistogram, name: "Histogram", unit: Unit.usd }),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
name: "Historical",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: "Historical Comparison",
|
||||
top: [...shortPeriods, ...longPeriods].map((p) =>
|
||||
price({
|
||||
metric: p.lookback,
|
||||
name: p.id,
|
||||
color: p.color,
|
||||
defaultActive: p.defaultActive,
|
||||
}),
|
||||
),
|
||||
},
|
||||
historicalSubSection("Short-term", shortPeriods),
|
||||
historicalSubSection("Long-term", longPeriods),
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
name: "Indicators",
|
||||
tree: [
|
||||
{
|
||||
name: "Pi Cycle",
|
||||
title: "Pi Cycle",
|
||||
top: [
|
||||
price({ metric: ma.price111dSma.price, name: "111d SMA", color: colors.green }),
|
||||
price({ metric: ma.price350dSmaX2, name: "350d SMA x2", color: colors.red }),
|
||||
],
|
||||
bottom: [baseline({ metric: indicators.piCycle, name: "Pi Cycle", unit: Unit.ratio, base: 1 })],
|
||||
},
|
||||
{
|
||||
name: "Puell Multiple",
|
||||
title: "Puell Multiple",
|
||||
bottom: [line({ metric: indicators.puellMultiple, name: "Puell", color: colors.green, unit: Unit.ratio })],
|
||||
},
|
||||
{
|
||||
name: "NVT",
|
||||
title: "NVT Ratio",
|
||||
bottom: [line({ metric: indicators.nvt, name: "NVT", color: colors.orange, unit: Unit.ratio })],
|
||||
},
|
||||
{
|
||||
name: "Gini",
|
||||
title: "Gini Coefficient",
|
||||
bottom: [line({ metric: indicators.gini, name: "Gini", color: colors.red, unit: Unit.ratio })],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -1,202 +0,0 @@
|
||||
/** Moving averages section */
|
||||
|
||||
import { price } from "../series.js";
|
||||
import { createPriceRatioCharts } from "../shared.js";
|
||||
import { periodIdToName } from "../utils.js";
|
||||
|
||||
/**
|
||||
* @param {Colors} colors
|
||||
* @param {MarketMovingAverage} ma
|
||||
*/
|
||||
function buildSmaAverages(colors, ma) {
|
||||
return /** @type {const} */ ([
|
||||
["1w", 7, "red", ma.price1wSma],
|
||||
["8d", 8, "orange", ma.price8dSma],
|
||||
["13d", 13, "amber", ma.price13dSma],
|
||||
["21d", 21, "yellow", ma.price21dSma],
|
||||
["1m", 30, "lime", ma.price1mSma],
|
||||
["34d", 34, "green", ma.price34dSma],
|
||||
["55d", 55, "emerald", ma.price55dSma],
|
||||
["89d", 89, "teal", ma.price89dSma],
|
||||
["111d", 111, "cyan", ma.price111dSma],
|
||||
["144d", 144, "sky", ma.price144dSma],
|
||||
["200d", 200, "blue", ma.price200dSma],
|
||||
["350d", 350, "indigo", ma.price350dSma],
|
||||
["1y", 365, "violet", ma.price1ySma],
|
||||
["2y", 730, "purple", ma.price2ySma],
|
||||
["200w", 1400, "fuchsia", ma.price200wSma],
|
||||
["4y", 1460, "pink", ma.price4ySma],
|
||||
]).map(([id, days, colorKey, ratio]) => ({
|
||||
id,
|
||||
name: periodIdToName(id, true),
|
||||
days,
|
||||
color: colors[colorKey],
|
||||
ratio,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Colors} colors
|
||||
* @param {MarketMovingAverage} ma
|
||||
*/
|
||||
function buildEmaAverages(colors, ma) {
|
||||
return /** @type {const} */ ([
|
||||
["1w", 7, "red", ma.price1wEma],
|
||||
["8d", 8, "orange", ma.price8dEma],
|
||||
["12d", 12, "amber", ma.price12dEma],
|
||||
["13d", 13, "yellow", ma.price13dEma],
|
||||
["21d", 21, "lime", ma.price21dEma],
|
||||
["26d", 26, "green", ma.price26dEma],
|
||||
["1m", 30, "emerald", ma.price1mEma],
|
||||
["34d", 34, "teal", ma.price34dEma],
|
||||
["55d", 55, "cyan", ma.price55dEma],
|
||||
["89d", 89, "sky", ma.price89dEma],
|
||||
["144d", 144, "blue", ma.price144dEma],
|
||||
["200d", 200, "indigo", ma.price200dEma],
|
||||
["1y", 365, "violet", ma.price1yEma],
|
||||
["2y", 730, "purple", ma.price2yEma],
|
||||
["200w", 1400, "fuchsia", ma.price200wEma],
|
||||
["4y", 1460, "pink", ma.price4yEma],
|
||||
]).map(([id, days, colorKey, ratio]) => ({
|
||||
id,
|
||||
name: periodIdToName(id, true),
|
||||
days,
|
||||
color: colors[colorKey],
|
||||
ratio,
|
||||
}));
|
||||
}
|
||||
|
||||
/** Common period IDs to show at top level */
|
||||
const COMMON_PERIODS = ["1w", "1m", "200d", "1y", "200w", "4y"];
|
||||
|
||||
/** Periods to compare SMA vs EMA */
|
||||
const COMPARISON_PERIODS = ["1w", "1m", "200d", "1y", "200w", "4y"];
|
||||
|
||||
/**
|
||||
* Create SMA vs EMA comparison section
|
||||
* @param {ReturnType<typeof buildSmaAverages>} smaAverages
|
||||
* @param {ReturnType<typeof buildEmaAverages>} emaAverages
|
||||
*/
|
||||
function createCompareSection(smaAverages, emaAverages) {
|
||||
// Find matching SMA/EMA pairs
|
||||
const pairs = COMPARISON_PERIODS.map((id) => {
|
||||
const sma = smaAverages.find((a) => a.id === id);
|
||||
const ema = emaAverages.find((a) => a.id === id);
|
||||
if (!sma || !ema) return null;
|
||||
return { id, sma, ema };
|
||||
}).filter(
|
||||
/** @type {(p: any) => p is { id: string, sma: ReturnType<typeof buildSmaAverages>[number], ema: ReturnType<typeof buildEmaAverages>[number] }} */ (
|
||||
p,
|
||||
) => p !== null,
|
||||
);
|
||||
|
||||
return {
|
||||
name: "Compare",
|
||||
tree: [
|
||||
{
|
||||
name: "All Periods",
|
||||
title: "SMA vs EMA Comparison",
|
||||
top: pairs.flatMap(({ sma, ema }) => [
|
||||
price({
|
||||
metric: sma.ratio.price,
|
||||
name: `${sma.id} SMA`,
|
||||
color: sma.color,
|
||||
}),
|
||||
price({
|
||||
metric: ema.ratio.price,
|
||||
name: `${ema.id} EMA`,
|
||||
color: ema.color,
|
||||
style: 1,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
...pairs.map(({ id, sma, ema }) => ({
|
||||
name: periodIdToName(id, true),
|
||||
title: `${periodIdToName(id, true)} SMA vs EMA`,
|
||||
top: [
|
||||
price({ metric: sma.ratio.price, name: "SMA", color: sma.color }),
|
||||
price({
|
||||
metric: ema.ratio.price,
|
||||
name: "EMA",
|
||||
color: ema.color,
|
||||
style: 1,
|
||||
}),
|
||||
],
|
||||
})),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {PartialContext} ctx
|
||||
* @param {MarketMovingAverage} movingAverage
|
||||
*/
|
||||
export function createAveragesSection(ctx, movingAverage) {
|
||||
const { colors } = ctx;
|
||||
const smaAverages = buildSmaAverages(colors, movingAverage);
|
||||
const emaAverages = buildEmaAverages(colors, movingAverage);
|
||||
|
||||
/**
|
||||
* @param {string} label
|
||||
* @param {ReturnType<typeof buildSmaAverages> | ReturnType<typeof buildEmaAverages>} averages
|
||||
*/
|
||||
const createSubSection = (label, averages) => {
|
||||
const commonAverages = averages.filter(({ id }) =>
|
||||
COMMON_PERIODS.includes(id),
|
||||
);
|
||||
const moreAverages = averages.filter(
|
||||
({ id }) => !COMMON_PERIODS.includes(id),
|
||||
);
|
||||
|
||||
return {
|
||||
name: label,
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: `Price ${label}s`,
|
||||
top: averages.map(({ id, color, ratio }) =>
|
||||
price({
|
||||
metric: ratio.price,
|
||||
name: id,
|
||||
color,
|
||||
}),
|
||||
),
|
||||
},
|
||||
// Common periods at top level
|
||||
...commonAverages.map(({ name, color, ratio }) => ({
|
||||
name,
|
||||
tree: createPriceRatioCharts(ctx, {
|
||||
context: `${name} ${label}`,
|
||||
legend: "average",
|
||||
pricePattern: ratio.price,
|
||||
ratio,
|
||||
color,
|
||||
}),
|
||||
})),
|
||||
// Less common periods in "More..." folder
|
||||
{
|
||||
name: "More...",
|
||||
tree: moreAverages.map(({ name, color, ratio }) => ({
|
||||
name,
|
||||
tree: createPriceRatioCharts(ctx, {
|
||||
context: `${name} ${label}`,
|
||||
legend: "average",
|
||||
pricePattern: ratio.price,
|
||||
ratio,
|
||||
color,
|
||||
}),
|
||||
})),
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
name: "Moving Averages",
|
||||
tree: [
|
||||
createCompareSection(smaAverages, emaAverages),
|
||||
createSubSection("SMA", smaAverages),
|
||||
createSubSection("EMA", emaAverages),
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
import { price } from "../series.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 { 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: `${title} MinMax`,
|
||||
top: [
|
||||
price({
|
||||
metric: min,
|
||||
name: "Min",
|
||||
key: `price-min`,
|
||||
color: colors.red,
|
||||
}),
|
||||
price({
|
||||
metric: max,
|
||||
name: "Max",
|
||||
key: `price-max`,
|
||||
color: colors.green,
|
||||
}),
|
||||
],
|
||||
})),
|
||||
},
|
||||
{
|
||||
name: "Mayer Multiple",
|
||||
title: "Mayer Multiple",
|
||||
top: [
|
||||
price({
|
||||
metric: movingAverage.price200dSma.price,
|
||||
name: "200d SMA",
|
||||
color: colors.yellow,
|
||||
}),
|
||||
price({
|
||||
metric: movingAverage.price200dSmaX24,
|
||||
name: "200d SMA x2.4",
|
||||
color: colors.green,
|
||||
}),
|
||||
price({
|
||||
metric: movingAverage.price200dSmaX08,
|
||||
name: "200d SMA x0.8",
|
||||
color: colors.red,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -1,175 +0,0 @@
|
||||
/** Market section - Main entry point */
|
||||
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { line, price } from "../series.js";
|
||||
import { createAveragesSection } from "./averages.js";
|
||||
import { createReturnsSection } from "./performance.js";
|
||||
import { createMomentumSection } from "./momentum.js";
|
||||
import { createVolatilitySection } from "./volatility.js";
|
||||
import { createBandsSection } from "./bands.js";
|
||||
import { createValuationSection } from "./onchain.js";
|
||||
|
||||
/**
|
||||
* Create Market section
|
||||
* @param {PartialContext} ctx
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createMarketSection(ctx) {
|
||||
const { colors, brk } = ctx;
|
||||
const { market, supply } = brk.metrics;
|
||||
const { movingAverage, ath, returns, volatility, range, indicators } = market;
|
||||
|
||||
return {
|
||||
name: "Market",
|
||||
tree: [
|
||||
// Price
|
||||
{
|
||||
name: "Price",
|
||||
title: "Bitcoin Price",
|
||||
},
|
||||
// Oracle section is localhost-only debug - uses non-price-pattern metrics
|
||||
// ...(localhost
|
||||
// ? /** @type {PartialOptionsTree} */ ([
|
||||
// {
|
||||
// name: "Oracle",
|
||||
// title: "Oracle Price",
|
||||
// top: /** @type {any} */ ([
|
||||
// candlestick({
|
||||
// metric: priceMetrics.oracle.closeOhlcDollars,
|
||||
// name: "Close",
|
||||
// unit: Unit.usd,
|
||||
// }),
|
||||
// candlestick({
|
||||
// metric: priceMetrics.oracle.midOhlcDollars,
|
||||
// name: "Mid",
|
||||
// unit: Unit.usd,
|
||||
// }),
|
||||
// line({
|
||||
// metric: priceMetrics.oracle.phaseDailyDollars.median,
|
||||
// name: "o. p50",
|
||||
// unit: Unit.usd,
|
||||
// color: colors.yellow,
|
||||
// }),
|
||||
// line({
|
||||
// metric: priceMetrics.oracle.phaseV2DailyDollars.median,
|
||||
// name: "o2. p50",
|
||||
// unit: Unit.usd,
|
||||
// color: colors.orange,
|
||||
// }),
|
||||
// line({
|
||||
// metric: priceMetrics.oracle.phaseV2PeakDailyDollars.median,
|
||||
// name: "o2.2 p50",
|
||||
// unit: Unit.usd,
|
||||
// color: colors.orange,
|
||||
// }),
|
||||
// line({
|
||||
// metric: priceMetrics.oracle.phaseV3DailyDollars.median,
|
||||
// name: "o3. p50",
|
||||
// unit: Unit.usd,
|
||||
// color: colors.red,
|
||||
// }),
|
||||
// line({
|
||||
// metric: priceMetrics.oracle.phaseV3PeakDailyDollars.median,
|
||||
// name: "o3.2 p50",
|
||||
// unit: Unit.usd,
|
||||
// color: colors.red,
|
||||
// }),
|
||||
// line({
|
||||
// metric: priceMetrics.oracle.phaseDailyDollars.max,
|
||||
// name: "o. max",
|
||||
// unit: Unit.usd,
|
||||
// color: colors.lime,
|
||||
// }),
|
||||
// line({
|
||||
// metric: priceMetrics.oracle.phaseV2DailyDollars.max,
|
||||
// name: "o.2 max",
|
||||
// unit: Unit.usd,
|
||||
// color: colors.emerald,
|
||||
// }),
|
||||
// line({
|
||||
// metric: priceMetrics.oracle.phaseDailyDollars.min,
|
||||
// name: "o. min",
|
||||
// unit: Unit.usd,
|
||||
// color: colors.rose,
|
||||
// }),
|
||||
// line({
|
||||
// metric: priceMetrics.oracle.phaseV2DailyDollars.min,
|
||||
// name: "o.2 min",
|
||||
// unit: Unit.usd,
|
||||
// color: colors.purple,
|
||||
// }),
|
||||
// ]),
|
||||
// },
|
||||
// ])
|
||||
// : []),
|
||||
|
||||
// Capitalization
|
||||
{
|
||||
name: "Capitalization",
|
||||
title: "Market Cap",
|
||||
bottom: [
|
||||
line({
|
||||
metric: supply.marketCap,
|
||||
name: "Capitalization",
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
},
|
||||
|
||||
// All Time High
|
||||
{
|
||||
name: "All Time High",
|
||||
title: "All Time High",
|
||||
top: [price({ metric: ath.priceAth, name: "ATH" })],
|
||||
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,
|
||||
}),
|
||||
],
|
||||
},
|
||||
|
||||
// Moving Averages
|
||||
createAveragesSection(ctx, movingAverage),
|
||||
|
||||
// Returns
|
||||
createReturnsSection(ctx, returns),
|
||||
|
||||
// Volatility
|
||||
createVolatilitySection(ctx, { volatility, range }),
|
||||
|
||||
// Momentum
|
||||
createMomentumSection(ctx, indicators),
|
||||
|
||||
// Bands
|
||||
createBandsSection(ctx, { range, movingAverage }),
|
||||
|
||||
// Valuation
|
||||
createValuationSection(ctx, { indicators, movingAverage }),
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
/** Momentum indicators (RSI, StochRSI, Stochastic, MACD) */
|
||||
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { priceLine, priceLines } from "../constants.js";
|
||||
import { line, histogram } from "../series.js";
|
||||
|
||||
/**
|
||||
* Create Momentum section
|
||||
* @param {PartialContext} ctx
|
||||
* @param {Market["indicators"]} indicators
|
||||
*/
|
||||
export function createMomentumSection(ctx, indicators) {
|
||||
const { colors } = ctx;
|
||||
|
||||
return {
|
||||
name: "Momentum",
|
||||
tree: [
|
||||
{
|
||||
name: "RSI",
|
||||
title: "RSI (14d)",
|
||||
bottom: [
|
||||
line({
|
||||
metric: indicators.rsi14d,
|
||||
name: "RSI",
|
||||
color: colors.indigo,
|
||||
unit: Unit.index,
|
||||
}),
|
||||
line({
|
||||
metric: indicators.rsi14dMin,
|
||||
name: "Min",
|
||||
color: colors.red,
|
||||
defaultActive: false,
|
||||
unit: Unit.index,
|
||||
}),
|
||||
line({
|
||||
metric: indicators.rsi14dMax,
|
||||
name: "Max",
|
||||
color: colors.green,
|
||||
defaultActive: false,
|
||||
unit: Unit.index,
|
||||
}),
|
||||
priceLine({ ctx, unit: Unit.index, number: 70 }),
|
||||
priceLine({
|
||||
ctx,
|
||||
unit: Unit.index,
|
||||
number: 50,
|
||||
defaultActive: false,
|
||||
}),
|
||||
priceLine({ ctx, unit: Unit.index, number: 30 }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "StochRSI",
|
||||
title: "Stochastic RSI",
|
||||
bottom: [
|
||||
// line({
|
||||
// metric: indicators.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,
|
||||
}),
|
||||
...priceLines({ ctx, unit: Unit.index, numbers: [80, 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 }),
|
||||
// priceLines({ ctx, unit: Unit.index, numbers: [80, 20] }),
|
||||
// ],
|
||||
// },
|
||||
{
|
||||
name: "MACD",
|
||||
title: "MACD",
|
||||
bottom: [
|
||||
line({
|
||||
metric: indicators.macdLine,
|
||||
name: "MACD",
|
||||
color: colors.blue,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: indicators.macdSignal,
|
||||
name: "Signal",
|
||||
color: colors.orange,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
histogram({
|
||||
metric: indicators.macdHistogram,
|
||||
name: "Histogram",
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
/** On-chain indicators (Pi Cycle, Puell, NVT, Gini) */
|
||||
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { baseline, line, price } from "../series.js";
|
||||
|
||||
/**
|
||||
* Create Valuation section
|
||||
* @param {PartialContext} ctx
|
||||
* @param {Object} args
|
||||
* @param {Market["indicators"]} args.indicators
|
||||
* @param {Market["movingAverage"]} args.movingAverage
|
||||
*/
|
||||
export function createValuationSection(ctx, { indicators, movingAverage }) {
|
||||
const { colors } = ctx;
|
||||
|
||||
return {
|
||||
name: "Valuation",
|
||||
tree: [
|
||||
{
|
||||
name: "Pi Cycle",
|
||||
title: "Pi Cycle",
|
||||
top: [
|
||||
price({
|
||||
metric: movingAverage.price111dSma.price,
|
||||
name: "111d SMA",
|
||||
color: colors.green,
|
||||
}),
|
||||
price({
|
||||
metric: movingAverage.price350dSmaX2,
|
||||
name: "350d SMA x2",
|
||||
color: colors.red,
|
||||
}),
|
||||
],
|
||||
bottom: [
|
||||
baseline({
|
||||
metric: indicators.piCycle,
|
||||
name: "Pi Cycle",
|
||||
unit: Unit.ratio,
|
||||
base: 1,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Puell Multiple",
|
||||
title: "Puell Multiple",
|
||||
bottom: [
|
||||
line({
|
||||
metric: indicators.puellMultiple,
|
||||
name: "Puell",
|
||||
color: colors.green,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "NVT",
|
||||
title: "NVT Ratio",
|
||||
bottom: [
|
||||
line({
|
||||
metric: indicators.nvt,
|
||||
name: "NVT",
|
||||
color: colors.orange,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Gini",
|
||||
title: "Gini Coefficient",
|
||||
bottom: [
|
||||
line({
|
||||
metric: indicators.gini,
|
||||
name: "Gini",
|
||||
color: colors.red,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
/** Performance section */
|
||||
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { priceLine } from "../constants.js";
|
||||
import { baseline } from "../series.js";
|
||||
import { periodIdToName } from "../utils.js";
|
||||
|
||||
/**
|
||||
* Create Returns section
|
||||
* @param {PartialContext} ctx
|
||||
* @param {Market["returns"]} returns
|
||||
*/
|
||||
export function createReturnsSection(ctx, returns) {
|
||||
const { colors } = ctx;
|
||||
|
||||
const shortTermPeriods = /** @type {const} */ ([
|
||||
["1d", "_1d", undefined],
|
||||
["1w", "_1w", undefined],
|
||||
["1m", "_1m", undefined],
|
||||
]);
|
||||
|
||||
const mediumTermPeriods = /** @type {const} */ ([
|
||||
["3m", "_3m", undefined],
|
||||
["6m", "_6m", undefined],
|
||||
["1y", "_1y", undefined],
|
||||
]);
|
||||
|
||||
const longTermPeriods = /** @type {const} */ ([
|
||||
["2y", "_2y", "_2y"],
|
||||
["3y", "_3y", "_3y"],
|
||||
["4y", "_4y", "_4y"],
|
||||
["5y", "_5y", "_5y"],
|
||||
["6y", "_6y", "_6y"],
|
||||
["8y", "_8y", "_8y"],
|
||||
["10y", "_10y", "_10y"],
|
||||
]);
|
||||
|
||||
/**
|
||||
* @template {keyof typeof returns.priceReturns} K
|
||||
* @param {readonly [string, K, K | undefined]} period
|
||||
*/
|
||||
const createPeriodChart = ([id, returnKey, cagrKey]) => {
|
||||
const priceReturns = returns.priceReturns[/** @type {K} */ (returnKey)];
|
||||
const cagr = cagrKey
|
||||
? returns.cagr[/** @type {keyof typeof returns.cagr} */ (cagrKey)]
|
||||
: undefined;
|
||||
const name = periodIdToName(id, true);
|
||||
return {
|
||||
name,
|
||||
title: `${name} Returns`,
|
||||
bottom: [
|
||||
baseline({
|
||||
metric: priceReturns,
|
||||
name: "Total",
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
...(cagr
|
||||
? [
|
||||
baseline({
|
||||
metric: cagr,
|
||||
name: "CAGR",
|
||||
color: [colors.cyan, colors.orange],
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
name: "Returns",
|
||||
tree: [
|
||||
// Compare all periods
|
||||
{
|
||||
name: "Compare",
|
||||
title: "Returns Comparison",
|
||||
bottom: [
|
||||
baseline({
|
||||
metric: returns.priceReturns._1d,
|
||||
name: "1d",
|
||||
color: colors.red,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
baseline({
|
||||
metric: returns.priceReturns._1w,
|
||||
name: "1w",
|
||||
color: colors.orange,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
baseline({
|
||||
metric: returns.priceReturns._1m,
|
||||
name: "1m",
|
||||
color: colors.yellow,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
baseline({
|
||||
metric: returns.priceReturns._3m,
|
||||
name: "3m",
|
||||
color: colors.lime,
|
||||
unit: Unit.percentage,
|
||||
defaultActive: false,
|
||||
}),
|
||||
baseline({
|
||||
metric: returns.priceReturns._6m,
|
||||
name: "6m",
|
||||
color: colors.green,
|
||||
unit: Unit.percentage,
|
||||
defaultActive: false,
|
||||
}),
|
||||
baseline({
|
||||
metric: returns.priceReturns._1y,
|
||||
name: "1y",
|
||||
color: colors.teal,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
baseline({
|
||||
metric: returns.priceReturns._4y,
|
||||
name: "4y",
|
||||
color: colors.blue,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
],
|
||||
},
|
||||
// Short-term (1d, 1w, 1m)
|
||||
{
|
||||
name: "Short-term",
|
||||
tree: shortTermPeriods.map(createPeriodChart),
|
||||
},
|
||||
// Medium-term (3m, 6m, 1y)
|
||||
{
|
||||
name: "Medium-term",
|
||||
tree: mediumTermPeriods.map(createPeriodChart),
|
||||
},
|
||||
// Long-term (2y+)
|
||||
{
|
||||
name: "Long-term",
|
||||
tree: longTermPeriods.map(createPeriodChart),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
/** Volatility indicators (Index, True Range, Choppiness, Sharpe, Sortino) */
|
||||
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { priceLine, priceLines } from "../constants.js";
|
||||
import { line } from "../series.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 { colors } = ctx;
|
||||
|
||||
return {
|
||||
name: "Volatility",
|
||||
tree: [
|
||||
{
|
||||
name: "Index",
|
||||
title: "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: "True Range",
|
||||
bottom: [
|
||||
line({
|
||||
metric: range.priceTrueRange,
|
||||
name: "Value",
|
||||
color: colors.yellow,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Choppiness",
|
||||
title: "Choppiness Index",
|
||||
bottom: [
|
||||
line({
|
||||
metric: range.price2wChoppinessIndex,
|
||||
name: "2w",
|
||||
color: colors.red,
|
||||
unit: Unit.index,
|
||||
}),
|
||||
...priceLines({ ctx, unit: Unit.index, numbers: [61.8, 38.2] }),
|
||||
],
|
||||
},
|
||||
{
|
||||
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,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
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,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -618,7 +618,6 @@ export function createNetworkSection(ctx) {
|
||||
pattern: transactions.volume.receivedSum,
|
||||
name: "Received",
|
||||
color: colors.cyan,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
@@ -794,7 +793,6 @@ export function createNetworkSection(ctx) {
|
||||
...fromBaseStatsPattern({
|
||||
pattern: blocks.interval,
|
||||
unit: Unit.secs,
|
||||
avgActive: false,
|
||||
}),
|
||||
priceLine({ ctx, unit: Unit.secs, name: "Target", number: 600 }),
|
||||
],
|
||||
@@ -1003,8 +1001,8 @@ export function createNetworkSection(ctx) {
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Throughput",
|
||||
title: "Throughput",
|
||||
name: "Activity Rate",
|
||||
title: "Activity Rate",
|
||||
bottom: [
|
||||
dots({
|
||||
metric: transactions.volume.txPerSec,
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
createCohortFolderAddress,
|
||||
createAddressCohortFolder,
|
||||
} from "./distribution/index.js";
|
||||
import { createMarketSection } from "./market/index.js";
|
||||
import { createMarketSection } from "./market.js";
|
||||
import { createNetworkSection } from "./network.js";
|
||||
import { createMiningSection } from "./mining.js";
|
||||
import { createCointimeSection } from "./cointime.js";
|
||||
|
||||
@@ -401,16 +401,17 @@ export function createZScoresFolder(
|
||||
* @param {string} [args.ratioName] - Optional custom name for ratio chart (default: "ratio")
|
||||
* @param {string} [args.priceTitle] - Optional override for price chart title (default: context)
|
||||
* @param {string} [args.zScoresSuffix] - Optional suffix appended to context for z-scores (e.g., "MVRV" gives "2y Z-Score: STH MVRV")
|
||||
* @param {FetchedPriceSeriesBlueprint[]} [args.priceReferences] - Optional additional price series to show in Price chart
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
export function createPriceRatioCharts(ctx, { context, legend, pricePattern, ratio, color, ratioName, priceTitle, zScoresSuffix }) {
|
||||
export function createPriceRatioCharts(ctx, { context, legend, pricePattern, ratio, color, ratioName, priceTitle, zScoresSuffix, priceReferences }) {
|
||||
const titleFn = formatCohortTitle(context);
|
||||
const zScoresTitleFn = zScoresSuffix ? formatCohortTitle(`${context} ${zScoresSuffix}`) : titleFn;
|
||||
return [
|
||||
{
|
||||
name: "Price",
|
||||
title: priceTitle ?? context,
|
||||
top: [price({ metric: pricePattern, name: legend, color })],
|
||||
top: [price({ metric: pricePattern, name: legend, color }), ...(priceReferences ?? [])],
|
||||
},
|
||||
createRatioChart(ctx, {
|
||||
title: titleFn,
|
||||
|
||||
@@ -28,28 +28,29 @@ function walk(node, map, path) {
|
||||
for (const [key, value] of Object.entries(node)) {
|
||||
const kn = key.toLowerCase();
|
||||
if (
|
||||
kn === "mvrv" ||
|
||||
kn === "time" ||
|
||||
kn === "height" ||
|
||||
kn === "constants" ||
|
||||
// kn === "mvrv" ||
|
||||
// kn === "time" ||
|
||||
// kn === "height" ||
|
||||
// kn === "constants" ||
|
||||
kn === "blockhash" ||
|
||||
kn === "oracle" ||
|
||||
kn === "split" ||
|
||||
kn === "ohlc" ||
|
||||
kn === "outpoint" ||
|
||||
kn === "date" ||
|
||||
// kn === "oracle" ||
|
||||
// kn === "split" ||
|
||||
// kn === "ohlc" ||
|
||||
// kn === "outpoint" ||
|
||||
kn === "positions" ||
|
||||
kn === "outputtype" ||
|
||||
// kn === "outputtype" ||
|
||||
kn === "heighttopool" ||
|
||||
kn === "txid" ||
|
||||
kn.startsWith("satblocks") ||
|
||||
kn.startsWith("satdays") ||
|
||||
kn.endsWith("state") ||
|
||||
kn.endsWith("cents") ||
|
||||
// kn === "txid" ||
|
||||
kn.startsWith("timestamp") ||
|
||||
// kn.startsWith("satdays") ||
|
||||
// kn.endsWith("state") ||
|
||||
// kn.endsWith("cents") ||
|
||||
kn.endsWith("index") ||
|
||||
kn.endsWith("indexes") ||
|
||||
kn.endsWith("raw") ||
|
||||
kn.endsWith("bytes") ||
|
||||
(kn.startsWith("_") && kn.endsWith("start"))
|
||||
kn.endsWith("indexes")
|
||||
// kn.endsWith("raw") ||
|
||||
// kn.endsWith("bytes") ||
|
||||
// (kn.startsWith("_") && kn.endsWith("start"))
|
||||
)
|
||||
continue;
|
||||
walk(/** @type {TreeNode | null | undefined} */ (value), map, [
|
||||
|
||||
Reference in New Issue
Block a user