global: sats version of all prices

This commit is contained in:
nym21
2026-01-26 15:04:45 +01:00
parent f066fcda32
commit 3d01822d27
53 changed files with 2843 additions and 1688 deletions

View File

@@ -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),
},

View File

@@ -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,
}),
];
}

View File

@@ -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) {
],
});
}