mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-24 06:39:58 -07:00
global: sats version of all prices
This commit is contained in:
@@ -5,6 +5,36 @@ import { priceLine } from "./constants.js";
|
||||
import { line, baseline, dots } from "./series.js";
|
||||
import { satsBtcUsd } from "./shared.js";
|
||||
|
||||
/** Major pools to show in Compare section (by current hashrate dominance) */
|
||||
const MAJOR_POOL_IDS = [
|
||||
"foundryusa", // ~32% - largest pool
|
||||
"antpool", // ~18% - Bitmain-owned
|
||||
"viabtc", // ~14% - independent
|
||||
"f2pool", // ~10% - one of the oldest pools
|
||||
"marapool", // MARA Holdings
|
||||
"braiinspool", // formerly Slush Pool
|
||||
"spiderpool", // growing Asian pool
|
||||
"ocean", // decentralization-focused
|
||||
];
|
||||
|
||||
/**
|
||||
* AntPool & friends - pools sharing AntPool's block templates
|
||||
* Based on b10c's research: https://b10c.me/blog/015-bitcoin-mining-centralization/
|
||||
* Collectively ~35-40% of network hashrate
|
||||
*/
|
||||
const ANTPOOL_AND_FRIENDS_IDS = [
|
||||
"antpool", // Bitmain-owned, template source
|
||||
"poolin", // shares AntPool templates
|
||||
"btccom", // CloverPool (formerly BTC.com)
|
||||
"braiinspool", // shares AntPool templates
|
||||
"ultimuspool", // shares AntPool templates
|
||||
"binancepool", // shares AntPool templates
|
||||
"secpool", // shares AntPool templates
|
||||
"sigmapoolcom", // SigmaPool
|
||||
"rawpool", // shares AntPool templates
|
||||
"luxor", // shares AntPool templates
|
||||
];
|
||||
|
||||
/**
|
||||
* Create Chain section
|
||||
* @param {PartialContext} ctx
|
||||
@@ -77,7 +107,7 @@ export function createChainSection(ctx) {
|
||||
}),
|
||||
line({
|
||||
metric: pool.dominance,
|
||||
name: "all time",
|
||||
name: "All Time",
|
||||
color: colors.teal,
|
||||
unit: Unit.percentage,
|
||||
defaultActive: false,
|
||||
@@ -233,7 +263,7 @@ export function createChainSection(ctx) {
|
||||
...fromSizePattern(blocks.size, Unit.bytes),
|
||||
line({
|
||||
metric: blocks.totalSize,
|
||||
name: "total",
|
||||
name: "Total",
|
||||
color: colors.purple,
|
||||
unit: Unit.bytes,
|
||||
defaultActive: false,
|
||||
@@ -242,14 +272,14 @@ export function createChainSection(ctx) {
|
||||
...fromFullnessPattern(blocks.weight, Unit.wu),
|
||||
line({
|
||||
metric: blocks.weight.sum,
|
||||
name: "sum",
|
||||
name: "Sum",
|
||||
color: colors.stat.sum,
|
||||
unit: Unit.wu,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.weight.cumulative,
|
||||
name: "cumulative",
|
||||
name: "Cumulative",
|
||||
color: colors.stat.cumulative,
|
||||
unit: Unit.wu,
|
||||
defaultActive: false,
|
||||
@@ -297,27 +327,7 @@ export function createChainSection(ctx) {
|
||||
defaultActive: false,
|
||||
},
|
||||
),
|
||||
line({
|
||||
metric: transactions.volume.annualizedVolume.bitcoin,
|
||||
name: "annualized",
|
||||
color: colors.red,
|
||||
unit: Unit.btc,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: transactions.volume.annualizedVolume.sats,
|
||||
name: "annualized",
|
||||
color: colors.red,
|
||||
unit: Unit.sats,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: transactions.volume.annualizedVolume.dollars,
|
||||
name: "annualized",
|
||||
color: colors.lime,
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
...satsBtcUsd(transactions.volume.annualizedVolume, "Annualized", colors.red, { defaultActive: false }),
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -366,12 +376,12 @@ export function createChainSection(ctx) {
|
||||
bottom: [
|
||||
line({
|
||||
metric: supply.velocity.btc,
|
||||
name: "bitcoin",
|
||||
name: "Bitcoin",
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
line({
|
||||
metric: supply.velocity.usd,
|
||||
name: "dollars",
|
||||
name: "Dollars",
|
||||
color: colors.emerald,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
@@ -489,18 +499,18 @@ export function createChainSection(ctx) {
|
||||
name: "SegWit",
|
||||
title: "SegWit Adoption",
|
||||
bottom: [
|
||||
line({ metric: scripts.count.segwitAdoption.base, name: "base", unit: Unit.percentage }),
|
||||
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, unit: Unit.percentage, defaultActive: false }),
|
||||
line({ metric: scripts.count.segwitAdoption.base, name: "Base", unit: Unit.percentage }),
|
||||
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, unit: Unit.percentage, defaultActive: false }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Taproot",
|
||||
title: "Taproot Adoption",
|
||||
bottom: [
|
||||
line({ metric: scripts.count.taprootAdoption.base, name: "base", unit: Unit.percentage }),
|
||||
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, unit: Unit.percentage, defaultActive: false }),
|
||||
line({ metric: scripts.count.taprootAdoption.base, name: "Base", unit: Unit.percentage }),
|
||||
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, unit: Unit.percentage, defaultActive: false }),
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -750,6 +760,7 @@ export function createChainSection(ctx) {
|
||||
{
|
||||
name: "Mining",
|
||||
tree: [
|
||||
// Hashrate
|
||||
{
|
||||
name: "Hashrate",
|
||||
title: "Network Hashrate",
|
||||
@@ -796,123 +807,145 @@ export function createChainSection(ctx) {
|
||||
}),
|
||||
],
|
||||
},
|
||||
|
||||
// Difficulty group
|
||||
{
|
||||
name: "Difficulty",
|
||||
title: "Network Difficulty",
|
||||
bottom: [
|
||||
line({
|
||||
metric: blocks.difficulty.raw,
|
||||
name: "Difficulty",
|
||||
unit: Unit.difficulty,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.difficulty.epoch,
|
||||
name: "Epoch",
|
||||
color: colors.teal,
|
||||
unit: Unit.epoch,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.difficulty.blocksBeforeNextAdjustment,
|
||||
name: "before next",
|
||||
color: colors.indigo,
|
||||
unit: Unit.blocks,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.difficulty.daysBeforeNextAdjustment,
|
||||
name: "before next",
|
||||
color: colors.purple,
|
||||
unit: Unit.days,
|
||||
}),
|
||||
tree: [
|
||||
{
|
||||
name: "Level",
|
||||
title: "Network Difficulty",
|
||||
bottom: [
|
||||
line({
|
||||
metric: blocks.difficulty.raw,
|
||||
name: "Difficulty",
|
||||
unit: Unit.difficulty,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.difficulty.epoch,
|
||||
name: "Epoch",
|
||||
color: colors.teal,
|
||||
unit: Unit.epoch,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Adjustment",
|
||||
title: "Difficulty Adjustment",
|
||||
bottom: [
|
||||
baseline({
|
||||
metric: blocks.difficulty.adjustment,
|
||||
name: "Difficulty Change",
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
priceLine({ ctx, number: 0, unit: Unit.percentage }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Countdown",
|
||||
title: "Next Adjustment",
|
||||
bottom: [
|
||||
line({
|
||||
metric: blocks.difficulty.blocksBeforeNextAdjustment,
|
||||
name: "Before Next",
|
||||
color: colors.indigo,
|
||||
unit: Unit.blocks,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.difficulty.daysBeforeNextAdjustment,
|
||||
name: "Before Next",
|
||||
color: colors.purple,
|
||||
unit: Unit.days,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// Economics group
|
||||
{
|
||||
name: "Adjustment",
|
||||
title: "Difficulty Adjustment",
|
||||
bottom: [
|
||||
baseline({
|
||||
metric: blocks.difficulty.adjustment,
|
||||
name: "Difficulty Change",
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
priceLine({ ctx, number: 0, unit: Unit.percentage }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Hash Price",
|
||||
title: "Hash Price",
|
||||
bottom: [
|
||||
line({
|
||||
metric: blocks.mining.hashPriceThs,
|
||||
name: "TH/s",
|
||||
color: colors.emerald,
|
||||
unit: Unit.usdPerThsPerDay,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.mining.hashPricePhs,
|
||||
name: "PH/s",
|
||||
color: colors.emerald,
|
||||
unit: Unit.usdPerPhsPerDay,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.mining.hashPriceRebound,
|
||||
name: "Rebound",
|
||||
color: colors.yellow,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.mining.hashPriceThsMin,
|
||||
name: "TH/s Min",
|
||||
color: colors.red,
|
||||
unit: Unit.usdPerThsPerDay,
|
||||
options: { lineStyle: 1 },
|
||||
}),
|
||||
line({
|
||||
metric: blocks.mining.hashPricePhsMin,
|
||||
name: "PH/s Min",
|
||||
color: colors.red,
|
||||
unit: Unit.usdPerPhsPerDay,
|
||||
options: { lineStyle: 1 },
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Hash Value",
|
||||
title: "Hash Value",
|
||||
bottom: [
|
||||
line({
|
||||
metric: blocks.mining.hashValueThs,
|
||||
name: "TH/s",
|
||||
color: colors.orange,
|
||||
unit: Unit.satsPerThsPerDay,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.mining.hashValuePhs,
|
||||
name: "PH/s",
|
||||
color: colors.orange,
|
||||
unit: Unit.satsPerPhsPerDay,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.mining.hashValueRebound,
|
||||
name: "Rebound",
|
||||
color: colors.yellow,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.mining.hashValueThsMin,
|
||||
name: "TH/s Min",
|
||||
color: colors.red,
|
||||
unit: Unit.satsPerThsPerDay,
|
||||
options: { lineStyle: 1 },
|
||||
}),
|
||||
line({
|
||||
metric: blocks.mining.hashValuePhsMin,
|
||||
name: "PH/s Min",
|
||||
color: colors.red,
|
||||
unit: Unit.satsPerPhsPerDay,
|
||||
options: { lineStyle: 1 },
|
||||
}),
|
||||
name: "Economics",
|
||||
tree: [
|
||||
{
|
||||
name: "Hash Price",
|
||||
title: "Hash Price",
|
||||
bottom: [
|
||||
line({
|
||||
metric: blocks.mining.hashPriceThs,
|
||||
name: "TH/s",
|
||||
color: colors.emerald,
|
||||
unit: Unit.usdPerThsPerDay,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.mining.hashPricePhs,
|
||||
name: "PH/s",
|
||||
color: colors.emerald,
|
||||
unit: Unit.usdPerPhsPerDay,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.mining.hashPriceRebound,
|
||||
name: "Rebound",
|
||||
color: colors.yellow,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.mining.hashPriceThsMin,
|
||||
name: "TH/s Min",
|
||||
color: colors.red,
|
||||
unit: Unit.usdPerThsPerDay,
|
||||
options: { lineStyle: 1 },
|
||||
}),
|
||||
line({
|
||||
metric: blocks.mining.hashPricePhsMin,
|
||||
name: "PH/s Min",
|
||||
color: colors.red,
|
||||
unit: Unit.usdPerPhsPerDay,
|
||||
options: { lineStyle: 1 },
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Hash Value",
|
||||
title: "Hash Value",
|
||||
bottom: [
|
||||
line({
|
||||
metric: blocks.mining.hashValueThs,
|
||||
name: "TH/s",
|
||||
color: colors.orange,
|
||||
unit: Unit.satsPerThsPerDay,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.mining.hashValuePhs,
|
||||
name: "PH/s",
|
||||
color: colors.orange,
|
||||
unit: Unit.satsPerPhsPerDay,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.mining.hashValueRebound,
|
||||
name: "Rebound",
|
||||
color: colors.yellow,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.mining.hashValueThsMin,
|
||||
name: "TH/s Min",
|
||||
color: colors.red,
|
||||
unit: Unit.satsPerThsPerDay,
|
||||
options: { lineStyle: 1 },
|
||||
}),
|
||||
line({
|
||||
metric: blocks.mining.hashValuePhsMin,
|
||||
name: "PH/s Min",
|
||||
color: colors.red,
|
||||
unit: Unit.satsPerPhsPerDay,
|
||||
options: { lineStyle: 1 },
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// Halving (at top level for quick access)
|
||||
{
|
||||
name: "Halving",
|
||||
title: "Halving",
|
||||
@@ -925,12 +958,12 @@ export function createChainSection(ctx) {
|
||||
}),
|
||||
line({
|
||||
metric: blocks.halving.blocksBeforeNextHalving,
|
||||
name: "before next",
|
||||
name: "Before Next",
|
||||
unit: Unit.blocks,
|
||||
}),
|
||||
line({
|
||||
metric: blocks.halving.daysBeforeNextHalving,
|
||||
name: "before next",
|
||||
name: "Before Next",
|
||||
color: colors.blue,
|
||||
unit: Unit.days,
|
||||
}),
|
||||
@@ -942,7 +975,90 @@ export function createChainSection(ctx) {
|
||||
// Pools
|
||||
{
|
||||
name: "Pools",
|
||||
tree: poolsTree,
|
||||
tree: [
|
||||
// Compare section (major pools only)
|
||||
{
|
||||
name: "Compare",
|
||||
tree: [
|
||||
{
|
||||
name: "Dominance",
|
||||
title: "Pool Dominance (Major Pools)",
|
||||
bottom: poolEntries
|
||||
.filter(([key]) => MAJOR_POOL_IDS.includes(key.toLowerCase()))
|
||||
.map(([key, pool]) => {
|
||||
const poolName =
|
||||
brk.POOL_ID_TO_POOL_NAME[
|
||||
/** @type {keyof typeof brk.POOL_ID_TO_POOL_NAME} */ (key.toLowerCase())
|
||||
] || key;
|
||||
return line({
|
||||
metric: pool._1mDominance,
|
||||
name: poolName,
|
||||
unit: Unit.percentage,
|
||||
});
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Blocks Mined",
|
||||
title: "Blocks Mined - 1m (Major Pools)",
|
||||
bottom: poolEntries
|
||||
.filter(([key]) => MAJOR_POOL_IDS.includes(key.toLowerCase()))
|
||||
.map(([key, pool]) => {
|
||||
const poolName =
|
||||
brk.POOL_ID_TO_POOL_NAME[
|
||||
/** @type {keyof typeof brk.POOL_ID_TO_POOL_NAME} */ (key.toLowerCase())
|
||||
] || key;
|
||||
return line({
|
||||
metric: pool._1mBlocksMined,
|
||||
name: poolName,
|
||||
unit: Unit.count,
|
||||
});
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
// AntPool & friends - pools sharing block templates
|
||||
{
|
||||
name: "AntPool & Friends",
|
||||
tree: [
|
||||
{
|
||||
name: "Dominance",
|
||||
title: "AntPool & Friends Dominance",
|
||||
bottom: poolEntries
|
||||
.filter(([key]) => ANTPOOL_AND_FRIENDS_IDS.includes(key.toLowerCase()))
|
||||
.map(([key, pool]) => {
|
||||
const poolName =
|
||||
brk.POOL_ID_TO_POOL_NAME[
|
||||
/** @type {keyof typeof brk.POOL_ID_TO_POOL_NAME} */ (key.toLowerCase())
|
||||
] || key;
|
||||
return line({
|
||||
metric: pool._1mDominance,
|
||||
name: poolName,
|
||||
unit: Unit.percentage,
|
||||
});
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Blocks Mined",
|
||||
title: "AntPool & Friends Blocks Mined (1m)",
|
||||
bottom: poolEntries
|
||||
.filter(([key]) => ANTPOOL_AND_FRIENDS_IDS.includes(key.toLowerCase()))
|
||||
.map(([key, pool]) => {
|
||||
const poolName =
|
||||
brk.POOL_ID_TO_POOL_NAME[
|
||||
/** @type {keyof typeof brk.POOL_ID_TO_POOL_NAME} */ (key.toLowerCase())
|
||||
] || key;
|
||||
return line({
|
||||
metric: pool._1mBlocksMined,
|
||||
name: poolName,
|
||||
unit: Unit.count,
|
||||
});
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
// Individual pools
|
||||
...poolsTree,
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Unit } from "../utils/units.js";
|
||||
import { line } from "./series.js";
|
||||
import { line, price } from "./series.js";
|
||||
import {
|
||||
satsBtcUsd,
|
||||
createRatioChart,
|
||||
@@ -13,23 +13,23 @@ import {
|
||||
* @param {Object} args
|
||||
* @param {string} args.title
|
||||
* @param {string} args.legend
|
||||
* @param {AnyMetricPattern} args.price
|
||||
* @param {AnyPricePattern} args.pricePattern
|
||||
* @param {ActivePriceRatioPattern} args.ratio
|
||||
* @param {Color} args.color
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function createCointimePriceWithRatioOptions(
|
||||
ctx,
|
||||
{ title, legend, price, ratio, color },
|
||||
{ title, legend, pricePattern, ratio, color },
|
||||
) {
|
||||
return [
|
||||
{
|
||||
name: "Price",
|
||||
title,
|
||||
top: [line({ metric: price, name: legend, color, unit: Unit.usd })],
|
||||
top: [price({ metric: pricePattern, name: legend, color })],
|
||||
},
|
||||
createRatioChart(ctx, { title: formatCohortTitle(title), price, ratio, color }),
|
||||
createZScoresFolder(ctx, { title, legend, price, ratio, color }),
|
||||
createRatioChart(ctx, { title: formatCohortTitle(title), pricePattern, ratio, color }),
|
||||
createZScoresFolder(ctx, { title, legend, pricePattern, ratio, color }),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -55,28 +55,28 @@ export function createCointimeSection(ctx) {
|
||||
// Cointime prices data
|
||||
const cointimePrices = [
|
||||
{
|
||||
price: pricing.trueMarketMean,
|
||||
pricePattern: pricing.trueMarketMean,
|
||||
ratio: pricing.trueMarketMeanRatio,
|
||||
name: "True market mean",
|
||||
name: "True Market Mean",
|
||||
title: "True Market Mean",
|
||||
color: colors.blue,
|
||||
},
|
||||
{
|
||||
price: pricing.vaultedPrice,
|
||||
pricePattern: pricing.vaultedPrice,
|
||||
ratio: pricing.vaultedPriceRatio,
|
||||
name: "Vaulted",
|
||||
title: "Vaulted Price",
|
||||
color: colors.lime,
|
||||
},
|
||||
{
|
||||
price: pricing.activePrice,
|
||||
pricePattern: pricing.activePrice,
|
||||
ratio: pricing.activePriceRatio,
|
||||
name: "Active",
|
||||
title: "Active Price",
|
||||
color: colors.rose,
|
||||
},
|
||||
{
|
||||
price: pricing.cointimePrice,
|
||||
pricePattern: pricing.cointimePrice,
|
||||
ratio: pricing.cointimePriceRatio,
|
||||
name: "Cointime",
|
||||
title: "Cointime Price",
|
||||
@@ -128,14 +128,14 @@ export function createCointimeSection(ctx) {
|
||||
{
|
||||
name: "Compare",
|
||||
title: "Cointime Prices",
|
||||
top: cointimePrices.map(({ price, name, color }) =>
|
||||
line({ metric: price, name, color, unit: Unit.usd }),
|
||||
top: cointimePrices.map(({ pricePattern, name, color }) =>
|
||||
price({ metric: pricePattern, name, color }),
|
||||
),
|
||||
},
|
||||
...cointimePrices.map(({ price, ratio, name, color, title }) => ({
|
||||
...cointimePrices.map(({ pricePattern, ratio, name, color, title }) => ({
|
||||
name,
|
||||
tree: createCointimePriceWithRatioOptions(ctx, {
|
||||
price,
|
||||
pricePattern,
|
||||
ratio,
|
||||
legend: name,
|
||||
color,
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { priceLine } from "../constants.js";
|
||||
import { line, baseline } from "../series.js";
|
||||
import { line, baseline, price } from "../series.js";
|
||||
import { formatCohortTitle } from "../shared.js";
|
||||
import {
|
||||
createSingleSupplySeries,
|
||||
@@ -24,6 +24,9 @@ import {
|
||||
createGroupedSentDollarsSeries,
|
||||
groupedSupplyRelativeGenerators,
|
||||
createSingleSupplyRelativeOptions,
|
||||
createSingleSellSideRiskSeries,
|
||||
createSingleValueCreatedDestroyedSeries,
|
||||
createSingleSoprSeries,
|
||||
} from "./shared.js";
|
||||
|
||||
/**
|
||||
@@ -51,10 +54,17 @@ export function createAddressCohortFolder(ctx, args) {
|
||||
bottom: createSingleSupplySeries(
|
||||
ctx,
|
||||
/** @type {AddressCohortObject} */ (args),
|
||||
createSingleSupplyRelativeOptions(ctx, /** @type {AddressCohortObject} */ (args)),
|
||||
createSingleSupplyRelativeOptions(
|
||||
ctx,
|
||||
/** @type {AddressCohortObject} */ (args),
|
||||
),
|
||||
),
|
||||
}
|
||||
: createGroupedSupplySection(list, title, groupedSupplyRelativeGenerators),
|
||||
: createGroupedSupplySection(
|
||||
list,
|
||||
title,
|
||||
groupedSupplyRelativeGenerators,
|
||||
),
|
||||
|
||||
// UTXO count
|
||||
{
|
||||
@@ -144,11 +154,10 @@ function createRealizedPriceOptions(args, title) {
|
||||
name: "Price",
|
||||
title: title("Realized Price"),
|
||||
top: [
|
||||
line({
|
||||
price({
|
||||
metric: tree.realized.realizedPrice,
|
||||
name: "Realized",
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
},
|
||||
@@ -177,7 +186,7 @@ function createRealizedCapWithExtras(ctx, list, args, useGroupName) {
|
||||
? [
|
||||
baseline({
|
||||
metric: tree.realized.realizedCap30dDelta,
|
||||
name: "1m Change",
|
||||
name: "30d Change",
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
@@ -295,7 +304,7 @@ function createRealizedPnlSection(ctx, args, title) {
|
||||
}),
|
||||
baseline({
|
||||
metric: realized.netRealizedPnlCumulative30dDelta,
|
||||
name: "Cumulative 1m Change",
|
||||
name: "Cumulative 30d Change",
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
@@ -312,13 +321,13 @@ function createRealizedPnlSection(ctx, args, title) {
|
||||
}),
|
||||
baseline({
|
||||
metric: realized.netRealizedPnlCumulative30dDeltaRelToRealizedCap,
|
||||
name: "Cumulative 1m Change",
|
||||
name: "Cumulative 30d Change",
|
||||
unit: Unit.pctRcap,
|
||||
defaultActive: false,
|
||||
}),
|
||||
baseline({
|
||||
metric: realized.netRealizedPnlCumulative30dDeltaRelToMarketCap,
|
||||
name: "Cumulative 1m Change",
|
||||
name: "Cumulative 30d Change",
|
||||
unit: Unit.pctMcap,
|
||||
}),
|
||||
priceLine({
|
||||
@@ -340,28 +349,7 @@ function createRealizedPnlSection(ctx, args, title) {
|
||||
name: "SOPR",
|
||||
title: title("SOPR"),
|
||||
bottom: [
|
||||
baseline({
|
||||
metric: realized.sopr,
|
||||
name: "SOPR",
|
||||
unit: Unit.ratio,
|
||||
base: 1,
|
||||
}),
|
||||
baseline({
|
||||
metric: realized.sopr7dEma,
|
||||
name: "7d EMA",
|
||||
color: [colors.lime, colors.rose],
|
||||
unit: Unit.ratio,
|
||||
defaultActive: false,
|
||||
base: 1,
|
||||
}),
|
||||
baseline({
|
||||
metric: realized.sopr30dEma,
|
||||
name: "30d EMA",
|
||||
color: [colors.avocado, colors.pink],
|
||||
unit: Unit.ratio,
|
||||
defaultActive: false,
|
||||
base: 1,
|
||||
}),
|
||||
...createSingleSoprSeries(colors, args.tree),
|
||||
priceLine({
|
||||
ctx,
|
||||
unit: Unit.ratio,
|
||||
@@ -372,46 +360,12 @@ function createRealizedPnlSection(ctx, args, title) {
|
||||
{
|
||||
name: "Sell Side Risk",
|
||||
title: title("Sell Side Risk Ratio"),
|
||||
bottom: [
|
||||
line({
|
||||
metric: realized.sellSideRiskRatio,
|
||||
name: "Raw",
|
||||
color: colors.orange,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
line({
|
||||
metric: realized.sellSideRiskRatio7dEma,
|
||||
name: "7d EMA",
|
||||
color: colors.red,
|
||||
unit: Unit.ratio,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: realized.sellSideRiskRatio30dEma,
|
||||
name: "30d EMA",
|
||||
color: colors.rose,
|
||||
unit: Unit.ratio,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
bottom: createSingleSellSideRiskSeries(colors, args.tree),
|
||||
},
|
||||
{
|
||||
name: "Value",
|
||||
title: title("Value Created & Destroyed"),
|
||||
bottom: [
|
||||
line({
|
||||
metric: realized.valueCreated,
|
||||
name: "Created",
|
||||
color: colors.emerald,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: realized.valueDestroyed,
|
||||
name: "Destroyed",
|
||||
color: colors.red,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
bottom: createSingleValueCreatedDestroyedSeries(colors, args.tree),
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -582,23 +536,21 @@ function createCostBasisSection(list, useGroupName, title) {
|
||||
name: "Min",
|
||||
title: title("Min Cost Basis"),
|
||||
top: list.map(({ color, name, tree }) =>
|
||||
line({
|
||||
price({
|
||||
metric: tree.costBasis.min,
|
||||
name: useGroupName ? name : "Min",
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "max",
|
||||
name: "Max",
|
||||
title: title("Max Cost Basis"),
|
||||
top: list.map(({ color, name, tree }) =>
|
||||
line({
|
||||
price({
|
||||
metric: tree.costBasis.max,
|
||||
name: useGroupName ? name : "Max",
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
},
|
||||
@@ -645,12 +597,12 @@ function createActivitySection(args, title) {
|
||||
name: "Activity",
|
||||
tree: [
|
||||
{
|
||||
name: "coinblocks destroyed",
|
||||
name: "Coinblocks Destroyed",
|
||||
title: title("Coinblocks Destroyed"),
|
||||
bottom: createGroupedCoinblocksDestroyedSeries(list),
|
||||
},
|
||||
{
|
||||
name: "coindays destroyed",
|
||||
name: "Coindays Destroyed",
|
||||
title: title("Coindays Destroyed"),
|
||||
bottom: createGroupedCoindaysDestroyedSeries(list),
|
||||
},
|
||||
@@ -658,17 +610,17 @@ function createActivitySection(args, title) {
|
||||
name: "Sent",
|
||||
tree: [
|
||||
{
|
||||
name: "sats",
|
||||
name: "Sats",
|
||||
title: title("Sent (Sats)"),
|
||||
bottom: createGroupedSentSatsSeries(list),
|
||||
},
|
||||
{
|
||||
name: "bitcoin",
|
||||
name: "Bitcoin",
|
||||
title: title("Sent (BTC)"),
|
||||
bottom: createGroupedSentBitcoinSeries(list),
|
||||
},
|
||||
{
|
||||
name: "dollars",
|
||||
name: "Dollars",
|
||||
title: title("Sent ($)"),
|
||||
bottom: createGroupedSentDollarsSeries(list),
|
||||
},
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { priceLine } from "../constants.js";
|
||||
import { baseline, line } from "../series.js";
|
||||
import { baseline, dots, line, price } from "../series.js";
|
||||
import { satsBtcUsd } from "../shared.js";
|
||||
|
||||
/**
|
||||
@@ -285,11 +285,11 @@ export function createAddressCountSeries(ctx, list, useGroupName) {
|
||||
/**
|
||||
* Create realized price series for grouped cohorts
|
||||
* @param {readonly CohortObject[]} list
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
* @returns {FetchedPriceSeriesBlueprint[]}
|
||||
*/
|
||||
export function createRealizedPriceSeries(list) {
|
||||
return list.map(({ color, name, tree }) =>
|
||||
line({ metric: tree.realized.realizedPrice, name, color, unit: Unit.usd }),
|
||||
price({ metric: tree.realized.realizedPrice, name, color }),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -332,7 +332,7 @@ export function createRealizedCapSeries(list, useGroupName) {
|
||||
* @param {Colors} colors
|
||||
* @param {readonly CohortWithCostBasisPercentiles[]} list
|
||||
* @param {boolean} useGroupName
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
* @returns {FetchedPriceSeriesBlueprint[]}
|
||||
*/
|
||||
export function createCostBasisPercentilesSeries(colors, list, useGroupName) {
|
||||
return list.flatMap(({ name, tree }) => {
|
||||
@@ -340,27 +340,27 @@ export function createCostBasisPercentilesSeries(colors, list, useGroupName) {
|
||||
const p = cb.percentiles;
|
||||
const n = (/** @type {number} */ pct) => (useGroupName ? `${name} p${pct}` : `p${pct}`);
|
||||
return [
|
||||
line({ metric: cb.max, name: n(100), color: colors.purple, unit: Unit.usd, defaultActive: false }),
|
||||
line({ metric: p.pct95, name: n(95), color: colors.fuchsia, unit: Unit.usd, defaultActive: false }),
|
||||
line({ metric: p.pct90, name: n(90), color: colors.pink, unit: Unit.usd, defaultActive: false }),
|
||||
line({ metric: p.pct85, name: n(85), color: colors.pink, unit: Unit.usd, defaultActive: false }),
|
||||
line({ metric: p.pct80, name: n(80), color: colors.rose, unit: Unit.usd, defaultActive: false }),
|
||||
line({ metric: p.pct75, name: n(75), color: colors.red, unit: Unit.usd, defaultActive: false }),
|
||||
line({ metric: p.pct70, name: n(70), color: colors.orange, unit: Unit.usd, defaultActive: false }),
|
||||
line({ metric: p.pct65, name: n(65), color: colors.amber, unit: Unit.usd, defaultActive: false }),
|
||||
line({ metric: p.pct60, name: n(60), color: colors.yellow, unit: Unit.usd, defaultActive: false }),
|
||||
line({ metric: p.pct55, name: n(55), color: colors.yellow, unit: Unit.usd, defaultActive: false }),
|
||||
line({ metric: p.pct50, name: n(50), color: colors.avocado, unit: Unit.usd }),
|
||||
line({ metric: p.pct45, name: n(45), color: colors.lime, unit: Unit.usd, defaultActive: false }),
|
||||
line({ metric: p.pct40, name: n(40), color: colors.green, unit: Unit.usd, defaultActive: false }),
|
||||
line({ metric: p.pct35, name: n(35), color: colors.emerald, unit: Unit.usd, defaultActive: false }),
|
||||
line({ metric: p.pct30, name: n(30), color: colors.teal, unit: Unit.usd, defaultActive: false }),
|
||||
line({ metric: p.pct25, name: n(25), color: colors.teal, unit: Unit.usd, defaultActive: false }),
|
||||
line({ metric: p.pct20, name: n(20), color: colors.cyan, unit: Unit.usd, defaultActive: false }),
|
||||
line({ metric: p.pct15, name: n(15), color: colors.sky, unit: Unit.usd, defaultActive: false }),
|
||||
line({ metric: p.pct10, name: n(10), color: colors.blue, unit: Unit.usd, defaultActive: false }),
|
||||
line({ metric: p.pct05, name: n(5), color: colors.indigo, unit: Unit.usd, defaultActive: false }),
|
||||
line({ metric: cb.min, name: n(0), color: colors.violet, unit: Unit.usd, defaultActive: false }),
|
||||
price({ metric: cb.max, name: n(100), color: colors.purple, defaultActive: false }),
|
||||
price({ metric: p.pct95, name: n(95), color: colors.fuchsia, defaultActive: false }),
|
||||
price({ metric: p.pct90, name: n(90), color: colors.pink, defaultActive: false }),
|
||||
price({ metric: p.pct85, name: n(85), color: colors.pink, defaultActive: false }),
|
||||
price({ metric: p.pct80, name: n(80), color: colors.rose, defaultActive: false }),
|
||||
price({ metric: p.pct75, name: n(75), color: colors.red, defaultActive: false }),
|
||||
price({ metric: p.pct70, name: n(70), color: colors.orange, defaultActive: false }),
|
||||
price({ metric: p.pct65, name: n(65), color: colors.amber, defaultActive: false }),
|
||||
price({ metric: p.pct60, name: n(60), color: colors.yellow, defaultActive: false }),
|
||||
price({ metric: p.pct55, name: n(55), color: colors.yellow, defaultActive: false }),
|
||||
price({ metric: p.pct50, name: n(50), color: colors.avocado }),
|
||||
price({ metric: p.pct45, name: n(45), color: colors.lime, defaultActive: false }),
|
||||
price({ metric: p.pct40, name: n(40), color: colors.green, defaultActive: false }),
|
||||
price({ metric: p.pct35, name: n(35), color: colors.emerald, defaultActive: false }),
|
||||
price({ metric: p.pct30, name: n(30), color: colors.teal, defaultActive: false }),
|
||||
price({ metric: p.pct25, name: n(25), color: colors.teal, defaultActive: false }),
|
||||
price({ metric: p.pct20, name: n(20), color: colors.cyan, defaultActive: false }),
|
||||
price({ metric: p.pct15, name: n(15), color: colors.sky, defaultActive: false }),
|
||||
price({ metric: p.pct10, name: n(10), color: colors.blue, defaultActive: false }),
|
||||
price({ metric: p.pct05, name: n(5), color: colors.indigo, defaultActive: false }),
|
||||
price({ metric: cb.min, name: n(0), color: colors.violet, defaultActive: false }),
|
||||
];
|
||||
});
|
||||
}
|
||||
@@ -536,3 +536,116 @@ export function createGroupedSentDollarsSeries(list) {
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Sell Side Risk Ratio Helpers
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Create sell side risk ratio series for single cohort
|
||||
* @param {Colors} colors
|
||||
* @param {{ realized: AnyRealizedPattern }} tree
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function createSingleSellSideRiskSeries(colors, tree) {
|
||||
return [
|
||||
dots({
|
||||
metric: tree.realized.sellSideRiskRatio,
|
||||
name: "Raw",
|
||||
color: colors.orange,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.sellSideRiskRatio7dEma,
|
||||
name: "7d EMA",
|
||||
color: colors.red,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.sellSideRiskRatio30dEma,
|
||||
name: "30d EMA",
|
||||
color: colors.pink,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create sell side risk ratio series for grouped cohorts
|
||||
* @param {readonly CohortObject[]} list
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function createGroupedSellSideRiskSeries(list) {
|
||||
return list.flatMap(({ color, name, tree }) => [
|
||||
line({
|
||||
metric: tree.realized.sellSideRiskRatio,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Value Created & Destroyed Helpers
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Create value created & destroyed series for single cohort
|
||||
* @param {Colors} colors
|
||||
* @param {{ realized: AnyRealizedPattern }} tree
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function createSingleValueCreatedDestroyedSeries(colors, tree) {
|
||||
return [
|
||||
line({
|
||||
metric: tree.realized.valueCreated,
|
||||
name: "Created",
|
||||
color: colors.emerald,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.valueDestroyed,
|
||||
name: "Destroyed",
|
||||
color: colors.red,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// SOPR Helpers
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Create base SOPR series for single cohort (all cohorts have base SOPR)
|
||||
* @param {Colors} colors
|
||||
* @param {{ realized: AnyRealizedPattern }} tree
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function createSingleSoprSeries(colors, tree) {
|
||||
return [
|
||||
baseline({
|
||||
metric: tree.realized.sopr,
|
||||
name: "SOPR",
|
||||
unit: Unit.ratio,
|
||||
base: 1,
|
||||
}),
|
||||
baseline({
|
||||
metric: tree.realized.sopr7dEma,
|
||||
name: "7d EMA",
|
||||
color: [colors.lime, colors.rose],
|
||||
unit: Unit.ratio,
|
||||
defaultActive: false,
|
||||
base: 1,
|
||||
}),
|
||||
baseline({
|
||||
metric: tree.realized.sopr30dEma,
|
||||
name: "30d EMA",
|
||||
color: [colors.avocado, colors.pink],
|
||||
unit: Unit.ratio,
|
||||
defaultActive: false,
|
||||
base: 1,
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -32,6 +32,11 @@ import {
|
||||
createCostBasisPercentilesSeries,
|
||||
groupedSupplyRelativeGenerators,
|
||||
createSingleSupplyRelativeOptions,
|
||||
createSingleSellSideRiskSeries,
|
||||
createGroupedSellSideRiskSeries,
|
||||
createSingleValueCreatedDestroyedSeries,
|
||||
createSingleSoprSeries,
|
||||
createSingleCoinsDestroyedSeries,
|
||||
} from "./shared.js";
|
||||
import {
|
||||
createRatioChart,
|
||||
@@ -39,7 +44,7 @@ import {
|
||||
formatCohortTitle,
|
||||
} from "../shared.js";
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { line, baseline } from "../series.js";
|
||||
import { line, baseline, price } from "../series.js";
|
||||
import { priceLine } from "../constants.js";
|
||||
|
||||
// ============================================================================
|
||||
@@ -81,7 +86,11 @@ export function createCohortFolderFull(ctx, args) {
|
||||
return {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createGroupedSupplySection(list, title, groupedSupplyRelativeGenerators),
|
||||
createGroupedSupplySection(
|
||||
list,
|
||||
title,
|
||||
groupedSupplyRelativeGenerators,
|
||||
),
|
||||
createGroupedUtxoCountChart(list, title),
|
||||
createGroupedRealizedSectionWithAdjusted(ctx, list, title, {
|
||||
ratioMetrics: createGroupedRealizedPnlRatioMetrics,
|
||||
@@ -96,7 +105,12 @@ export function createCohortFolderFull(ctx, args) {
|
||||
return {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createSingleSupplyChart(ctx, args, title, createSingleSupplyRelativeOptions(ctx, args)),
|
||||
createSingleSupplyChart(
|
||||
ctx,
|
||||
args,
|
||||
title,
|
||||
createSingleSupplyRelativeOptions(ctx, args),
|
||||
),
|
||||
createSingleUtxoCountChart(args, title),
|
||||
createSingleRealizedSectionFull(ctx, args, title),
|
||||
createSingleUnrealizedSectionFull(ctx, args, title),
|
||||
@@ -119,7 +133,11 @@ export function createCohortFolderWithAdjusted(ctx, args) {
|
||||
return {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createGroupedSupplySection(list, title, groupedSupplyRelativeGenerators),
|
||||
createGroupedSupplySection(
|
||||
list,
|
||||
title,
|
||||
groupedSupplyRelativeGenerators,
|
||||
),
|
||||
createGroupedUtxoCountChart(list, title),
|
||||
createGroupedRealizedSectionWithAdjusted(ctx, list, title),
|
||||
createGroupedUnrealizedSectionWithMarketCap(ctx, list, title),
|
||||
@@ -132,7 +150,12 @@ export function createCohortFolderWithAdjusted(ctx, args) {
|
||||
return {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createSingleSupplyChart(ctx, args, title, createSingleSupplyRelativeOptions(ctx, args)),
|
||||
createSingleSupplyChart(
|
||||
ctx,
|
||||
args,
|
||||
title,
|
||||
createSingleSupplyRelativeOptions(ctx, args),
|
||||
),
|
||||
createSingleUtxoCountChart(args, title),
|
||||
createSingleRealizedSectionWithAdjusted(ctx, args, title),
|
||||
createSingleUnrealizedSectionWithMarketCap(ctx, args, title),
|
||||
@@ -155,7 +178,11 @@ export function createCohortFolderWithNupl(ctx, args) {
|
||||
return {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createGroupedSupplySection(list, title, groupedSupplyRelativeGenerators),
|
||||
createGroupedSupplySection(
|
||||
list,
|
||||
title,
|
||||
groupedSupplyRelativeGenerators,
|
||||
),
|
||||
createGroupedUtxoCountChart(list, title),
|
||||
createGroupedRealizedSectionBasic(ctx, list, title, {
|
||||
ratioMetrics: createGroupedRealizedPnlRatioMetrics,
|
||||
@@ -170,7 +197,12 @@ export function createCohortFolderWithNupl(ctx, args) {
|
||||
return {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createSingleSupplyChart(ctx, args, title, createSingleSupplyRelativeOptions(ctx, args)),
|
||||
createSingleSupplyChart(
|
||||
ctx,
|
||||
args,
|
||||
title,
|
||||
createSingleSupplyRelativeOptions(ctx, args),
|
||||
),
|
||||
createSingleUtxoCountChart(args, title),
|
||||
createSingleRealizedSectionWithPercentiles(ctx, args, title),
|
||||
createSingleUnrealizedSectionWithNupl({ ctx, cohort: args, title }),
|
||||
@@ -180,7 +212,6 @@ export function createCohortFolderWithNupl(ctx, args) {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Age range folder: ageRange.* (no nupl via RelativePattern2)
|
||||
* @param {PartialContext} ctx
|
||||
@@ -232,7 +263,11 @@ export function createCohortFolderBasicWithMarketCap(ctx, args) {
|
||||
return {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createGroupedSupplySection(list, title, groupedSupplyRelativeGenerators),
|
||||
createGroupedSupplySection(
|
||||
list,
|
||||
title,
|
||||
groupedSupplyRelativeGenerators,
|
||||
),
|
||||
createGroupedUtxoCountChart(list, title),
|
||||
createGroupedRealizedSectionBasic(ctx, list, title),
|
||||
createGroupedUnrealizedSectionWithMarketCapOnly(ctx, list, title),
|
||||
@@ -245,7 +280,12 @@ export function createCohortFolderBasicWithMarketCap(ctx, args) {
|
||||
return {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createSingleSupplyChart(ctx, args, title, createSingleSupplyRelativeOptions(ctx, args)),
|
||||
createSingleSupplyChart(
|
||||
ctx,
|
||||
args,
|
||||
title,
|
||||
createSingleSupplyRelativeOptions(ctx, args),
|
||||
),
|
||||
createSingleUtxoCountChart(args, title),
|
||||
createSingleRealizedSectionBasic(ctx, args, title),
|
||||
createSingleUnrealizedSectionWithMarketCapOnly(ctx, args, title),
|
||||
@@ -474,7 +514,12 @@ function createSingleRealizedSectionWithAdjusted(ctx, cohort, title) {
|
||||
* @param {(cohort: T[number]) => AnyFetchedSeriesBlueprint[]} [options.ratioMetrics] - Generator for ratio metrics per cohort
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
function createGroupedRealizedSectionWithAdjusted(ctx, list, title, { ratioMetrics } = {}) {
|
||||
function createGroupedRealizedSectionWithAdjusted(
|
||||
ctx,
|
||||
list,
|
||||
title,
|
||||
{ ratioMetrics } = {},
|
||||
) {
|
||||
return {
|
||||
name: "Realized",
|
||||
tree: [
|
||||
@@ -559,7 +604,12 @@ function createSingleRealizedSectionBasic(ctx, cohort, title) {
|
||||
* @param {(cohort: T[number]) => AnyFetchedSeriesBlueprint[]} [options.ratioMetrics] - Generator for ratio metrics per cohort
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
function createGroupedRealizedSectionBasic(ctx, list, title, { ratioMetrics } = {}) {
|
||||
function createGroupedRealizedSectionBasic(
|
||||
ctx,
|
||||
list,
|
||||
title,
|
||||
{ ratioMetrics } = {},
|
||||
) {
|
||||
return {
|
||||
name: "Realized",
|
||||
tree: [
|
||||
@@ -596,11 +646,10 @@ function createSingleRealizedPriceChart(cohort, title) {
|
||||
name: "Price",
|
||||
title: title("Realized Price"),
|
||||
top: [
|
||||
line({
|
||||
price({
|
||||
metric: tree.realized.realizedPrice,
|
||||
name: "realized",
|
||||
name: "Realized",
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
};
|
||||
@@ -623,7 +672,7 @@ function createSingleRealizedPriceChartsWithRatio(ctx, cohort, title) {
|
||||
createSingleRealizedPriceChart(cohort, title),
|
||||
createRatioChart(ctx, {
|
||||
title,
|
||||
price: tree.realized.realizedPrice,
|
||||
pricePattern: tree.realized.realizedPrice,
|
||||
ratio,
|
||||
color,
|
||||
name: "MVRV",
|
||||
@@ -631,7 +680,7 @@ function createSingleRealizedPriceChartsWithRatio(ctx, cohort, title) {
|
||||
createZScoresFolder(ctx, {
|
||||
title: title("Realized Price"),
|
||||
legend: "price",
|
||||
price: tree.realized.realizedPrice,
|
||||
pricePattern: tree.realized.realizedPrice,
|
||||
ratio,
|
||||
color,
|
||||
}),
|
||||
@@ -693,7 +742,7 @@ function createSingleRealizedCapSeries(ctx, cohort, { extra = [] } = {}) {
|
||||
}),
|
||||
baseline({
|
||||
metric: tree.realized.realizedCap30dDelta,
|
||||
name: "1m Change",
|
||||
name: "30d Change",
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
@@ -712,7 +761,7 @@ function createRealizedCapRatioSeries(ctx, tree) {
|
||||
return [
|
||||
baseline({
|
||||
metric: tree.realized.realizedCapRelToOwnMarketCap,
|
||||
name: "ratio",
|
||||
name: "Ratio",
|
||||
unit: Unit.pctOwnMcap,
|
||||
options: { baseValue: { price: 100 } },
|
||||
}),
|
||||
@@ -751,7 +800,7 @@ function createRealizedPnlRatioSeries(colors, tree) {
|
||||
return [
|
||||
line({
|
||||
metric: tree.realized.realizedProfitToLossRatio,
|
||||
name: "Profit / Loss",
|
||||
name: "P/L Ratio",
|
||||
color: colors.yellow,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
@@ -783,7 +832,12 @@ function createGroupedRealizedPnlRatioMetrics(cohort) {
|
||||
* @param {AnyFetchedSeriesBlueprint[]} [options.extra] - Extra series (e.g., pnl ratio for cohorts with RealizedWithPnlRatio)
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function createSingleRealizedPnlSection(ctx, cohort, title, { extra = [] } = {}) {
|
||||
function createSingleRealizedPnlSection(
|
||||
ctx,
|
||||
cohort,
|
||||
title,
|
||||
{ extra = [] } = {},
|
||||
) {
|
||||
const { colors, fromBlockCountWithUnit, fromBitcoinPatternWithUnit } = ctx;
|
||||
const { tree } = cohort;
|
||||
|
||||
@@ -859,7 +913,7 @@ function createSingleRealizedPnlSection(ctx, cohort, title, { extra = [] } = {})
|
||||
),
|
||||
baseline({
|
||||
metric: tree.realized.netRealizedPnlCumulative30dDelta,
|
||||
name: "Cumulative 1m Change",
|
||||
name: "Cumulative 30d Change",
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
@@ -877,13 +931,13 @@ function createSingleRealizedPnlSection(ctx, cohort, title, { extra = [] } = {})
|
||||
baseline({
|
||||
metric:
|
||||
tree.realized.netRealizedPnlCumulative30dDeltaRelToRealizedCap,
|
||||
name: "Cumulative 1m Change",
|
||||
name: "Cumulative 30d Change",
|
||||
unit: Unit.pctRcap,
|
||||
defaultActive: false,
|
||||
}),
|
||||
baseline({
|
||||
metric: tree.realized.netRealizedPnlCumulative30dDeltaRelToMarketCap,
|
||||
name: "Cumulative 1m Change",
|
||||
name: "Cumulative 30d Change",
|
||||
unit: Unit.pctMcap,
|
||||
}),
|
||||
priceLine({ ctx, unit: Unit.pctMcap }),
|
||||
@@ -904,7 +958,12 @@ function createSingleRealizedPnlSection(ctx, cohort, title, { extra = [] } = {})
|
||||
* @param {(cohort: T[number]) => AnyFetchedSeriesBlueprint[]} [options.ratioMetrics] - Generator for ratio metrics per cohort
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function createGroupedRealizedPnlSections(ctx, list, title, { ratioMetrics } = {}) {
|
||||
function createGroupedRealizedPnlSections(
|
||||
ctx,
|
||||
list,
|
||||
title,
|
||||
{ ratioMetrics } = {},
|
||||
) {
|
||||
return [
|
||||
{
|
||||
name: "Profit",
|
||||
@@ -1028,8 +1087,8 @@ function createGroupedRealizedPnlSections(ctx, list, title, { ratioMetrics } = {
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Net P&L 1m Change",
|
||||
title: title("Net Realized P&L 1m Change"),
|
||||
name: "Net P&L 30d Change",
|
||||
title: title("Net Realized P&L 30d Change"),
|
||||
bottom: [
|
||||
...list.flatMap(({ color, name, tree }) => [
|
||||
baseline({
|
||||
@@ -1076,36 +1135,10 @@ function createGroupedRealizedPnlSections(ctx, list, title, { ratioMetrics } = {
|
||||
* @returns {PartialChartOption}
|
||||
*/
|
||||
function createSingleBaseSoprChart(ctx, cohort, title) {
|
||||
const { colors } = ctx;
|
||||
const { tree } = cohort;
|
||||
|
||||
return {
|
||||
name: "Normal",
|
||||
title: title("SOPR"),
|
||||
bottom: [
|
||||
baseline({
|
||||
metric: tree.realized.sopr,
|
||||
name: "SOPR",
|
||||
unit: Unit.ratio,
|
||||
base: 1,
|
||||
}),
|
||||
baseline({
|
||||
metric: tree.realized.sopr7dEma,
|
||||
name: "7d EMA",
|
||||
color: [colors.lime, colors.rose],
|
||||
unit: Unit.ratio,
|
||||
defaultActive: false,
|
||||
base: 1,
|
||||
}),
|
||||
baseline({
|
||||
metric: tree.realized.sopr30dEma,
|
||||
name: "30d EMA",
|
||||
color: [colors.avocado, colors.pink],
|
||||
unit: Unit.ratio,
|
||||
defaultActive: false,
|
||||
base: 1,
|
||||
}),
|
||||
],
|
||||
bottom: createSingleSoprSeries(ctx.colors, cohort.tree),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1547,7 +1580,14 @@ function createGroupedNuplChart(ctx, list, title) {
|
||||
* @param {PartialChartOption[]} [args.charts] - Extra charts (e.g., nupl)
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
function createUnrealizedSection({ ctx, tree, title, pnl = [], netPnl = [], charts = [] }) {
|
||||
function createUnrealizedSection({
|
||||
ctx,
|
||||
tree,
|
||||
title,
|
||||
pnl = [],
|
||||
netPnl = [],
|
||||
charts = [],
|
||||
}) {
|
||||
return {
|
||||
name: "Unrealized",
|
||||
tree: [
|
||||
@@ -1584,7 +1624,12 @@ function createUnrealizedSection({ ctx, tree, title, pnl = [], netPnl = [], char
|
||||
* @param {PartialChartOption[]} [args.charts] - Extra charts
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
function createGroupedUnrealizedSection({ list, title, netPnlMetrics, charts = [] }) {
|
||||
function createGroupedUnrealizedSection({
|
||||
list,
|
||||
title,
|
||||
netPnlMetrics,
|
||||
charts = [],
|
||||
}) {
|
||||
return {
|
||||
name: "Unrealized",
|
||||
tree: [
|
||||
@@ -1682,7 +1727,6 @@ function createSingleUnrealizedSectionWithMarketCap(ctx, cohort, title) {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Unrealized section WITH RelToMarketCap metrics (for CohortBasicWithMarketCap)
|
||||
* @param {PartialContext} ctx
|
||||
@@ -1774,9 +1818,24 @@ function createGroupedUnrealizedSectionFull(ctx, list, title) {
|
||||
list,
|
||||
title,
|
||||
netPnlMetrics: ({ color, name, tree }) => [
|
||||
baseline({ metric: tree.relative.netUnrealizedPnlRelToMarketCap, name, color, unit: Unit.pctMcap }),
|
||||
baseline({ metric: tree.relative.netUnrealizedPnlRelToOwnMarketCap, name, color, unit: Unit.pctOwnMcap }),
|
||||
baseline({ metric: tree.relative.netUnrealizedPnlRelToOwnTotalUnrealizedPnl, name, color, unit: Unit.pctOwnPnl }),
|
||||
baseline({
|
||||
metric: tree.relative.netUnrealizedPnlRelToMarketCap,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.pctMcap,
|
||||
}),
|
||||
baseline({
|
||||
metric: tree.relative.netUnrealizedPnlRelToOwnMarketCap,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.pctOwnMcap,
|
||||
}),
|
||||
baseline({
|
||||
metric: tree.relative.netUnrealizedPnlRelToOwnTotalUnrealizedPnl,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.pctOwnPnl,
|
||||
}),
|
||||
],
|
||||
charts: [createGroupedNuplChart(ctx, list, title)],
|
||||
});
|
||||
@@ -1793,13 +1852,17 @@ function createGroupedUnrealizedSectionWithMarketCap(ctx, list, title) {
|
||||
list,
|
||||
title,
|
||||
netPnlMetrics: ({ color, name, tree }) => [
|
||||
baseline({ metric: tree.relative.netUnrealizedPnlRelToMarketCap, name, color, unit: Unit.pctMcap }),
|
||||
baseline({
|
||||
metric: tree.relative.netUnrealizedPnlRelToMarketCap,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.pctMcap,
|
||||
}),
|
||||
],
|
||||
charts: [createGroupedNuplChart(ctx, list, title)],
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Grouped unrealized section WITH RelToMarketCap (for CohortBasicWithMarketCap)
|
||||
* @param {PartialContext} ctx
|
||||
@@ -1811,7 +1874,12 @@ function createGroupedUnrealizedSectionWithMarketCapOnly(ctx, list, title) {
|
||||
list,
|
||||
title,
|
||||
netPnlMetrics: ({ color, name, tree }) => [
|
||||
baseline({ metric: tree.relative.netUnrealizedPnlRelToMarketCap, name, color, unit: Unit.pctMcap }),
|
||||
baseline({
|
||||
metric: tree.relative.netUnrealizedPnlRelToMarketCap,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.pctMcap,
|
||||
}),
|
||||
],
|
||||
charts: [createGroupedNuplChart(ctx, list, title)],
|
||||
});
|
||||
@@ -1865,9 +1933,24 @@ function createGroupedUnrealizedSectionWithNupl({ ctx, list, title }) {
|
||||
list,
|
||||
title,
|
||||
netPnlMetrics: ({ color, name, tree }) => [
|
||||
baseline({ metric: tree.relative.netUnrealizedPnlRelToMarketCap, name, color, unit: Unit.pctMcap }),
|
||||
baseline({ metric: tree.relative.netUnrealizedPnlRelToOwnMarketCap, name, color, unit: Unit.pctOwnMcap }),
|
||||
baseline({ metric: tree.relative.netUnrealizedPnlRelToOwnTotalUnrealizedPnl, name, color, unit: Unit.pctOwnPnl }),
|
||||
baseline({
|
||||
metric: tree.relative.netUnrealizedPnlRelToMarketCap,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.pctMcap,
|
||||
}),
|
||||
baseline({
|
||||
metric: tree.relative.netUnrealizedPnlRelToOwnMarketCap,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.pctOwnMcap,
|
||||
}),
|
||||
baseline({
|
||||
metric: tree.relative.netUnrealizedPnlRelToOwnTotalUnrealizedPnl,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.pctOwnPnl,
|
||||
}),
|
||||
],
|
||||
charts: [createGroupedNuplChart(ctx, list, title)],
|
||||
});
|
||||
@@ -1906,8 +1989,18 @@ function createGroupedUnrealizedSectionAgeRange(list, title) {
|
||||
list,
|
||||
title,
|
||||
netPnlMetrics: ({ color, name, tree }) => [
|
||||
baseline({ metric: tree.relative.netUnrealizedPnlRelToOwnMarketCap, name, color, unit: Unit.pctOwnMcap }),
|
||||
baseline({ metric: tree.relative.netUnrealizedPnlRelToOwnTotalUnrealizedPnl, name, color, unit: Unit.pctOwnPnl }),
|
||||
baseline({
|
||||
metric: tree.relative.netUnrealizedPnlRelToOwnMarketCap,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.pctOwnMcap,
|
||||
}),
|
||||
baseline({
|
||||
metric: tree.relative.netUnrealizedPnlRelToOwnTotalUnrealizedPnl,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.pctOwnPnl,
|
||||
}),
|
||||
],
|
||||
});
|
||||
}
|
||||
@@ -1935,24 +2028,21 @@ function createCostBasisSection(ctx, { cohort, title, charts = [] }) {
|
||||
name: "Average",
|
||||
title: title("Cost Basis"),
|
||||
top: [
|
||||
line({
|
||||
price({
|
||||
metric: tree.realized.realizedPrice,
|
||||
name: "Average",
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
price({
|
||||
metric: tree.costBasis.max,
|
||||
name: "Max",
|
||||
color: colors.green,
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
price({
|
||||
metric: tree.costBasis.min,
|
||||
name: "Min",
|
||||
color: colors.red,
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
@@ -1961,11 +2051,10 @@ function createCostBasisSection(ctx, { cohort, title, charts = [] }) {
|
||||
name: "Max",
|
||||
title: title("Max Cost Basis"),
|
||||
top: [
|
||||
line({
|
||||
price({
|
||||
metric: tree.costBasis.max,
|
||||
name: "Max",
|
||||
color: colors.green,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
},
|
||||
@@ -1973,11 +2062,10 @@ function createCostBasisSection(ctx, { cohort, title, charts = [] }) {
|
||||
name: "Min",
|
||||
title: title("Min Cost Basis"),
|
||||
top: [
|
||||
line({
|
||||
price({
|
||||
metric: tree.costBasis.min,
|
||||
name: "Min",
|
||||
color: colors.red,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
},
|
||||
@@ -2003,11 +2091,10 @@ function createGroupedCostBasisSection({ list, title, charts = [] }) {
|
||||
name: "Average",
|
||||
title: title("Average Cost Basis"),
|
||||
top: list.map(({ color, name, tree }) =>
|
||||
line({
|
||||
price({
|
||||
metric: tree.realized.realizedPrice,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
},
|
||||
@@ -2015,14 +2102,14 @@ function createGroupedCostBasisSection({ list, title, charts = [] }) {
|
||||
name: "Max",
|
||||
title: title("Max Cost Basis"),
|
||||
top: list.map(({ color, name, tree }) =>
|
||||
line({ metric: tree.costBasis.max, name, color, unit: Unit.usd }),
|
||||
price({ metric: tree.costBasis.max, name, color }),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Min",
|
||||
title: title("Min Cost Basis"),
|
||||
top: list.map(({ color, name, tree }) =>
|
||||
line({ metric: tree.costBasis.min, name, color, unit: Unit.usd }),
|
||||
price({ metric: tree.costBasis.min, name, color }),
|
||||
),
|
||||
},
|
||||
...charts,
|
||||
@@ -2078,7 +2165,6 @@ function createGroupedCostBasisSectionWithPercentiles(ctx, list, title) {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// ============================================================================
|
||||
// Activity Section Builders (generic, type-safe composition)
|
||||
// ============================================================================
|
||||
@@ -2103,38 +2189,43 @@ function createActivitySection({ ctx, cohort, title, valueMetrics = [] }) {
|
||||
name: "Sent",
|
||||
title: title("Sent"),
|
||||
bottom: [
|
||||
...fromBlockCountWithUnit(tree.activity.sent.sats, Unit.sats, undefined, color),
|
||||
...fromBitcoinPatternWithUnit(tree.activity.sent.bitcoin, Unit.btc, undefined, color),
|
||||
...fromBlockCountWithUnit(tree.activity.sent.dollars, Unit.usd, undefined, color),
|
||||
...fromBlockCountWithUnit(
|
||||
tree.activity.sent.sats,
|
||||
Unit.sats,
|
||||
undefined,
|
||||
color,
|
||||
),
|
||||
...fromBitcoinPatternWithUnit(
|
||||
tree.activity.sent.bitcoin,
|
||||
Unit.btc,
|
||||
undefined,
|
||||
color,
|
||||
),
|
||||
...fromBlockCountWithUnit(
|
||||
tree.activity.sent.dollars,
|
||||
Unit.usd,
|
||||
undefined,
|
||||
color,
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Sell Side Risk",
|
||||
title: title("Sell Side Risk Ratio"),
|
||||
bottom: [
|
||||
line({ metric: tree.realized.sellSideRiskRatio, name: "Raw", color: colors.orange, unit: Unit.ratio }),
|
||||
line({ metric: tree.realized.sellSideRiskRatio7dEma, name: "7d EMA", color: colors.red, unit: Unit.ratio, defaultActive: false }),
|
||||
line({ metric: tree.realized.sellSideRiskRatio30dEma, name: "30d EMA", color: colors.rose, unit: Unit.ratio, defaultActive: false }),
|
||||
],
|
||||
bottom: createSingleSellSideRiskSeries(colors, tree),
|
||||
},
|
||||
{
|
||||
name: "Value",
|
||||
title: title("Value Created & Destroyed"),
|
||||
bottom: [
|
||||
line({ metric: tree.realized.valueCreated, name: "Created", color: colors.emerald, unit: Unit.usd }),
|
||||
line({ metric: tree.realized.valueDestroyed, name: "Destroyed", color: colors.red, unit: Unit.usd }),
|
||||
...createSingleValueCreatedDestroyedSeries(colors, tree),
|
||||
...valueMetrics,
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Coins Destroyed",
|
||||
title: title("Coins Destroyed"),
|
||||
bottom: [
|
||||
line({ metric: tree.activity.coinblocksDestroyed.sum, name: "Coinblocks", color, unit: Unit.coinblocks }),
|
||||
line({ metric: tree.activity.coinblocksDestroyed.cumulative, name: "Cumulative", color, unit: Unit.coinblocks, defaultActive: false }),
|
||||
line({ metric: tree.activity.coindaysDestroyed.sum, name: "Coindays", color, unit: Unit.coindays }),
|
||||
line({ metric: tree.activity.coindaysDestroyed.cumulative, name: "Cumulative", color, unit: Unit.coindays, defaultActive: false }),
|
||||
],
|
||||
bottom: createSingleCoinsDestroyedSeries(cohort),
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -2156,9 +2247,7 @@ function createGroupedActivitySection({ list, title, valueTree }) {
|
||||
{
|
||||
name: "Sell Side Risk",
|
||||
title: title("Sell Side Risk Ratio"),
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
line({ metric: tree.realized.sellSideRiskRatio, name, color, unit: Unit.ratio }),
|
||||
]),
|
||||
bottom: createGroupedSellSideRiskSeries(list),
|
||||
},
|
||||
{
|
||||
name: "Value",
|
||||
@@ -2167,14 +2256,24 @@ function createGroupedActivitySection({ list, title, valueTree }) {
|
||||
name: "Created",
|
||||
title: title("Value Created"),
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
line({ metric: tree.realized.valueCreated, name, color, unit: Unit.usd }),
|
||||
line({
|
||||
metric: tree.realized.valueCreated,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
{
|
||||
name: "Destroyed",
|
||||
title: title("Value Destroyed"),
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
line({ metric: tree.realized.valueDestroyed, name, color, unit: Unit.usd }),
|
||||
line({
|
||||
metric: tree.realized.valueDestroyed,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
],
|
||||
@@ -2186,16 +2285,36 @@ function createGroupedActivitySection({ list, title, valueTree }) {
|
||||
name: "Sum",
|
||||
title: title("Coins Destroyed"),
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
line({ metric: tree.activity.coinblocksDestroyed.sum, name, color, unit: Unit.coinblocks }),
|
||||
line({ metric: tree.activity.coindaysDestroyed.sum, name, color, unit: Unit.coindays }),
|
||||
line({
|
||||
metric: tree.activity.coinblocksDestroyed.sum,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.coinblocks,
|
||||
}),
|
||||
line({
|
||||
metric: tree.activity.coindaysDestroyed.sum,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.coindays,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: title("Cumulative Coins Destroyed"),
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
line({ metric: tree.activity.coinblocksDestroyed.cumulative, name, color, unit: Unit.coinblocks }),
|
||||
line({ metric: tree.activity.coindaysDestroyed.cumulative, name, color, unit: Unit.coindays }),
|
||||
line({
|
||||
metric: tree.activity.coinblocksDestroyed.cumulative,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.coinblocks,
|
||||
}),
|
||||
line({
|
||||
metric: tree.activity.coindaysDestroyed.cumulative,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.coindays,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
],
|
||||
@@ -2223,8 +2342,18 @@ function createSingleActivitySectionWithAdjusted(ctx, cohort, title) {
|
||||
cohort,
|
||||
title,
|
||||
valueMetrics: [
|
||||
line({ metric: tree.realized.adjustedValueCreated, name: "Adjusted Created", color: colors.lime, unit: Unit.usd }),
|
||||
line({ metric: tree.realized.adjustedValueDestroyed, name: "Adjusted Destroyed", color: colors.pink, unit: Unit.usd }),
|
||||
line({
|
||||
metric: tree.realized.adjustedValueCreated,
|
||||
name: "Adjusted Created",
|
||||
color: colors.lime,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.adjustedValueDestroyed,
|
||||
name: "Adjusted Destroyed",
|
||||
color: colors.pink,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
});
|
||||
}
|
||||
@@ -2247,14 +2376,24 @@ function createGroupedActivitySectionWithAdjusted(list, title) {
|
||||
name: "Normal",
|
||||
title: title("Value Created"),
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
line({ metric: tree.realized.valueCreated, name, color, unit: Unit.usd }),
|
||||
line({
|
||||
metric: tree.realized.valueCreated,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
{
|
||||
name: "Adjusted",
|
||||
title: title("Adjusted Value Created"),
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
line({ metric: tree.realized.adjustedValueCreated, name, color, unit: Unit.usd }),
|
||||
line({
|
||||
metric: tree.realized.adjustedValueCreated,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
],
|
||||
@@ -2266,14 +2405,24 @@ function createGroupedActivitySectionWithAdjusted(list, title) {
|
||||
name: "Normal",
|
||||
title: title("Value Destroyed"),
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
line({ metric: tree.realized.valueDestroyed, name, color, unit: Unit.usd }),
|
||||
line({
|
||||
metric: tree.realized.valueDestroyed,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
{
|
||||
name: "Adjusted",
|
||||
title: title("Adjusted Value Destroyed"),
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
line({ metric: tree.realized.adjustedValueDestroyed, name, color, unit: Unit.usd }),
|
||||
line({
|
||||
metric: tree.realized.adjustedValueDestroyed,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
],
|
||||
@@ -2281,4 +2430,3 @@ function createGroupedActivitySectionWithAdjusted(list, title) {
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { collect, markUsed, logUnused } from "./unused.js";
|
||||
import { setQr } from "../panes/share.js";
|
||||
import { getConstant } from "./constants.js";
|
||||
import { colors } from "../chart/colors.js";
|
||||
import { Unit } from "../utils/units.js";
|
||||
|
||||
/**
|
||||
* @param {BrkClient} brk
|
||||
@@ -83,7 +84,23 @@ export function initOptions(brk) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AnyFetchedSeriesBlueprint[]} [arr]
|
||||
* Check if a metric is an ActivePricePattern (has dollars and sats sub-metrics)
|
||||
* @param {any} metric
|
||||
* @returns {metric is ActivePricePattern}
|
||||
*/
|
||||
function isActivePricePattern(metric) {
|
||||
return (
|
||||
metric &&
|
||||
typeof metric === "object" &&
|
||||
"dollars" in metric &&
|
||||
"sats" in metric &&
|
||||
metric.dollars?.by &&
|
||||
metric.sats?.by
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {(AnyFetchedSeriesBlueprint | FetchedPriceSeriesBlueprint)[]} [arr]
|
||||
*/
|
||||
function arrayToMap(arr = []) {
|
||||
/** @type {Map<Unit, AnyFetchedSeriesBlueprint[]>} */
|
||||
@@ -97,29 +114,50 @@ export function initOptions(brk) {
|
||||
`Blueprint missing metric: ${JSON.stringify(blueprint)}`,
|
||||
);
|
||||
}
|
||||
if (!blueprint.unit) {
|
||||
throw new Error(`Blueprint missing unit: ${blueprint.title}`);
|
||||
|
||||
// Auto-expand ActivePricePattern into USD and sats versions
|
||||
if (isActivePricePattern(blueprint.metric)) {
|
||||
const pricePattern = /** @type {AnyPricePattern} */ (blueprint.metric);
|
||||
|
||||
// USD version
|
||||
markUsed(pricePattern.dollars);
|
||||
if (!map.has(Unit.usd)) map.set(Unit.usd, []);
|
||||
map.get(Unit.usd)?.push({ ...blueprint, metric: pricePattern.dollars, unit: Unit.usd });
|
||||
|
||||
// Sats version
|
||||
markUsed(pricePattern.sats);
|
||||
if (!map.has(Unit.sats)) map.set(Unit.sats, []);
|
||||
map.get(Unit.sats)?.push({ ...blueprint, metric: pricePattern.sats, unit: Unit.sats });
|
||||
|
||||
continue;
|
||||
}
|
||||
markUsed(blueprint.metric);
|
||||
const unit = blueprint.unit;
|
||||
|
||||
// At this point, blueprint is definitely an AnyFetchedSeriesBlueprint (not a price pattern)
|
||||
const regularBlueprint = /** @type {AnyFetchedSeriesBlueprint} */ (blueprint);
|
||||
|
||||
if (!regularBlueprint.unit) {
|
||||
throw new Error(`Blueprint missing unit: ${regularBlueprint.title}`);
|
||||
}
|
||||
markUsed(regularBlueprint.metric);
|
||||
const unit = regularBlueprint.unit;
|
||||
if (!map.has(unit)) {
|
||||
map.set(unit, []);
|
||||
}
|
||||
map.get(unit)?.push(blueprint);
|
||||
map.get(unit)?.push(regularBlueprint);
|
||||
|
||||
// Track baseline base values for auto price lines
|
||||
if (blueprint.type === "Baseline") {
|
||||
const baseValue = blueprint.options?.baseValue?.price ?? 0;
|
||||
if (regularBlueprint.type === "Baseline") {
|
||||
const baseValue = regularBlueprint.options?.baseValue?.price ?? 0;
|
||||
if (!priceLines.has(unit)) priceLines.set(unit, new Set());
|
||||
priceLines.get(unit)?.add(baseValue);
|
||||
}
|
||||
|
||||
// Remove from set if manual price line already exists
|
||||
// Note: line() doesn't set type, so undefined means Line
|
||||
if (blueprint.type === "Line" || blueprint.type === undefined) {
|
||||
const path = Object.values(blueprint.metric.by)[0]?.path ?? "";
|
||||
if (regularBlueprint.type === "Line" || regularBlueprint.type === undefined) {
|
||||
const path = Object.values(regularBlueprint.metric.by)[0]?.path ?? "";
|
||||
if (path.includes("constant_")) {
|
||||
priceLines.get(unit)?.delete(parseFloat(blueprint.title));
|
||||
priceLines.get(unit)?.delete(parseFloat(regularBlueprint.title));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/** Moving averages section */
|
||||
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { line } from "../series.js";
|
||||
import { price } from "../series.js";
|
||||
import { createRatioChart, createZScoresFolder, formatCohortTitle } from "../shared.js";
|
||||
import { periodIdToName } from "./utils.js";
|
||||
|
||||
@@ -81,19 +80,19 @@ export function createPriceWithRatioOptions(
|
||||
ctx,
|
||||
{ title, legend, ratio, color },
|
||||
) {
|
||||
const priceMetric = ratio.price;
|
||||
const pricePattern = ratio.price;
|
||||
|
||||
return [
|
||||
{
|
||||
name: "Price",
|
||||
title,
|
||||
top: [line({ metric: priceMetric, name: legend, color, unit: Unit.usd })],
|
||||
top: [price({ metric: pricePattern, name: legend, color })],
|
||||
},
|
||||
createRatioChart(ctx, { title: formatCohortTitle(title), price: priceMetric, ratio, color }),
|
||||
createRatioChart(ctx, { title: formatCohortTitle(title), pricePattern, ratio, color }),
|
||||
createZScoresFolder(ctx, {
|
||||
title,
|
||||
legend,
|
||||
price: priceMetric,
|
||||
pricePattern,
|
||||
ratio,
|
||||
color,
|
||||
}),
|
||||
@@ -103,6 +102,46 @@ export function createPriceWithRatioOptions(
|
||||
/** 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, options: { lineStyle: 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, options: { lineStyle: 1 } }),
|
||||
],
|
||||
})),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {PartialContext} ctx
|
||||
* @param {MarketMovingAverage} movingAverage
|
||||
@@ -127,11 +166,10 @@ export function createAveragesSection(ctx, movingAverage) {
|
||||
name: "Compare",
|
||||
title: `Price ${label}s`,
|
||||
top: averages.map(({ id, color, ratio }) =>
|
||||
line({
|
||||
price({
|
||||
metric: ratio.price,
|
||||
name: id,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
},
|
||||
@@ -165,6 +203,7 @@ export function createAveragesSection(ctx, movingAverage) {
|
||||
return {
|
||||
name: "Moving Averages",
|
||||
tree: [
|
||||
createCompareSection(smaAverages, emaAverages),
|
||||
createSubSection("SMA", smaAverages),
|
||||
createSubSection("EMA", emaAverages),
|
||||
],
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
/** Bands indicators (MinMax, Mayer Multiple) */
|
||||
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { line } from "../series.js";
|
||||
import { price } from "../series.js";
|
||||
|
||||
/**
|
||||
* Create Bands section
|
||||
@@ -47,19 +44,17 @@ export function createBandsSection(ctx, { range, movingAverage }) {
|
||||
name: id,
|
||||
title: `${title} MinMax`,
|
||||
top: [
|
||||
line({
|
||||
price({
|
||||
metric: min,
|
||||
name: "Min",
|
||||
key: `price-min`,
|
||||
color: colors.red,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
price({
|
||||
metric: max,
|
||||
name: "Max",
|
||||
key: `price-max`,
|
||||
color: colors.green,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
})),
|
||||
@@ -68,23 +63,20 @@ export function createBandsSection(ctx, { range, movingAverage }) {
|
||||
name: "Mayer Multiple",
|
||||
title: "Mayer Multiple",
|
||||
top: [
|
||||
line({
|
||||
price({
|
||||
metric: movingAverage.price200dSma.price,
|
||||
name: "200d SMA",
|
||||
color: colors.yellow,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
price({
|
||||
metric: movingAverage.price200dSmaX24,
|
||||
name: "200d SMA x2.4",
|
||||
color: colors.green,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
price({
|
||||
metric: movingAverage.price200dSmaX08,
|
||||
name: "200d SMA x0.8",
|
||||
color: colors.red,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
},
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { localhost } from "../../utils/env.js";
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { candlestick, line } from "../series.js";
|
||||
import { candlestick, line, price } from "../series.js";
|
||||
import { createAveragesSection } from "./averages.js";
|
||||
import { createReturnsSection } from "./performance.js";
|
||||
import { createMomentumSection } from "./momentum.js";
|
||||
@@ -18,7 +18,7 @@ import { createDcaVsLumpSumSection, createDcaByYearSection } from "./investing.j
|
||||
*/
|
||||
export function createMarketSection(ctx) {
|
||||
const { colors, brk } = ctx;
|
||||
const { market, supply, price } = brk.metrics;
|
||||
const { market, supply, price: priceMetrics } = brk.metrics;
|
||||
const {
|
||||
movingAverage,
|
||||
ath,
|
||||
@@ -39,79 +39,80 @@ export function createMarketSection(ctx) {
|
||||
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: [
|
||||
top: /** @type {any} */ ([
|
||||
candlestick({
|
||||
metric: price.oracle.closeOhlcDollars,
|
||||
name: "close",
|
||||
metric: priceMetrics.oracle.closeOhlcDollars,
|
||||
name: "Close",
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
candlestick({
|
||||
metric: price.oracle.midOhlcDollars,
|
||||
name: "mid",
|
||||
metric: priceMetrics.oracle.midOhlcDollars,
|
||||
name: "Mid",
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: price.oracle.phaseDailyDollars.median,
|
||||
metric: priceMetrics.oracle.phaseDailyDollars.median,
|
||||
name: "o. p50",
|
||||
unit: Unit.usd,
|
||||
color: colors.yellow,
|
||||
}),
|
||||
line({
|
||||
metric: price.oracle.phaseV2DailyDollars.median,
|
||||
metric: priceMetrics.oracle.phaseV2DailyDollars.median,
|
||||
name: "o2. p50",
|
||||
unit: Unit.usd,
|
||||
color: colors.orange,
|
||||
}),
|
||||
line({
|
||||
metric: price.oracle.phaseV2PeakDailyDollars.median,
|
||||
metric: priceMetrics.oracle.phaseV2PeakDailyDollars.median,
|
||||
name: "o2.2 p50",
|
||||
unit: Unit.usd,
|
||||
color: colors.orange,
|
||||
}),
|
||||
line({
|
||||
metric: price.oracle.phaseV3DailyDollars.median,
|
||||
metric: priceMetrics.oracle.phaseV3DailyDollars.median,
|
||||
name: "o3. p50",
|
||||
unit: Unit.usd,
|
||||
color: colors.red,
|
||||
}),
|
||||
line({
|
||||
metric: price.oracle.phaseV3PeakDailyDollars.median,
|
||||
metric: priceMetrics.oracle.phaseV3PeakDailyDollars.median,
|
||||
name: "o3.2 p50",
|
||||
unit: Unit.usd,
|
||||
color: colors.red,
|
||||
}),
|
||||
line({
|
||||
metric: price.oracle.phaseDailyDollars.max,
|
||||
metric: priceMetrics.oracle.phaseDailyDollars.max,
|
||||
name: "o. max",
|
||||
unit: Unit.usd,
|
||||
color: colors.lime,
|
||||
}),
|
||||
line({
|
||||
metric: price.oracle.phaseV2DailyDollars.max,
|
||||
metric: priceMetrics.oracle.phaseV2DailyDollars.max,
|
||||
name: "o.2 max",
|
||||
unit: Unit.usd,
|
||||
color: colors.emerald,
|
||||
}),
|
||||
line({
|
||||
metric: price.oracle.phaseDailyDollars.min,
|
||||
metric: priceMetrics.oracle.phaseDailyDollars.min,
|
||||
name: "o. min",
|
||||
unit: Unit.usd,
|
||||
color: colors.rose,
|
||||
}),
|
||||
line({
|
||||
metric: price.oracle.phaseV2DailyDollars.min,
|
||||
metric: priceMetrics.oracle.phaseV2DailyDollars.min,
|
||||
name: "o.2 min",
|
||||
unit: Unit.usd,
|
||||
color: colors.purple,
|
||||
}),
|
||||
],
|
||||
]),
|
||||
},
|
||||
]
|
||||
])
|
||||
: []),
|
||||
|
||||
// Capitalization
|
||||
@@ -131,7 +132,7 @@ export function createMarketSection(ctx) {
|
||||
{
|
||||
name: "All Time High",
|
||||
title: "All Time High",
|
||||
top: [line({ metric: ath.priceAth, name: "ATH", unit: Unit.usd })],
|
||||
top: [price({ metric: ath.priceAth, name: "ATH" })],
|
||||
bottom: [
|
||||
line({
|
||||
metric: ath.priceDrawdown,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { priceLine } from "../constants.js";
|
||||
import { line, baseline } from "../series.js";
|
||||
import { line, baseline, price } from "../series.js";
|
||||
import { satsBtcUsd } from "../shared.js";
|
||||
import { periodIdToName } from "./utils.js";
|
||||
|
||||
@@ -58,17 +58,15 @@ export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) {
|
||||
name: "Cost Basis",
|
||||
title: `${name} Cost Basis`,
|
||||
top: [
|
||||
line({
|
||||
price({
|
||||
metric: dca.periodAveragePrice[key],
|
||||
name: "DCA",
|
||||
color: colors.green,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
price({
|
||||
metric: lookback[key],
|
||||
name: "Lump sum",
|
||||
color: colors.orange,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
});
|
||||
@@ -78,8 +76,8 @@ export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) {
|
||||
name: "Days in Profit",
|
||||
title: `${name} Days in Profit`,
|
||||
top: [
|
||||
line({ metric: dca.periodAveragePrice[key], name: "DCA", color: colors.green, unit: Unit.usd }),
|
||||
line({ metric: lookback[key], name: "Lump sum", color: colors.orange, unit: Unit.usd }),
|
||||
price({ metric: dca.periodAveragePrice[key], name: "DCA", color: colors.green }),
|
||||
price({ metric: lookback[key], name: "Lump sum", color: colors.orange }),
|
||||
],
|
||||
bottom: [
|
||||
line({ metric: dca.periodDaysInProfit[key], name: "DCA", color: colors.green, unit: Unit.days }),
|
||||
@@ -92,8 +90,8 @@ export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) {
|
||||
name: "Days in Loss",
|
||||
title: `${name} Days in Loss`,
|
||||
top: [
|
||||
line({ metric: dca.periodAveragePrice[key], name: "DCA", color: colors.green, unit: Unit.usd }),
|
||||
line({ metric: lookback[key], name: "Lump sum", color: colors.orange, unit: Unit.usd }),
|
||||
price({ metric: dca.periodAveragePrice[key], name: "DCA", color: colors.green }),
|
||||
price({ metric: lookback[key], name: "Lump sum", color: colors.orange }),
|
||||
],
|
||||
bottom: [
|
||||
line({ metric: dca.periodDaysInLoss[key], name: "DCA", color: colors.red, unit: Unit.days }),
|
||||
@@ -106,8 +104,8 @@ export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) {
|
||||
name: "Max Drawdown",
|
||||
title: `${name} Max Drawdown`,
|
||||
top: [
|
||||
line({ metric: dca.periodAveragePrice[key], name: "DCA", color: colors.green, unit: Unit.usd }),
|
||||
line({ metric: lookback[key], name: "Lump sum", color: colors.orange, unit: Unit.usd }),
|
||||
price({ metric: dca.periodAveragePrice[key], name: "DCA", color: colors.green }),
|
||||
price({ metric: lookback[key], name: "Lump sum", color: colors.orange }),
|
||||
],
|
||||
bottom: [
|
||||
line({ metric: dca.periodMaxDrawdown[key], name: "DCA", color: colors.green, unit: Unit.percentage }),
|
||||
@@ -120,8 +118,8 @@ export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) {
|
||||
name: "Max Return",
|
||||
title: `${name} Max Return`,
|
||||
top: [
|
||||
line({ metric: dca.periodAveragePrice[key], name: "DCA", color: colors.green, unit: Unit.usd }),
|
||||
line({ metric: lookback[key], name: "Lump sum", color: colors.orange, unit: Unit.usd }),
|
||||
price({ metric: dca.periodAveragePrice[key], name: "DCA", color: colors.green }),
|
||||
price({ metric: lookback[key], name: "Lump sum", color: colors.orange }),
|
||||
],
|
||||
bottom: [
|
||||
line({ metric: dca.periodMaxReturn[key], name: "DCA", color: colors.green, unit: Unit.percentage }),
|
||||
@@ -279,12 +277,11 @@ export function createDcaByYearSection(ctx, { dca }) {
|
||||
name: "Cost basis",
|
||||
title: "DCA Cost Basis",
|
||||
top: dcaClasses.map(({ year, color, defaultActive, costBasis }) =>
|
||||
line({
|
||||
price({
|
||||
metric: costBasis,
|
||||
name: `${year}`,
|
||||
color,
|
||||
defaultActive,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
},
|
||||
@@ -353,11 +350,10 @@ export function createDcaByYearSection(ctx, { dca }) {
|
||||
name: "Cost Basis",
|
||||
title: `${year} Cost Basis`,
|
||||
top: [
|
||||
line({
|
||||
price({
|
||||
metric: costBasis,
|
||||
name: "Cost Basis",
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/** On-chain indicators (Pi Cycle, Puell, NVT, Gini) */
|
||||
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { baseline, line } from "../series.js";
|
||||
import { baseline, line, price } from "../series.js";
|
||||
|
||||
/**
|
||||
* Create Valuation section
|
||||
@@ -20,17 +20,15 @@ export function createValuationSection(ctx, { indicators, movingAverage }) {
|
||||
name: "Pi Cycle",
|
||||
title: "Pi Cycle",
|
||||
top: [
|
||||
line({
|
||||
price({
|
||||
metric: movingAverage.price111dSma.price,
|
||||
name: "111d SMA",
|
||||
color: colors.green,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
price({
|
||||
metric: movingAverage.price350dSmaX2,
|
||||
name: "350d SMA x2",
|
||||
color: colors.red,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
bottom: [
|
||||
|
||||
@@ -282,9 +282,9 @@ export function createPartialOptions({ brk }) {
|
||||
],
|
||||
},
|
||||
|
||||
// Research section
|
||||
// Frameworks section
|
||||
{
|
||||
name: "Research",
|
||||
name: "Frameworks",
|
||||
tree: [
|
||||
createCointimeSection(ctx),
|
||||
],
|
||||
|
||||
@@ -2,6 +2,44 @@
|
||||
|
||||
import { Unit } from "../utils/units.js";
|
||||
|
||||
// ============================================================================
|
||||
// Price helper for top pane (auto-expands to USD + sats)
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Create a price series for the top pane (auto-expands to USD + sats versions)
|
||||
* @param {Object} args
|
||||
* @param {AnyPricePattern} args.metric - Price pattern with dollars and sats
|
||||
* @param {string} args.name
|
||||
* @param {string} [args.key]
|
||||
* @param {LineStyle} [args.style]
|
||||
* @param {Color} [args.color]
|
||||
* @param {boolean} [args.defaultActive]
|
||||
* @param {LineSeriesPartialOptions} [args.options]
|
||||
* @returns {FetchedPriceSeriesBlueprint}
|
||||
*/
|
||||
export function price({
|
||||
metric,
|
||||
name,
|
||||
key,
|
||||
style,
|
||||
color,
|
||||
defaultActive,
|
||||
options,
|
||||
}) {
|
||||
return {
|
||||
metric,
|
||||
title: name,
|
||||
key,
|
||||
color,
|
||||
defaultActive,
|
||||
options: {
|
||||
lineStyle: style,
|
||||
...options,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Shared percentile helper
|
||||
// ============================================================================
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/** Shared helpers for options */
|
||||
|
||||
import { Unit } from "../utils/units.js";
|
||||
import { line, baseline } from "./series.js";
|
||||
import { line, baseline, price } from "./series.js";
|
||||
import { priceLine, priceLines } from "./constants.js";
|
||||
|
||||
/**
|
||||
@@ -153,27 +153,26 @@ export function ratioSmas(colors, ratio) {
|
||||
* @param {PartialContext} ctx
|
||||
* @param {Object} args
|
||||
* @param {(metric: string) => string} args.title
|
||||
* @param {AnyMetricPattern} args.price - The price metric to show in top pane
|
||||
* @param {AnyPricePattern} args.pricePattern - The price pattern to show in top pane
|
||||
* @param {ActivePriceRatioPattern} args.ratio - The ratio pattern
|
||||
* @param {Color} args.color
|
||||
* @param {string} [args.name] - Optional name override (default: "ratio")
|
||||
* @returns {PartialChartOption}
|
||||
*/
|
||||
export function createRatioChart(ctx, { title, price, ratio, color, name }) {
|
||||
export function createRatioChart(ctx, { title, pricePattern, ratio, color, name }) {
|
||||
const { colors } = ctx;
|
||||
|
||||
return {
|
||||
name: name ?? "ratio",
|
||||
title: title(name ?? "Ratio"),
|
||||
top: [
|
||||
line({ metric: price, name: "Price", color, unit: Unit.usd }),
|
||||
price({ metric: pricePattern, name: "Price", color }),
|
||||
...percentileUsdMap(colors, ratio).map(({ name, prop, color }) =>
|
||||
line({
|
||||
price({
|
||||
metric: prop,
|
||||
name,
|
||||
color,
|
||||
defaultActive: false,
|
||||
unit: Unit.usd,
|
||||
options: { lineStyle: 1 },
|
||||
}),
|
||||
),
|
||||
@@ -208,14 +207,14 @@ export function createRatioChart(ctx, { title, price, ratio, color, name }) {
|
||||
* @param {Object} args
|
||||
* @param {string} args.title
|
||||
* @param {string} args.legend
|
||||
* @param {AnyMetricPattern} args.price - The price metric to show in top pane
|
||||
* @param {AnyPricePattern} args.pricePattern - The price pattern to show in top pane
|
||||
* @param {ActivePriceRatioPattern} args.ratio - The ratio pattern
|
||||
* @param {Color} args.color
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createZScoresFolder(
|
||||
ctx,
|
||||
{ title, legend, price, ratio, color },
|
||||
{ title, legend, pricePattern, ratio, color },
|
||||
) {
|
||||
const { colors } = ctx;
|
||||
const sdPats = sdPatterns(ratio);
|
||||
@@ -227,40 +226,36 @@ export function createZScoresFolder(
|
||||
name: "Compare",
|
||||
title: `${title} Z-Scores`,
|
||||
top: [
|
||||
line({ metric: price, name: legend, color, unit: Unit.usd }),
|
||||
line({
|
||||
price({ metric: pricePattern, name: legend, color }),
|
||||
price({
|
||||
metric: ratio.ratio1ySd._0sdUsd,
|
||||
name: "1y 0σ",
|
||||
color: colors.orange,
|
||||
defaultActive: false,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
price({
|
||||
metric: ratio.ratio2ySd._0sdUsd,
|
||||
name: "2y 0σ",
|
||||
color: colors.yellow,
|
||||
defaultActive: false,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
price({
|
||||
metric: ratio.ratio4ySd._0sdUsd,
|
||||
name: "4y 0σ",
|
||||
color: colors.lime,
|
||||
defaultActive: false,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
price({
|
||||
metric: ratio.ratioSd._0sdUsd,
|
||||
name: "all 0σ",
|
||||
color: colors.blue,
|
||||
defaultActive: false,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
bottom: [
|
||||
line({
|
||||
metric: ratio.ratioSd.zscore,
|
||||
name: "all",
|
||||
name: "All",
|
||||
color: colors.blue,
|
||||
unit: Unit.sd,
|
||||
}),
|
||||
@@ -294,14 +289,13 @@ export function createZScoresFolder(
|
||||
name: nameAddon,
|
||||
title: `${title} ${titleAddon} Z-Score`,
|
||||
top: [
|
||||
line({ metric: price, name: legend, color, unit: Unit.usd }),
|
||||
price({ metric: pricePattern, name: legend, color }),
|
||||
...sdBandsUsd(colors, sd).map(
|
||||
({ name: bandName, prop, color: bandColor }) =>
|
||||
line({
|
||||
price({
|
||||
metric: prop,
|
||||
name: bandName,
|
||||
color: bandColor,
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
),
|
||||
|
||||
@@ -48,6 +48,13 @@
|
||||
* @typedef {DotsSeriesBlueprint & FetchedAnySeriesOptions} FetchedDotsSeriesBlueprint
|
||||
* @typedef {AnySeriesBlueprint & FetchedAnySeriesOptions} AnyFetchedSeriesBlueprint
|
||||
*
|
||||
* Any pattern with dollars and sats sub-metrics (auto-expands to USD + sats)
|
||||
* @typedef {{ dollars: AnyMetricPattern, sats: AnyMetricPattern }} AnyPricePattern
|
||||
*
|
||||
* Top pane price series - requires a price pattern with dollars/sats, auto-expands to USD + sats
|
||||
* @typedef {{ metric: AnyPricePattern }} FetchedPriceSeriesOptions
|
||||
* @typedef {LineSeriesBlueprint & FetchedPriceSeriesOptions} FetchedPriceSeriesBlueprint
|
||||
*
|
||||
* @typedef {Object} PartialOption
|
||||
* @property {string} name
|
||||
*
|
||||
@@ -66,7 +73,7 @@
|
||||
* @typedef {Object} PartialChartOptionSpecific
|
||||
* @property {"chart"} [kind]
|
||||
* @property {string} title
|
||||
* @property {AnyFetchedSeriesBlueprint[]} [top]
|
||||
* @property {FetchedPriceSeriesBlueprint[]} [top]
|
||||
* @property {AnyFetchedSeriesBlueprint[]} [bottom]
|
||||
*
|
||||
* @typedef {PartialOption & PartialChartOptionSpecific} PartialChartOption
|
||||
|
||||
Reference in New Issue
Block a user