website: snapshot

This commit is contained in:
nym21
2026-02-03 23:43:52 +01:00
parent 277a0eb6a7
commit 0d5d7da70f
44 changed files with 2999 additions and 1591 deletions
+44 -44
View File
@@ -242,52 +242,52 @@ function createRealizedPnlSection(args, title) {
line({
metric: realized.realizedProfit.sum,
name: "Profit",
color: colors.green,
color: colors.profit,
unit: Unit.usd,
}),
line({
metric: realized.realizedProfit7dEma,
name: "Profit 7d EMA",
color: colors.green,
color: colors.profit,
unit: Unit.usd,
}),
line({
metric: realized.realizedProfit.cumulative,
name: "Profit Cumulative",
color: colors.green,
color: colors.profit,
unit: Unit.usd,
defaultActive: false,
}),
line({
metric: realized.realizedLoss.sum,
name: "Loss",
color: colors.red,
color: colors.loss,
unit: Unit.usd,
}),
line({
metric: realized.realizedLoss7dEma,
name: "Loss 7d EMA",
color: colors.red,
color: colors.loss,
unit: Unit.usd,
}),
line({
metric: realized.realizedLoss.cumulative,
name: "Loss Cumulative",
color: colors.red,
color: colors.loss,
unit: Unit.usd,
defaultActive: false,
}),
line({
metric: realized.negRealizedLoss.sum,
name: "Negative Loss",
color: colors.red,
color: colors.loss,
unit: Unit.usd,
defaultActive: false,
}),
line({
metric: realized.negRealizedLoss.cumulative,
name: "Negative Loss Cumulative",
color: colors.red,
color: colors.loss,
unit: Unit.usd,
defaultActive: false,
}),
@@ -301,26 +301,26 @@ function createRealizedPnlSection(args, title) {
baseline({
metric: realized.realizedProfitRelToRealizedCap.sum,
name: "Profit",
color: colors.green,
color: colors.profit,
unit: Unit.pctRcap,
}),
baseline({
metric: realized.realizedProfitRelToRealizedCap.cumulative,
name: "Profit Cumulative",
color: colors.green,
color: colors.profit,
unit: Unit.pctRcap,
defaultActive: false,
}),
baseline({
metric: realized.realizedLossRelToRealizedCap.sum,
name: "Loss",
color: colors.red,
color: colors.loss,
unit: Unit.pctRcap,
}),
baseline({
metric: realized.realizedLossRelToRealizedCap.cumulative,
name: "Loss Cumulative",
color: colors.red,
color: colors.loss,
unit: Unit.pctRcap,
defaultActive: false,
}),
@@ -390,7 +390,7 @@ function createRealizedPnlSection(args, title) {
name: "SOPR",
title: title("SOPR"),
bottom: [
...createSingleSoprSeries(colors, args.tree),
...createSingleSoprSeries(args.tree),
priceLine({
unit: Unit.ratio,
number: 1,
@@ -400,7 +400,7 @@ function createRealizedPnlSection(args, title) {
{
name: "Sell Side Risk",
title: title("Sell Side Risk Ratio"),
bottom: createSingleSellSideRiskSeries(colors, args.tree),
bottom: createSingleSellSideRiskSeries(args.tree),
},
{
name: "Value",
@@ -408,17 +408,17 @@ function createRealizedPnlSection(args, title) {
{
name: "Created & Destroyed",
title: title("Value Created & Destroyed"),
bottom: createSingleValueCreatedDestroyedSeries(colors, args.tree),
bottom: createSingleValueCreatedDestroyedSeries(args.tree),
},
{
name: "Breakdown",
title: title("Value Flow Breakdown"),
bottom: createSingleValueFlowBreakdownSeries(colors, args.tree),
bottom: createSingleValueFlowBreakdownSeries(args.tree),
},
{
name: "Flow",
title: title("Capitulation & Profit Flow"),
bottom: createSingleCapitulationProfitFlowSeries(colors, args.tree),
bottom: createSingleCapitulationProfitFlowSeries(args.tree),
},
],
},
@@ -429,20 +429,20 @@ function createRealizedPnlSection(args, title) {
line({
metric: realized.peakRegret.sum,
name: "Sum",
color: colors.red,
color: colors.loss,
unit: Unit.usd,
}),
line({
metric: realized.peakRegret.cumulative,
name: "Cumulative",
color: colors.red,
color: colors.loss,
unit: Unit.usd,
defaultActive: false,
}),
baseline({
metric: realized.peakRegretRelToRealizedCap,
name: "Rel. to Realized Cap",
color: colors.orange,
color: colors.realized,
unit: Unit.pctRcap,
}),
],
@@ -457,39 +457,39 @@ function createRealizedPnlSection(args, title) {
line({
metric: realized.sentInProfit.bitcoin.sum,
name: "Sum",
color: colors.green,
color: colors.profit,
unit: Unit.btc,
}),
line({
metric: realized.sentInProfit.bitcoin.cumulative,
name: "Cumulative",
color: colors.green,
color: colors.profit,
unit: Unit.btc,
defaultActive: false,
}),
line({
metric: realized.sentInProfit.sats.sum,
name: "Sum",
color: colors.green,
color: colors.profit,
unit: Unit.sats,
}),
line({
metric: realized.sentInProfit.sats.cumulative,
name: "Cumulative",
color: colors.green,
color: colors.profit,
unit: Unit.sats,
defaultActive: false,
}),
line({
metric: realized.sentInProfit.dollars.sum,
name: "Sum",
color: colors.green,
color: colors.profit,
unit: Unit.usd,
}),
line({
metric: realized.sentInProfit.dollars.cumulative,
name: "Cumulative",
color: colors.green,
color: colors.profit,
unit: Unit.usd,
defaultActive: false,
}),
@@ -502,39 +502,39 @@ function createRealizedPnlSection(args, title) {
line({
metric: realized.sentInLoss.bitcoin.sum,
name: "Sum",
color: colors.red,
color: colors.loss,
unit: Unit.btc,
}),
line({
metric: realized.sentInLoss.bitcoin.cumulative,
name: "Cumulative",
color: colors.red,
color: colors.loss,
unit: Unit.btc,
defaultActive: false,
}),
line({
metric: realized.sentInLoss.sats.sum,
name: "Sum",
color: colors.red,
color: colors.loss,
unit: Unit.sats,
}),
line({
metric: realized.sentInLoss.sats.cumulative,
name: "Cumulative",
color: colors.red,
color: colors.loss,
unit: Unit.sats,
defaultActive: false,
}),
line({
metric: realized.sentInLoss.dollars.sum,
name: "Sum",
color: colors.red,
color: colors.loss,
unit: Unit.usd,
}),
line({
metric: realized.sentInLoss.dollars.cumulative,
name: "Cumulative",
color: colors.red,
color: colors.loss,
unit: Unit.usd,
defaultActive: false,
}),
@@ -546,7 +546,7 @@ function createRealizedPnlSection(args, title) {
bottom: satsBtcUsd({
pattern: realized.sentInProfit14dEma,
name: "14d EMA",
color: colors.green,
color: colors.profit,
}),
},
{
@@ -555,7 +555,7 @@ function createRealizedPnlSection(args, title) {
bottom: satsBtcUsd({
pattern: realized.sentInLoss14dEma,
name: "14d EMA",
color: colors.red,
color: colors.loss,
}),
},
],
@@ -820,7 +820,7 @@ function createUnrealizedSection(list, useGroupName, title) {
line({
metric: tree.unrealized.unrealizedProfit,
name: useGroupName ? name : "Profit",
color: useGroupName ? color : colors.green,
color: useGroupName ? color : colors.profit,
unit: Unit.usd,
}),
]),
@@ -832,7 +832,7 @@ function createUnrealizedSection(list, useGroupName, title) {
line({
metric: tree.unrealized.unrealizedLoss,
name: useGroupName ? name : "Loss",
color: useGroupName ? color : colors.red,
color: useGroupName ? color : colors.loss,
unit: Unit.usd,
}),
]),
@@ -856,7 +856,7 @@ function createUnrealizedSection(list, useGroupName, title) {
line({
metric: tree.unrealized.negUnrealizedLoss,
name: useGroupName ? name : "Negative Loss",
color: useGroupName ? color : colors.red,
color: useGroupName ? color : colors.loss,
unit: Unit.usd,
}),
]),
@@ -871,7 +871,7 @@ function createUnrealizedSection(list, useGroupName, title) {
line({
metric: tree.unrealized.investedCapitalInProfit,
name: useGroupName ? name : "In Profit",
color: useGroupName ? color : colors.green,
color: useGroupName ? color : colors.profit,
unit: Unit.usd,
}),
]),
@@ -883,7 +883,7 @@ function createUnrealizedSection(list, useGroupName, title) {
line({
metric: tree.unrealized.investedCapitalInLoss,
name: useGroupName ? name : "In Loss",
color: useGroupName ? color : colors.red,
color: useGroupName ? color : colors.loss,
unit: Unit.usd,
}),
]),
@@ -913,7 +913,7 @@ function createUnrealizedSection(list, useGroupName, title) {
line({
metric: tree.relative.unrealizedProfitRelToMarketCap,
name: useGroupName ? name : "Profit",
color: useGroupName ? color : colors.green,
color: useGroupName ? color : colors.profit,
unit: Unit.pctMcap,
}),
]),
@@ -925,7 +925,7 @@ function createUnrealizedSection(list, useGroupName, title) {
line({
metric: tree.relative.unrealizedLossRelToMarketCap,
name: useGroupName ? name : "Loss",
color: useGroupName ? color : colors.red,
color: useGroupName ? color : colors.loss,
unit: Unit.pctMcap,
}),
]),
@@ -949,7 +949,7 @@ function createUnrealizedSection(list, useGroupName, title) {
line({
metric: tree.relative.negUnrealizedLossRelToMarketCap,
name: useGroupName ? name : "Negative Loss",
color: useGroupName ? color : colors.red,
color: useGroupName ? color : colors.loss,
unit: Unit.pctMcap,
}),
]),
@@ -961,7 +961,7 @@ function createUnrealizedSection(list, useGroupName, title) {
baseline({
metric: tree.relative.investedCapitalInProfitPct,
name: useGroupName ? name : "In Profit",
color: useGroupName ? color : colors.green,
color: useGroupName ? color : colors.profit,
unit: Unit.pctRcap,
}),
]),
@@ -973,7 +973,7 @@ function createUnrealizedSection(list, useGroupName, title) {
baseline({
metric: tree.relative.investedCapitalInLossPct,
name: useGroupName ? name : "In Loss",
color: useGroupName ? color : colors.red,
color: useGroupName ? color : colors.loss,
unit: Unit.pctRcap,
}),
]),
@@ -0,0 +1,453 @@
/**
* Cost Basis section builders
*
* Structure:
* - Summary: Key stats (avg + median active, quartiles/extremes available)
* - By Coin: BTC-weighted percentiles (IQR active: p25, p50, p75)
* - By Capital: USD-weighted percentiles (IQR active: p25, p50, p75)
* - Price Position: Spot percentile (both perspectives active)
*
* For cohorts WITHOUT percentiles: Summary only
*/
import { colors } from "../../utils/colors.js";
import { Unit } from "../../utils/units.js";
import { priceLines } from "../constants.js";
import { line, price } from "../series.js";
/**
* @param {PercentilesPattern} p
* @param {(name: string) => string} [n]
* @returns {FetchedPriceSeriesBlueprint[]}
*/
function createCorePercentileSeries(p, n = (x) => x) {
return [
price({
metric: p.pct95,
name: n("p95"),
color: colors.pct._95,
defaultActive: false,
}),
price({
metric: p.pct90,
name: n("p90"),
color: colors.pct._90,
defaultActive: false,
}),
price({
metric: p.pct85,
name: n("p85"),
color: colors.pct._85,
defaultActive: false,
}),
price({
metric: p.pct80,
name: n("p80"),
color: colors.pct._80,
defaultActive: false,
}),
price({ metric: p.pct75, name: n("p75"), color: colors.pct._75 }),
price({
metric: p.pct70,
name: n("p70"),
color: colors.pct._70,
defaultActive: false,
}),
price({
metric: p.pct65,
name: n("p65"),
color: colors.pct._65,
defaultActive: false,
}),
price({
metric: p.pct60,
name: n("p60"),
color: colors.pct._60,
defaultActive: false,
}),
price({
metric: p.pct55,
name: n("p55"),
color: colors.pct._55,
defaultActive: false,
}),
price({ metric: p.pct50, name: n("p50"), color: colors.pct._50 }),
price({
metric: p.pct45,
name: n("p45"),
color: colors.pct._45,
defaultActive: false,
}),
price({
metric: p.pct40,
name: n("p40"),
color: colors.pct._40,
defaultActive: false,
}),
price({
metric: p.pct35,
name: n("p35"),
color: colors.pct._35,
defaultActive: false,
}),
price({
metric: p.pct30,
name: n("p30"),
color: colors.pct._30,
defaultActive: false,
}),
price({ metric: p.pct25, name: n("p25"), color: colors.pct._25 }),
price({
metric: p.pct20,
name: n("p20"),
color: colors.pct._20,
defaultActive: false,
}),
price({
metric: p.pct15,
name: n("p15"),
color: colors.pct._15,
defaultActive: false,
}),
price({
metric: p.pct10,
name: n("p10"),
color: colors.pct._10,
defaultActive: false,
}),
price({
metric: p.pct05,
name: n("p05"),
color: colors.pct._05,
defaultActive: false,
}),
];
}
/**
* @param {UtxoCohortObject | CohortWithoutRelative} cohort
* @returns {FetchedPriceSeriesBlueprint[]}
*/
function createSingleSummarySeriesBasic(cohort) {
const { color, tree } = cohort;
return [
price({ metric: tree.realized.realizedPrice, name: "Average", color }),
price({
metric: tree.costBasis.max,
name: "Max",
color: colors.pct._100,
defaultActive: false,
}),
price({
metric: tree.costBasis.min,
name: "Min",
color: colors.pct._0,
defaultActive: false,
}),
];
}
/**
* @param {CohortAll | CohortFull | CohortWithPercentiles} cohort
* @returns {FetchedPriceSeriesBlueprint[]}
*/
function createSingleSummarySeriesWithPercentiles(cohort) {
const { color, tree } = cohort;
const p = tree.costBasis.percentiles;
return [
price({ metric: tree.realized.realizedPrice, name: "Average", color }),
price({
metric: tree.costBasis.max,
name: "Max (p100)",
color: colors.pct._100,
defaultActive: false,
}),
price({
metric: p.pct75,
name: "Q3 (p75)",
color: colors.pct._75,
defaultActive: false,
}),
price({ metric: p.pct50, name: "Median (p50)", color: colors.pct._50 }),
price({
metric: p.pct25,
name: "Q1 (p25)",
color: colors.pct._25,
defaultActive: false,
}),
price({
metric: tree.costBasis.min,
name: "Min (p0)",
color: colors.pct._0,
defaultActive: false,
}),
];
}
/**
* @template {readonly { name: string, color: Color, tree: { realized: { realizedPrice: ActivePricePattern } } }[]} T
* @param {T} list
* @returns {FetchedPriceSeriesBlueprint[]}
*/
function createGroupedSummarySeries(list) {
return list.map(({ name, color, tree }) =>
price({ metric: tree.realized.realizedPrice, name, color }),
);
}
/**
* @param {CohortAll | CohortFull | CohortWithPercentiles} cohort
* @returns {FetchedPriceSeriesBlueprint[]}
*/
function createSingleByCoinSeries(cohort) {
const { color, tree } = cohort;
const cb = tree.costBasis;
return [
price({ metric: tree.realized.realizedPrice, name: "Average", color }),
price({
metric: cb.max,
name: "p100",
color: colors.pct._100,
defaultActive: false,
}),
...createCorePercentileSeries(cb.percentiles),
price({
metric: cb.min,
name: "p0",
color: colors.pct._0,
defaultActive: false,
}),
];
}
/**
* @param {CohortAll | CohortFull | CohortWithPercentiles} cohort
* @returns {FetchedPriceSeriesBlueprint[]}
*/
function createSingleByCapitalSeries(cohort) {
const { color, tree } = cohort;
return [
price({ metric: tree.realized.investorPrice, name: "Average", color }),
...createCorePercentileSeries(tree.costBasis.investedCapital),
];
}
/**
* @param {CohortAll | CohortFull | CohortWithPercentiles} cohort
* @returns {AnyFetchedSeriesBlueprint[]}
*/
function createSinglePricePositionSeries(cohort) {
const { tree } = cohort;
return [
line({
metric: tree.costBasis.spotCostBasisPercentile,
name: "By Coin",
color: colors.bitcoin,
unit: Unit.percentage,
}),
line({
metric: tree.costBasis.spotInvestedCapitalPercentile,
name: "By Capital",
color: colors.usd,
unit: Unit.percentage,
}),
...priceLines({ numbers: [100, 50, 0], unit: Unit.percentage }),
];
}
/**
* @param {{ cohort: UtxoCohortObject | CohortWithoutRelative, title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createCostBasisSection({ cohort, title }) {
return {
name: "Cost Basis",
tree: [
{
name: "Summary",
title: title("Cost Basis Summary"),
top: createSingleSummarySeriesBasic(cohort),
},
],
};
}
/**
* @param {{ cohort: CohortAll | CohortFull | CohortWithPercentiles, title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createCostBasisSectionWithPercentiles({ cohort, title }) {
return {
name: "Cost Basis",
tree: [
{
name: "Summary",
title: title("Cost Basis Summary"),
top: createSingleSummarySeriesWithPercentiles(cohort),
},
{
name: "By Coin",
title: title("Cost Basis Distribution (BTC-weighted)"),
top: createSingleByCoinSeries(cohort),
},
{
name: "By Capital",
title: title("Cost Basis Distribution (USD-weighted)"),
top: createSingleByCapitalSeries(cohort),
},
{
name: "Price Position",
title: title("Current Price Position"),
bottom: createSinglePricePositionSeries(cohort),
},
],
};
}
/**
* @template {readonly (UtxoCohortObject | CohortWithoutRelative)[]} T
* @param {{ list: T, title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createGroupedCostBasisSection({ list, title }) {
return {
name: "Cost Basis",
tree: [
{
name: "Summary",
title: title("Cost Basis Summary"),
top: createGroupedSummarySeries(list),
},
],
};
}
/**
* @param {{ list: readonly (CohortAll | CohortFull | CohortWithPercentiles)[], title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createGroupedCostBasisSectionWithPercentiles({ list, title }) {
return {
name: "Cost Basis",
tree: [
{
name: "Summary",
title: title("Cost Basis Summary"),
top: createGroupedSummarySeries(list),
},
{
name: "By Coin",
tree: [
{
name: "Average",
title: title("Realized Price Comparison"),
top: list.map(({ name, color, tree }) =>
price({ metric: tree.realized.realizedPrice, name, color }),
),
},
{
name: "Median",
title: title("Cost Basis Median (BTC-weighted)"),
top: list.map(({ name, color, tree }) =>
price({ metric: tree.costBasis.percentiles.pct50, name, color }),
),
},
{
name: "Q3",
title: title("Cost Basis Q3 (BTC-weighted)"),
top: list.map(({ name, color, tree }) =>
price({ metric: tree.costBasis.percentiles.pct75, name, color }),
),
},
{
name: "Q1",
title: title("Cost Basis Q1 (BTC-weighted)"),
top: list.map(({ name, color, tree }) =>
price({ metric: tree.costBasis.percentiles.pct25, name, color }),
),
},
],
},
{
name: "By Capital",
tree: [
{
name: "Average",
title: title("Investor Price Comparison"),
top: list.map(({ name, color, tree }) =>
price({ metric: tree.realized.investorPrice, name, color }),
),
},
{
name: "Median",
title: title("Cost Basis Median (USD-weighted)"),
top: list.map(({ name, color, tree }) =>
price({
metric: tree.costBasis.investedCapital.pct50,
name,
color,
}),
),
},
{
name: "Q3",
title: title("Cost Basis Q3 (USD-weighted)"),
top: list.map(({ name, color, tree }) =>
price({
metric: tree.costBasis.investedCapital.pct75,
name,
color,
}),
),
},
{
name: "Q1",
title: title("Cost Basis Q1 (USD-weighted)"),
top: list.map(({ name, color, tree }) =>
price({
metric: tree.costBasis.investedCapital.pct25,
name,
color,
}),
),
},
],
},
{
name: "Price Position",
tree: [
{
name: "By Coin",
title: title("Price Position (BTC-weighted)"),
bottom: [
...list.map(({ name, color, tree }) =>
line({
metric: tree.costBasis.spotCostBasisPercentile,
name,
color,
unit: Unit.percentage,
}),
),
...priceLines({ numbers: [100, 50, 0], unit: Unit.percentage }),
],
},
{
name: "By Capital",
title: title("Price Position (USD-weighted)"),
bottom: [
...list.map(({ name, color, tree }) =>
line({
metric: tree.costBasis.spotInvestedCapitalPercentile,
name,
color,
unit: Unit.percentage,
}),
),
...priceLines({ numbers: [100, 50, 0], unit: Unit.percentage }),
],
},
],
},
],
};
}
+1 -1
View File
@@ -44,7 +44,7 @@ export function buildCohortData() {
const cohortAll = {
name: "",
title: "",
color: colors.orange,
color: colors.bitcoin,
tree: utxoCohorts.all,
addrCount: addrCount.all,
};
@@ -0,0 +1,479 @@
/**
* Holdings section builders
*
* Structure (Option C - optimized for UX):
* - Supply: Total BTC held (flat, one click)
* - UTXO Count: Number of UTXOs (flat, one click)
* - Address Count: Number of addresses (when available, flat)
* - 30d Changes/: Folder for change metrics
* - Supply: 30d supply change
* - Relative: % of circulating supply (when available)
*
* Rationale: Most-used metrics (Supply, UTXO Count) are immediately accessible.
* 30d changes are grouped together for consistency and cleaner navigation.
*/
import { Unit } from "../../utils/units.js";
import { line, baseline } from "../series.js";
import { satsBtcUsd, satsBtcUsdBaseline } from "../shared.js";
/**
* @param {UtxoCohortObject | CohortWithoutRelative} cohort
* @returns {AnyFetchedSeriesBlueprint[]}
*/
function createSingleSupplySeries(cohort) {
const { color, tree } = cohort;
return [...satsBtcUsd({ pattern: tree.supply.total, name: "Supply", color })];
}
/**
* @param {UtxoCohortObject | CohortWithoutRelative} cohort
* @returns {AnyFetchedSeriesBlueprint[]}
*/
function createSingle30dChangeSeries(cohort) {
return satsBtcUsdBaseline({ pattern: cohort.tree.supply._30dChange, name: "30d Change" });
}
/**
* @param {UtxoCohortObject | CohortWithoutRelative} cohort
* @returns {AnyFetchedSeriesBlueprint[]}
*/
function createSingleUtxoCountSeries(cohort) {
const { color, tree } = cohort;
return [
line({
metric: tree.outputs.utxoCount,
name: "UTXO Count",
color,
unit: Unit.count,
}),
];
}
/**
* @param {UtxoCohortObject | CohortWithoutRelative} cohort
* @returns {AnyFetchedSeriesBlueprint[]}
*/
function createSingleUtxoCount30dChangeSeries(cohort) {
return [
baseline({
metric: cohort.tree.outputs.utxoCount30dChange,
name: "30d Change",
unit: Unit.count,
}),
];
}
/**
* @param {CohortAll | CohortAddress} cohort
* @returns {AnyFetchedSeriesBlueprint[]}
*/
function createSingleAddrCount30dChangeSeries(cohort) {
return [
baseline({
metric: cohort.addrCount._30dChange,
name: "30d Change",
unit: Unit.count,
}),
];
}
/**
* @param {CohortFull | CohortWithAdjusted | CohortBasicWithMarketCap | CohortMinAge} cohort
* @returns {AnyFetchedSeriesBlueprint[]}
*/
function createSingleRelativeSeries(cohort) {
const { color, tree } = cohort;
return [
line({
metric: tree.relative.supplyRelToCirculatingSupply,
name: "% of Circulating",
color,
unit: Unit.pctSupply,
}),
];
}
/**
* @param {{ cohort: UtxoCohortObject | CohortWithoutRelative, title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createHoldingsSection({ cohort, title }) {
return {
name: "Holdings",
tree: [
{
name: "Supply",
title: title("Supply"),
bottom: createSingleSupplySeries(cohort),
},
{
name: "UTXO Count",
title: title("UTXO Count"),
bottom: createSingleUtxoCountSeries(cohort),
},
{
name: "30d Changes",
tree: [
{
name: "Supply",
title: title("Supply 30d Change"),
bottom: createSingle30dChangeSeries(cohort),
},
{
name: "UTXO Count",
title: title("UTXO Count 30d Change"),
bottom: createSingleUtxoCount30dChangeSeries(cohort),
},
],
},
],
};
}
/**
* @param {{ cohort: CohortFull | CohortWithAdjusted | CohortBasicWithMarketCap | CohortMinAge, title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createHoldingsSectionWithRelative({ cohort, title }) {
return {
name: "Holdings",
tree: [
{
name: "Supply",
title: title("Supply"),
bottom: createSingleSupplySeries(cohort),
},
{
name: "UTXO Count",
title: title("UTXO Count"),
bottom: createSingleUtxoCountSeries(cohort),
},
{
name: "30d Changes",
tree: [
{
name: "Supply",
title: title("Supply 30d Change"),
bottom: createSingle30dChangeSeries(cohort),
},
{
name: "UTXO Count",
title: title("UTXO Count 30d Change"),
bottom: createSingleUtxoCount30dChangeSeries(cohort),
},
],
},
{
name: "Relative",
title: title("Relative to Circulating Supply"),
bottom: createSingleRelativeSeries(cohort),
},
],
};
}
/**
* @param {{ cohort: CohortAll, title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createHoldingsSectionAll({ cohort, title }) {
return {
name: "Holdings",
tree: [
{
name: "Supply",
title: title("Supply"),
bottom: createSingleSupplySeries(cohort),
},
{
name: "UTXO Count",
title: title("UTXO Count"),
bottom: createSingleUtxoCountSeries(cohort),
},
{
name: "Address Count",
title: title("Address Count"),
bottom: [
line({
metric: cohort.addrCount.count,
name: "Address Count",
color: cohort.color,
unit: Unit.count,
}),
],
},
{
name: "30d Changes",
tree: [
{
name: "Supply",
title: title("Supply 30d Change"),
bottom: createSingle30dChangeSeries(cohort),
},
{
name: "UTXO Count",
title: title("UTXO Count 30d Change"),
bottom: createSingleUtxoCount30dChangeSeries(cohort),
},
{
name: "Address Count",
title: title("Address Count 30d Change"),
bottom: createSingleAddrCount30dChangeSeries(cohort),
},
],
},
],
};
}
/**
* @param {{ cohort: CohortAddress, title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createHoldingsSectionAddress({ cohort, title }) {
return {
name: "Holdings",
tree: [
{
name: "Supply",
title: title("Supply"),
bottom: createSingleSupplySeries(cohort),
},
{
name: "UTXO Count",
title: title("UTXO Count"),
bottom: createSingleUtxoCountSeries(cohort),
},
{
name: "Address Count",
title: title("Address Count"),
bottom: [
line({
metric: cohort.addrCount.count,
name: "Address Count",
color: cohort.color,
unit: Unit.count,
}),
],
},
{
name: "30d Changes",
tree: [
{
name: "Supply",
title: title("Supply 30d Change"),
bottom: createSingle30dChangeSeries(cohort),
},
{
name: "UTXO Count",
title: title("UTXO Count 30d Change"),
bottom: createSingleUtxoCount30dChangeSeries(cohort),
},
{
name: "Address Count",
title: title("Address Count 30d Change"),
bottom: createSingleAddrCount30dChangeSeries(cohort),
},
],
},
],
};
}
/**
* @param {{ list: readonly CohortAddress[], title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createGroupedHoldingsSectionAddress({ list, title }) {
return {
name: "Holdings",
tree: [
{
name: "Supply",
title: title("Supply"),
bottom: list.flatMap(({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.supply.total, name, color }),
),
},
{
name: "UTXO Count",
title: title("UTXO Count"),
bottom: list.map(({ name, color, tree }) =>
line({
metric: tree.outputs.utxoCount,
name,
color,
unit: Unit.count,
}),
),
},
{
name: "Address Count",
title: title("Address Count"),
bottom: list.map(({ name, color, addrCount }) =>
line({ metric: addrCount.count, name, color, unit: Unit.count }),
),
},
{
name: "30d Changes",
tree: [
{
name: "Supply",
title: title("Supply 30d Change"),
bottom: list.flatMap(({ name, color, tree }) =>
satsBtcUsdBaseline({ pattern: tree.supply._30dChange, name, color }),
),
},
{
name: "UTXO Count",
title: title("UTXO Count 30d Change"),
bottom: list.map(({ name, color, tree }) =>
baseline({
metric: tree.outputs.utxoCount30dChange,
name,
unit: Unit.count,
color,
}),
),
},
{
name: "Address Count",
title: title("Address Count 30d Change"),
bottom: list.map(({ name, color, addrCount }) =>
baseline({
metric: addrCount._30dChange,
name,
unit: Unit.count,
color,
}),
),
},
],
},
],
};
}
/**
* @template {readonly (UtxoCohortObject | CohortWithoutRelative)[]} T
* @param {{ list: T, title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createGroupedHoldingsSection({ list, title }) {
return {
name: "Holdings",
tree: [
{
name: "Supply",
title: title("Supply"),
bottom: list.flatMap(({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.supply.total, name, color }),
),
},
{
name: "UTXO Count",
title: title("UTXO Count"),
bottom: list.map(({ name, color, tree }) =>
line({
metric: tree.outputs.utxoCount,
name,
color,
unit: Unit.count,
}),
),
},
{
name: "30d Changes",
tree: [
{
name: "Supply",
title: title("Supply 30d Change"),
bottom: list.flatMap(({ name, color, tree }) =>
satsBtcUsdBaseline({ pattern: tree.supply._30dChange, name, color }),
),
},
{
name: "UTXO Count",
title: title("UTXO Count 30d Change"),
bottom: list.map(({ name, color, tree }) =>
baseline({
metric: tree.outputs.utxoCount30dChange,
name,
unit: Unit.count,
color,
}),
),
},
],
},
],
};
}
/**
* @param {{ list: readonly (CohortFull | CohortWithAdjusted | CohortBasicWithMarketCap | CohortMinAge)[], title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createGroupedHoldingsSectionWithRelative({ list, title }) {
return {
name: "Holdings",
tree: [
{
name: "Supply",
title: title("Supply"),
bottom: list.flatMap(({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.supply.total, name, color }),
),
},
{
name: "UTXO Count",
title: title("UTXO Count"),
bottom: list.map(({ name, color, tree }) =>
line({
metric: tree.outputs.utxoCount,
name,
color,
unit: Unit.count,
}),
),
},
{
name: "30d Changes",
tree: [
{
name: "Supply",
title: title("Supply 30d Change"),
bottom: list.flatMap(({ name, color, tree }) =>
satsBtcUsdBaseline({ pattern: tree.supply._30dChange, name, color }),
),
},
{
name: "UTXO Count",
title: title("UTXO Count 30d Change"),
bottom: list.map(({ name, color, tree }) =>
baseline({
metric: tree.outputs.utxoCount30dChange,
name,
unit: Unit.count,
color,
}),
),
},
],
},
{
name: "Relative",
title: title("Relative to Circulating Supply"),
bottom: list.map(({ name, color, tree }) =>
line({
metric: tree.relative.supplyRelToCirculatingSupply,
name,
color,
unit: Unit.pctSupply,
}),
),
},
],
};
}
@@ -0,0 +1,225 @@
/**
* Prices section builders
*
* Structure (single cohort):
* - Compare: Both prices on one chart
* - Realized: Price + Ratio (MVRV) + Z-Scores (for full cohorts)
* - Investor: Price + Ratio + Z-Scores (for full cohorts)
*
* Structure (grouped cohorts):
* - Realized: Price + Ratio comparison across cohorts
* - Investor: Price + Ratio comparison across cohorts
*
* For cohorts WITHOUT full ratio patterns: basic Price/Ratio charts only (no Z-Scores)
*/
import { colors } from "../../utils/colors.js";
import { createPriceRatioCharts } from "../shared.js";
import { baseline, price } from "../series.js";
import { Unit } from "../../utils/units.js";
/**
* Create prices section for cohorts with full ActivePriceRatioPattern
* (CohortAll, CohortFull, CohortWithPercentiles)
* @param {{ cohort: CohortAll | CohortFull | CohortWithPercentiles, title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createPricesSectionFull({ cohort, title }) {
const { tree, color } = cohort;
return {
name: "Prices",
tree: [
{
name: "Compare",
title: title("Prices"),
top: [
price({
metric: tree.realized.realizedPrice,
name: "Realized",
color: colors.realized,
}),
price({
metric: tree.realized.investorPrice,
name: "Investor",
color: colors.investor,
}),
],
},
{
name: "Realized",
tree: createPriceRatioCharts({
context: cohort.name,
legend: "Realized",
pricePattern: tree.realized.realizedPrice,
ratio: tree.realized.realizedPriceExtra,
color,
priceTitle: title("Realized Price"),
titlePrefix: "Realized Price",
}),
},
{
name: "Investor",
tree: createPriceRatioCharts({
context: cohort.name,
legend: "Investor",
pricePattern: tree.realized.investorPrice,
ratio: tree.realized.investorPriceExtra,
color,
priceTitle: title("Investor Price"),
titlePrefix: "Investor Price",
}),
},
],
};
}
/**
* Create prices section for cohorts with basic ratio patterns only
* (CohortWithAdjusted, CohortBasic, CohortAddress, CohortWithoutRelative)
* @param {{ cohort: CohortWithAdjusted | CohortBasic | CohortAddress | CohortWithoutRelative, title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createPricesSectionBasic({ cohort, title }) {
const { tree, color } = cohort;
return {
name: "Prices",
tree: [
{
name: "Compare",
title: title("Prices"),
top: [
price({
metric: tree.realized.realizedPrice,
name: "Realized",
color: colors.realized,
}),
price({
metric: tree.realized.investorPrice,
name: "Investor",
color: colors.investor,
}),
],
},
{
name: "Realized",
tree: [
{
name: "Price",
title: title("Realized Price"),
top: [
price({
metric: tree.realized.realizedPrice,
name: "Realized",
color,
}),
],
},
{
name: "Ratio",
title: title("Realized Price Ratio"),
bottom: [
baseline({
metric: tree.realized.realizedPriceExtra.ratio,
name: "Ratio",
unit: Unit.ratio,
base: 1,
}),
],
},
],
},
{
name: "Investor",
tree: [
{
name: "Price",
title: title("Investor Price"),
top: [
price({
metric: tree.realized.investorPrice,
name: "Investor",
color,
}),
],
},
{
name: "Ratio",
title: title("Investor Price Ratio"),
bottom: [
baseline({
metric: tree.realized.investorPriceExtra.ratio,
name: "Ratio",
unit: Unit.ratio,
base: 1,
}),
],
},
],
},
],
};
}
/**
* Create prices section for grouped cohorts
* @template {readonly (CohortAll | CohortFull | CohortWithPercentiles | CohortWithAdjusted | CohortBasic | CohortAddress | CohortWithoutRelative)[]} T
* @param {{ list: T, title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createGroupedPricesSection({ list, title }) {
return {
name: "Prices",
tree: [
{
name: "Realized",
tree: [
{
name: "Price",
title: title("Realized Price"),
top: list.map(({ name, color, tree }) =>
price({ metric: tree.realized.realizedPrice, name, color }),
),
},
{
name: "Ratio",
title: title("Realized Price Ratio"),
bottom: list.map(({ name, color, tree }) =>
baseline({
metric: tree.realized.realizedPriceExtra.ratio,
name,
color,
unit: Unit.ratio,
base: 1,
}),
),
},
],
},
{
name: "Investor",
tree: [
{
name: "Price",
title: title("Investor Price"),
top: list.map(({ name, color, tree }) =>
price({ metric: tree.realized.investorPrice, name, color }),
),
},
{
name: "Ratio",
title: title("Investor Price Ratio"),
bottom: list.map(({ name, color, tree }) =>
baseline({
metric: tree.realized.investorPriceExtra.ratio,
name,
color,
unit: Unit.ratio,
base: 1,
}),
),
},
],
},
],
};
}
+73 -86
View File
@@ -176,17 +176,17 @@ function createSingleSupplySeriesBase(cohort) {
...satsBtcUsd({
pattern: tree.supply._30dChange,
name: "30d Change",
color: colors.orange,
color: colors.bitcoin,
}),
...satsBtcUsd({
pattern: tree.unrealized.supplyInProfit,
name: "In Profit",
color: colors.green,
color: colors.profit,
}),
...satsBtcUsd({
pattern: tree.unrealized.supplyInLoss,
name: "In Loss",
color: colors.red,
color: colors.loss,
}),
...satsBtcUsd({
pattern: tree.supply.halved,
@@ -211,13 +211,13 @@ function createSingleSupplyRelativeToOwnMetrics(cohort) {
line({
metric: tree.relative.supplyInProfitRelToOwnSupply,
name: "In Profit",
color: colors.green,
color: colors.profit,
unit: Unit.pctOwn,
}),
line({
metric: tree.relative.supplyInLossRelToOwnSupply,
name: "In Loss",
color: colors.red,
color: colors.loss,
unit: Unit.pctOwn,
}),
priceLine({
@@ -403,13 +403,13 @@ export function createSupplyPnlRelativeToCirculatingSeries(cohort) {
line({
metric: cohort.tree.relative.supplyInProfitRelToCirculatingSupply,
name: "In Profit",
color: colors.green,
color: colors.profit,
unit: Unit.pctSupply,
}),
line({
metric: cohort.tree.relative.supplyInLossRelToCirculatingSupply,
name: "In Loss",
color: colors.red,
color: colors.loss,
unit: Unit.pctSupply,
}),
];
@@ -513,7 +513,7 @@ export function createAddressCountSeries(list, useGroupName) {
line({
metric: tree.addrCount,
name: useGroupName ? name : "Count",
color: useGroupName ? color : colors.orange,
color: useGroupName ? color : colors.bitcoin,
unit: Unit.count,
}),
]);
@@ -566,12 +566,11 @@ export function createRealizedCapSeries(list, useGroupName) {
/**
* Create cost basis percentile series (only for cohorts with CostBasisPattern2)
* Includes min (p0) and max (p100) with full rainbow coloring
* @param {Colors} colors
* @param {readonly CohortWithCostBasisPercentiles[]} list
* @param {boolean} useGroupName
* @returns {FetchedPriceSeriesBlueprint[]}
*/
export function createCostBasisPercentilesSeries(colors, list, useGroupName) {
export function createCostBasisPercentilesSeries(list, useGroupName) {
return list.flatMap(({ name, tree }) => {
const cb = tree.costBasis;
const p = cb.percentiles;
@@ -581,122 +580,122 @@ export function createCostBasisPercentilesSeries(colors, list, useGroupName) {
price({
metric: cb.max,
name: n(100),
color: colors.purple,
color: colors.pct._100,
defaultActive: false,
}),
price({
metric: p.pct95,
name: n(95),
color: colors.fuchsia,
color: colors.pct._95,
defaultActive: false,
}),
price({
metric: p.pct90,
name: n(90),
color: colors.pink,
color: colors.pct._90,
defaultActive: false,
}),
price({
metric: p.pct85,
name: n(85),
color: colors.pink,
color: colors.pct._85,
defaultActive: false,
}),
price({
metric: p.pct80,
name: n(80),
color: colors.rose,
color: colors.pct._80,
defaultActive: false,
}),
price({
metric: p.pct75,
name: n(75),
color: colors.red,
color: colors.pct._75,
defaultActive: false,
}),
price({
metric: p.pct70,
name: n(70),
color: colors.orange,
color: colors.pct._70,
defaultActive: false,
}),
price({
metric: p.pct65,
name: n(65),
color: colors.amber,
color: colors.pct._65,
defaultActive: false,
}),
price({
metric: p.pct60,
name: n(60),
color: colors.yellow,
color: colors.pct._60,
defaultActive: false,
}),
price({
metric: p.pct55,
name: n(55),
color: colors.yellow,
color: colors.pct._55,
defaultActive: false,
}),
price({ metric: p.pct50, name: n(50), color: colors.avocado }),
price({ metric: p.pct50, name: n(50), color: colors.pct._50 }),
price({
metric: p.pct45,
name: n(45),
color: colors.lime,
color: colors.pct._45,
defaultActive: false,
}),
price({
metric: p.pct40,
name: n(40),
color: colors.green,
color: colors.pct._40,
defaultActive: false,
}),
price({
metric: p.pct35,
name: n(35),
color: colors.emerald,
color: colors.pct._35,
defaultActive: false,
}),
price({
metric: p.pct30,
name: n(30),
color: colors.teal,
color: colors.pct._30,
defaultActive: false,
}),
price({
metric: p.pct25,
name: n(25),
color: colors.teal,
color: colors.pct._25,
defaultActive: false,
}),
price({
metric: p.pct20,
name: n(20),
color: colors.cyan,
color: colors.pct._20,
defaultActive: false,
}),
price({
metric: p.pct15,
name: n(15),
color: colors.sky,
color: colors.pct._15,
defaultActive: false,
}),
price({
metric: p.pct10,
name: n(10),
color: colors.blue,
color: colors.pct._10,
defaultActive: false,
}),
price({
metric: p.pct05,
name: n(5),
color: colors.indigo,
color: colors.pct._05,
defaultActive: false,
}),
price({
metric: cb.min,
name: n(0),
color: colors.violet,
color: colors.pct._0,
defaultActive: false,
}),
];
@@ -706,16 +705,11 @@ export function createCostBasisPercentilesSeries(colors, list, useGroupName) {
/**
* Create invested capital percentile series (only for cohorts with CostBasisPattern2)
* Shows invested capital at each percentile level
* @param {Colors} colors
* @param {readonly CohortWithCostBasisPercentiles[]} list
* @param {boolean} useGroupName
* @returns {FetchedPriceSeriesBlueprint[]}
*/
export function createInvestedCapitalPercentilesSeries(
colors,
list,
useGroupName,
) {
export function createInvestedCapitalPercentilesSeries(list, useGroupName) {
return list.flatMap(({ name, tree }) => {
const ic = tree.costBasis.investedCapital;
const n = (/** @type {number} */ pct) =>
@@ -724,110 +718,110 @@ export function createInvestedCapitalPercentilesSeries(
price({
metric: ic.pct95,
name: n(95),
color: colors.fuchsia,
color: colors.pct._95,
defaultActive: false,
}),
price({
metric: ic.pct90,
name: n(90),
color: colors.pink,
color: colors.pct._90,
defaultActive: false,
}),
price({
metric: ic.pct85,
name: n(85),
color: colors.pink,
color: colors.pct._85,
defaultActive: false,
}),
price({
metric: ic.pct80,
name: n(80),
color: colors.rose,
color: colors.pct._80,
defaultActive: false,
}),
price({
metric: ic.pct75,
name: n(75),
color: colors.red,
color: colors.pct._75,
defaultActive: false,
}),
price({
metric: ic.pct70,
name: n(70),
color: colors.orange,
color: colors.pct._70,
defaultActive: false,
}),
price({
metric: ic.pct65,
name: n(65),
color: colors.amber,
color: colors.pct._65,
defaultActive: false,
}),
price({
metric: ic.pct60,
name: n(60),
color: colors.yellow,
color: colors.pct._60,
defaultActive: false,
}),
price({
metric: ic.pct55,
name: n(55),
color: colors.yellow,
color: colors.pct._55,
defaultActive: false,
}),
price({ metric: ic.pct50, name: n(50), color: colors.avocado }),
price({ metric: ic.pct50, name: n(50), color: colors.pct._50 }),
price({
metric: ic.pct45,
name: n(45),
color: colors.lime,
color: colors.pct._45,
defaultActive: false,
}),
price({
metric: ic.pct40,
name: n(40),
color: colors.green,
color: colors.pct._40,
defaultActive: false,
}),
price({
metric: ic.pct35,
name: n(35),
color: colors.emerald,
color: colors.pct._35,
defaultActive: false,
}),
price({
metric: ic.pct30,
name: n(30),
color: colors.teal,
color: colors.pct._30,
defaultActive: false,
}),
price({
metric: ic.pct25,
name: n(25),
color: colors.teal,
color: colors.pct._25,
defaultActive: false,
}),
price({
metric: ic.pct20,
name: n(20),
color: colors.cyan,
color: colors.pct._20,
defaultActive: false,
}),
price({
metric: ic.pct15,
name: n(15),
color: colors.sky,
color: colors.pct._15,
defaultActive: false,
}),
price({
metric: ic.pct10,
name: n(10),
color: colors.blue,
color: colors.pct._10,
defaultActive: false,
}),
price({
metric: ic.pct05,
name: n(5),
color: colors.indigo,
color: colors.pct._05,
defaultActive: false,
}),
];
@@ -836,12 +830,11 @@ export function createInvestedCapitalPercentilesSeries(
/**
* Create spot percentile series (shows current percentile of price relative to cost basis/invested capital)
* @param {Colors} colors
* @param {readonly CohortWithCostBasisPercentiles[]} list
* @param {boolean} useGroupName
* @returns {FetchedBaselineSeriesBlueprint[]}
*/
export function createSpotPercentileSeries(colors, list, useGroupName) {
export function createSpotPercentileSeries(list, useGroupName) {
return list.flatMap(({ name, color, tree }) => [
baseline({
metric: tree.costBasis.spotCostBasisPercentile,
@@ -852,7 +845,7 @@ export function createSpotPercentileSeries(colors, list, useGroupName) {
baseline({
metric: tree.costBasis.spotInvestedCapitalPercentile,
name: useGroupName ? `${name} Invested Capital` : "Invested Capital",
color: useGroupName ? color : colors.orange,
color: useGroupName ? color : colors.bitcoin,
unit: Unit.ratio,
defaultActive: false,
}),
@@ -990,28 +983,27 @@ export function createSingleSentSeries(cohort) {
/**
* Create sell side risk ratio series for single cohort
* @param {Colors} colors
* @param {{ realized: AnyRealizedPattern }} tree
* @returns {AnyFetchedSeriesBlueprint[]}
*/
export function createSingleSellSideRiskSeries(colors, tree) {
export function createSingleSellSideRiskSeries(tree) {
return [
dots({
metric: tree.realized.sellSideRiskRatio,
name: "Raw",
color: colors.orange,
color: colors.bitcoin,
unit: Unit.ratio,
}),
line({
metric: tree.realized.sellSideRiskRatio7dEma,
name: "7d EMA",
color: colors.red,
color: colors.ma._1w,
unit: Unit.ratio,
}),
line({
metric: tree.realized.sellSideRiskRatio30dEma,
name: "30d EMA",
color: colors.pink,
color: colors.ma._1m,
unit: Unit.ratio,
}),
];
@@ -1039,22 +1031,21 @@ export function createGroupedSellSideRiskSeries(list) {
/**
* Create value created & destroyed series for single cohort
* @param {Colors} colors
* @param {{ realized: AnyRealizedPattern }} tree
* @returns {AnyFetchedSeriesBlueprint[]}
*/
export function createSingleValueCreatedDestroyedSeries(colors, tree) {
export function createSingleValueCreatedDestroyedSeries(tree) {
return [
line({
metric: tree.realized.valueCreated,
name: "Created",
color: colors.emerald,
color: colors.usd,
unit: Unit.usd,
}),
line({
metric: tree.realized.valueDestroyed,
name: "Destroyed",
color: colors.red,
color: colors.loss,
unit: Unit.usd,
}),
];
@@ -1063,36 +1054,35 @@ export function createSingleValueCreatedDestroyedSeries(colors, tree) {
/**
* Create profit/loss value breakdown series for single cohort
* Shows profit value created/destroyed and loss value created/destroyed
* @param {Colors} colors
* @param {{ realized: AnyRealizedPattern }} tree
* @returns {AnyFetchedSeriesBlueprint[]}
*/
export function createSingleValueFlowBreakdownSeries(colors, tree) {
export function createSingleValueFlowBreakdownSeries(tree) {
return [
line({
metric: tree.realized.profitValueCreated,
name: "Profit Created",
color: colors.green,
color: colors.profit,
unit: Unit.usd,
}),
line({
metric: tree.realized.profitValueDestroyed,
name: "Profit Destroyed",
color: colors.lime,
color: colors.loss,
unit: Unit.usd,
defaultActive: false,
}),
line({
metric: tree.realized.lossValueCreated,
name: "Loss Created",
color: colors.orange,
color: colors.bitcoin,
unit: Unit.usd,
defaultActive: false,
}),
line({
metric: tree.realized.lossValueDestroyed,
name: "Loss Destroyed",
color: colors.red,
color: colors.loss,
unit: Unit.usd,
}),
];
@@ -1100,22 +1090,21 @@ export function createSingleValueFlowBreakdownSeries(colors, tree) {
/**
* Create capitulation & profit flow series for single cohort
* @param {Colors} colors
* @param {{ realized: AnyRealizedPattern }} tree
* @returns {AnyFetchedSeriesBlueprint[]}
*/
export function createSingleCapitulationProfitFlowSeries(colors, tree) {
export function createSingleCapitulationProfitFlowSeries(tree) {
return [
line({
metric: tree.realized.profitFlow,
name: "Profit Flow",
color: colors.green,
color: colors.profit,
unit: Unit.usd,
}),
line({
metric: tree.realized.capitulationFlow,
name: "Capitulation Flow",
color: colors.red,
color: colors.loss,
unit: Unit.usd,
}),
];
@@ -1127,11 +1116,10 @@ export function createSingleCapitulationProfitFlowSeries(colors, tree) {
/**
* 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) {
export function createSingleSoprSeries(tree) {
return [
baseline({
metric: tree.realized.sopr,
@@ -1142,7 +1130,7 @@ export function createSingleSoprSeries(colors, tree) {
baseline({
metric: tree.realized.sopr7dEma,
name: "7d EMA",
color: [colors.lime, colors.rose],
color: colors.bi.sopr7d,
unit: Unit.ratio,
defaultActive: false,
base: 1,
@@ -1150,7 +1138,7 @@ export function createSingleSoprSeries(colors, tree) {
baseline({
metric: tree.realized.sopr30dEma,
name: "30d EMA",
color: [colors.avocado, colors.pink],
color: colors.bi.sopr30d,
unit: Unit.ratio,
defaultActive: false,
base: 1,
@@ -1337,11 +1325,10 @@ export function createGroupedRealizedAthRegretSeries(list) {
/**
* Create sentiment series for single cohort
* @param {Colors} colors
* @param {{ unrealized: UnrealizedPattern }} tree
* @returns {AnyFetchedSeriesBlueprint[]}
*/
export function createSingleSentimentSeries(colors, tree) {
export function createSingleSentimentSeries(tree) {
return [
baseline({
metric: tree.unrealized.netSentiment,
@@ -1351,13 +1338,13 @@ export function createSingleSentimentSeries(colors, tree) {
line({
metric: tree.unrealized.greedIndex,
name: "Greed Index",
color: colors.green,
color: colors.profit,
unit: Unit.usd,
}),
line({
metric: tree.unrealized.painIndex,
name: "Pain Index",
color: colors.red,
color: colors.loss,
unit: Unit.usd,
}),
];
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,165 @@
/**
* Valuation section builders
*
* Structure:
* - Realized Cap: Total value at cost basis (USD)
* - 30d Change: Recent realized cap changes
* - MVRV: Market Value to Realized Value ratio
*
* For cohorts WITH full ratio patterns: MVRV uses createRatioChart (price + percentiles)
* For cohorts WITHOUT full ratio patterns: MVRV is simple baseline
*/
import { Unit } from "../../utils/units.js";
import { line, baseline } from "../series.js";
import { createRatioChart } from "../shared.js";
/**
* @param {UtxoCohortObject | CohortWithoutRelative} cohort
* @returns {AnyFetchedSeriesBlueprint[]}
*/
function createSingleRealizedCapSeries(cohort) {
const { color, tree } = cohort;
return [
line({
metric: tree.realized.realizedCap,
name: "Realized Cap",
color,
unit: Unit.usd,
}),
];
}
/**
* @param {UtxoCohortObject | CohortWithoutRelative} cohort
* @returns {AnyFetchedSeriesBlueprint[]}
*/
function createSingle30dChangeSeries(cohort) {
return [
baseline({
metric: cohort.tree.realized.realizedCap30dDelta,
name: "30d Change",
unit: Unit.usd,
}),
];
}
/**
* Create valuation section for cohorts with full ratio patterns
* (CohortAll, CohortFull, CohortWithPercentiles)
* @param {{ cohort: CohortAll | CohortFull | CohortWithPercentiles, title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createValuationSectionFull({ cohort, title }) {
const { tree, color } = cohort;
return {
name: "Valuation",
tree: [
{
name: "Realized Cap",
title: title("Realized Cap"),
bottom: createSingleRealizedCapSeries(cohort),
},
{
name: "30d Change",
title: title("Realized Cap 30d Change"),
bottom: createSingle30dChangeSeries(cohort),
},
createRatioChart({
title,
pricePattern: tree.realized.realizedPrice,
ratio: tree.realized.realizedPriceExtra,
color,
name: "MVRV",
}),
],
};
}
/**
* Create valuation section for cohorts with basic ratio patterns
* (CohortWithAdjusted, CohortBasic, CohortAddress, CohortWithoutRelative)
* @param {{ cohort: CohortWithAdjusted | CohortBasic | CohortAddress | CohortWithoutRelative, title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createValuationSection({ cohort, title }) {
const { tree, color } = cohort;
return {
name: "Valuation",
tree: [
{
name: "Realized Cap",
title: title("Realized Cap"),
bottom: createSingleRealizedCapSeries(cohort),
},
{
name: "30d Change",
title: title("Realized Cap 30d Change"),
bottom: createSingle30dChangeSeries(cohort),
},
{
name: "MVRV",
title: title("MVRV"),
bottom: [
baseline({
metric: tree.realized.realizedPriceExtra.ratio,
name: "MVRV",
color,
unit: Unit.ratio,
base: 1,
}),
],
},
],
};
}
/**
* @template {readonly (UtxoCohortObject | CohortWithoutRelative)[]} T
* @param {{ list: T, title: (metric: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createGroupedValuationSection({ list, title }) {
return {
name: "Valuation",
tree: [
{
name: "Realized Cap",
title: title("Realized Cap"),
bottom: list.map(({ name, color, tree }) =>
line({
metric: tree.realized.realizedCap,
name,
color,
unit: Unit.usd,
}),
),
},
{
name: "30d Change",
title: title("Realized Cap 30d Change"),
bottom: list.map(({ name, color, tree }) =>
baseline({
metric: tree.realized.realizedCap30dDelta,
name,
color,
unit: Unit.usd,
}),
),
},
{
name: "MVRV",
title: title("MVRV"),
bottom: list.map(({ name, color, tree }) =>
baseline({
metric: tree.realized.realizedPriceExtra.ratio,
name,
color,
unit: Unit.ratio,
base: 1,
}),
),
},
],
};
}