mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-24 14:49:58 -07:00
website: snapshot
This commit is contained in:
803
website/scripts/options/distribution/activity.js
Normal file
803
website/scripts/options/distribution/activity.js
Normal file
@@ -0,0 +1,803 @@
|
||||
/**
|
||||
* Activity section builders
|
||||
*
|
||||
* Structure:
|
||||
* - Volume: Sent volume (Sum, Cumulative, 14d EMA)
|
||||
* - SOPR: Spent Output Profit Ratio (30d > 7d > raw)
|
||||
* - Sell Side Risk: Risk ratio
|
||||
* - Value: Flows, Created & Destroyed, Breakdown
|
||||
* - Coins Destroyed: Coinblocks/Coindays (Sum, Cumulative)
|
||||
*
|
||||
* For cohorts WITH adjusted values: Additional Normal/Adjusted sub-sections
|
||||
*/
|
||||
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { line, baseline, dotsBaseline } from "../series.js";
|
||||
import { satsBtcUsd } from "../shared.js";
|
||||
import { colors } from "../../utils/colors.js";
|
||||
import {
|
||||
createSingleSellSideRiskSeries,
|
||||
createGroupedSellSideRiskSeries,
|
||||
createSingleValueCreatedDestroyedSeries,
|
||||
createSingleCapitulationProfitFlowSeries,
|
||||
} from "./shared.js";
|
||||
|
||||
// ============================================================================
|
||||
// Shared Helpers
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Create SOPR series from realized pattern (30d > 7d > raw order)
|
||||
* @param {{ sopr: AnyMetricPattern, sopr7dEma: AnyMetricPattern, sopr30dEma: AnyMetricPattern }} realized
|
||||
* @param {string} rawName - Name for the raw SOPR series
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
function soprSeries(realized, rawName = "SOPR") {
|
||||
return [
|
||||
baseline({
|
||||
metric: realized.sopr30dEma,
|
||||
name: "30d EMA",
|
||||
color: colors.bi.p3,
|
||||
unit: Unit.ratio,
|
||||
base: 1,
|
||||
}),
|
||||
baseline({
|
||||
metric: realized.sopr7dEma,
|
||||
name: "7d EMA",
|
||||
color: colors.bi.p2,
|
||||
unit: Unit.ratio,
|
||||
base: 1,
|
||||
}),
|
||||
dotsBaseline({
|
||||
metric: realized.sopr,
|
||||
name: rawName,
|
||||
color: colors.bi.p1,
|
||||
unit: Unit.ratio,
|
||||
base: 1,
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create grouped SOPR chart entries (Raw, 7d EMA, 30d EMA)
|
||||
* @template {{ color: Color, name: string }} T
|
||||
* @param {readonly T[]} list
|
||||
* @param {(item: T) => AnyMetricPattern} getSopr
|
||||
* @param {(item: T) => AnyMetricPattern} getSopr7d
|
||||
* @param {(item: T) => AnyMetricPattern} getSopr30d
|
||||
* @param {(metric: string) => string} title
|
||||
* @param {string} titlePrefix
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function groupedSoprCharts(
|
||||
list,
|
||||
getSopr,
|
||||
getSopr7d,
|
||||
getSopr30d,
|
||||
title,
|
||||
titlePrefix,
|
||||
) {
|
||||
return [
|
||||
{
|
||||
name: "Raw",
|
||||
title: title(`${titlePrefix}SOPR`),
|
||||
bottom: list.map((item) =>
|
||||
baseline({
|
||||
metric: getSopr(item),
|
||||
name: item.name,
|
||||
color: item.color,
|
||||
unit: Unit.ratio,
|
||||
base: 1,
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "7d EMA",
|
||||
title: title(`${titlePrefix}SOPR 7d EMA`),
|
||||
bottom: list.map((item) =>
|
||||
baseline({
|
||||
metric: getSopr7d(item),
|
||||
name: item.name,
|
||||
color: item.color,
|
||||
unit: Unit.ratio,
|
||||
base: 1,
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "30d EMA",
|
||||
title: title(`${titlePrefix}SOPR 30d EMA`),
|
||||
bottom: list.map((item) =>
|
||||
baseline({
|
||||
metric: getSopr30d(item),
|
||||
name: item.name,
|
||||
color: item.color,
|
||||
unit: Unit.ratio,
|
||||
base: 1,
|
||||
}),
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create value breakdown tree (Profit/Loss Created/Destroyed)
|
||||
* @template {{ color: Color, name: string, tree: { realized: AnyRealizedPattern } }} T
|
||||
* @param {readonly T[]} list
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function valueBreakdownTree(list, title) {
|
||||
return [
|
||||
{
|
||||
name: "Profit",
|
||||
tree: [
|
||||
{
|
||||
name: "Created",
|
||||
title: title("Profit Value Created"),
|
||||
bottom: list.map(({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.realized.profitValueCreated,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Destroyed",
|
||||
title: title("Profit Value Destroyed"),
|
||||
bottom: list.map(({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.realized.profitValueDestroyed,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Loss",
|
||||
tree: [
|
||||
{
|
||||
name: "Created",
|
||||
title: title("Loss Value Created"),
|
||||
bottom: list.map(({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.realized.lossValueCreated,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Destroyed",
|
||||
title: title("Loss Value Destroyed"),
|
||||
bottom: list.map(({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.realized.lossValueDestroyed,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create coins destroyed tree (Sum/Cumulative with Coinblocks/Coindays)
|
||||
* @template {{ color: Color, name: string, tree: { activity: { coinblocksDestroyed: CountPattern<any>, coindaysDestroyed: CountPattern<any> } } }} T
|
||||
* @param {readonly T[]} list
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function coinsDestroyedTree(list, title) {
|
||||
return [
|
||||
{
|
||||
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,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
{
|
||||
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,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// SOPR Helpers
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Create SOPR series for single cohort (30d > 7d > raw order)
|
||||
* @param {UtxoCohortObject | CohortWithoutRelative} cohort
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
function createSingleSoprSeries(cohort) {
|
||||
return soprSeries(cohort.tree.realized);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create SOPR tree with normal and adjusted sub-sections
|
||||
* @param {CohortAll | CohortFull | CohortWithAdjusted} cohort
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function createSingleSoprTreeWithAdjusted(cohort, title) {
|
||||
const { realized } = cohort.tree;
|
||||
return [
|
||||
{
|
||||
name: "Normal",
|
||||
title: title("SOPR"),
|
||||
bottom: soprSeries(realized),
|
||||
},
|
||||
{
|
||||
name: "Adjusted",
|
||||
title: title("Adjusted SOPR"),
|
||||
bottom: soprSeries(
|
||||
{
|
||||
sopr: realized.adjustedSopr,
|
||||
sopr7dEma: realized.adjustedSopr7dEma,
|
||||
sopr30dEma: realized.adjustedSopr30dEma,
|
||||
},
|
||||
"Adjusted SOPR",
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create grouped SOPR tree with separate charts for each variant
|
||||
* @template {readonly (UtxoCohortObject | CohortWithoutRelative)[]} T
|
||||
* @param {T} list
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function createGroupedSoprTree(list, title) {
|
||||
return groupedSoprCharts(
|
||||
list,
|
||||
(c) => c.tree.realized.sopr,
|
||||
(c) => c.tree.realized.sopr7dEma,
|
||||
(c) => c.tree.realized.sopr30dEma,
|
||||
title,
|
||||
"",
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create grouped SOPR tree with Normal and Adjusted sub-sections
|
||||
* @param {readonly (CohortAll | CohortFull | CohortWithAdjusted)[]} list
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function createGroupedSoprTreeWithAdjusted(list, title) {
|
||||
return [
|
||||
{
|
||||
name: "Normal",
|
||||
tree: groupedSoprCharts(
|
||||
list,
|
||||
(c) => c.tree.realized.sopr,
|
||||
(c) => c.tree.realized.sopr7dEma,
|
||||
(c) => c.tree.realized.sopr30dEma,
|
||||
title,
|
||||
"",
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Adjusted",
|
||||
tree: groupedSoprCharts(
|
||||
list,
|
||||
(c) => c.tree.realized.adjustedSopr,
|
||||
(c) => c.tree.realized.adjustedSopr7dEma,
|
||||
(c) => c.tree.realized.adjustedSopr30dEma,
|
||||
title,
|
||||
"Adjusted ",
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Single Cohort Activity Section
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Base activity section builder for single cohorts
|
||||
* @param {Object} args
|
||||
* @param {UtxoCohortObject | CohortWithoutRelative} args.cohort
|
||||
* @param {(metric: string) => string} args.title
|
||||
* @param {AnyFetchedSeriesBlueprint[]} [args.valueMetrics] - Optional additional value metrics
|
||||
* @param {PartialOptionsTree} [args.soprTree] - Optional SOPR tree override
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createActivitySection({
|
||||
cohort,
|
||||
title,
|
||||
valueMetrics = [],
|
||||
soprTree,
|
||||
}) {
|
||||
const { tree, color } = cohort;
|
||||
|
||||
return {
|
||||
name: "Activity",
|
||||
tree: [
|
||||
{
|
||||
name: "Volume",
|
||||
tree: [
|
||||
{
|
||||
name: "Sum",
|
||||
title: title("Sent Volume"),
|
||||
bottom: [
|
||||
line({
|
||||
metric: tree.activity.sent14dEma.sats,
|
||||
name: "14d EMA",
|
||||
color: colors.ma._14d,
|
||||
unit: Unit.sats,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: tree.activity.sent14dEma.bitcoin,
|
||||
name: "14d EMA",
|
||||
color: colors.ma._14d,
|
||||
unit: Unit.btc,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: tree.activity.sent14dEma.dollars,
|
||||
name: "14d EMA",
|
||||
color: colors.ma._14d,
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: tree.activity.sent.sats.sum,
|
||||
name: "sum",
|
||||
color,
|
||||
unit: Unit.sats,
|
||||
}),
|
||||
line({
|
||||
metric: tree.activity.sent.bitcoin.sum,
|
||||
name: "sum",
|
||||
color,
|
||||
unit: Unit.btc,
|
||||
}),
|
||||
line({
|
||||
metric: tree.activity.sent.dollars.sum,
|
||||
name: "sum",
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: title("Sent Volume (Total)"),
|
||||
bottom: [
|
||||
line({
|
||||
metric: tree.activity.sent.sats.cumulative,
|
||||
name: "all-time",
|
||||
color,
|
||||
unit: Unit.sats,
|
||||
}),
|
||||
line({
|
||||
metric: tree.activity.sent.bitcoin.cumulative,
|
||||
name: "all-time",
|
||||
color,
|
||||
unit: Unit.btc,
|
||||
}),
|
||||
line({
|
||||
metric: tree.activity.sent.dollars.cumulative,
|
||||
name: "all-time",
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
soprTree
|
||||
? { name: "SOPR", tree: soprTree }
|
||||
: {
|
||||
name: "SOPR",
|
||||
title: title("SOPR"),
|
||||
bottom: createSingleSoprSeries(cohort),
|
||||
},
|
||||
{
|
||||
name: "Sell Side Risk",
|
||||
title: title("Sell Side Risk Ratio"),
|
||||
bottom: createSingleSellSideRiskSeries(tree),
|
||||
},
|
||||
{
|
||||
name: "Value",
|
||||
tree: [
|
||||
{
|
||||
name: "Flows",
|
||||
title: title("Profit & Capitulation Flows"),
|
||||
bottom: createSingleCapitulationProfitFlowSeries(tree),
|
||||
},
|
||||
{
|
||||
name: "Created & Destroyed",
|
||||
title: title("Value Created & Destroyed"),
|
||||
bottom: [
|
||||
...createSingleValueCreatedDestroyedSeries(tree),
|
||||
...valueMetrics,
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Breakdown",
|
||||
tree: [
|
||||
{
|
||||
name: "Profit",
|
||||
title: title("Profit Value Created & Destroyed"),
|
||||
bottom: [
|
||||
line({
|
||||
metric: tree.realized.profitValueCreated,
|
||||
name: "Created",
|
||||
color: colors.profit,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.profitValueDestroyed,
|
||||
name: "Destroyed",
|
||||
color: colors.loss,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Loss",
|
||||
title: title("Loss Value Created & Destroyed"),
|
||||
bottom: [
|
||||
line({
|
||||
metric: tree.realized.lossValueCreated,
|
||||
name: "Created",
|
||||
color: colors.profit,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.lossValueDestroyed,
|
||||
name: "Destroyed",
|
||||
color: colors.loss,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Coins Destroyed",
|
||||
tree: [
|
||||
{
|
||||
name: "Sum",
|
||||
title: title("Coins Destroyed"),
|
||||
bottom: [
|
||||
line({
|
||||
metric: tree.activity.coinblocksDestroyed.sum,
|
||||
name: "Coinblocks",
|
||||
color,
|
||||
unit: Unit.coinblocks,
|
||||
}),
|
||||
line({
|
||||
metric: tree.activity.coindaysDestroyed.sum,
|
||||
name: "Coindays",
|
||||
color,
|
||||
unit: Unit.coindays,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: title("Cumulative Coins Destroyed"),
|
||||
bottom: [
|
||||
line({
|
||||
metric: tree.activity.coinblocksDestroyed.cumulative,
|
||||
name: "Coinblocks",
|
||||
color,
|
||||
unit: Unit.coinblocks,
|
||||
}),
|
||||
line({
|
||||
metric: tree.activity.coindaysDestroyed.cumulative,
|
||||
name: "Coindays",
|
||||
color,
|
||||
unit: Unit.coindays,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Activity section with adjusted values (for cohorts with RealizedPattern3/4)
|
||||
* @param {{ cohort: CohortAll | CohortFull | CohortWithAdjusted, title: (metric: string) => string }} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createActivitySectionWithAdjusted({ cohort, title }) {
|
||||
const { tree } = cohort;
|
||||
return createActivitySection({
|
||||
cohort,
|
||||
title,
|
||||
soprTree: createSingleSoprTreeWithAdjusted(cohort, title),
|
||||
valueMetrics: [
|
||||
line({
|
||||
metric: tree.realized.adjustedValueCreated,
|
||||
name: "Adjusted Created",
|
||||
color: colors.adjustedCreated,
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.adjustedValueDestroyed,
|
||||
name: "Adjusted Destroyed",
|
||||
color: colors.adjustedDestroyed,
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Grouped Cohort Activity Section
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Create grouped flows tree (Profit Flow, Capitulation Flow)
|
||||
* @template {{ color: Color, name: string, tree: { realized: AnyRealizedPattern } }} T
|
||||
* @param {readonly T[]} list
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function groupedFlowsTree(list, title) {
|
||||
return [
|
||||
{
|
||||
name: "Profit",
|
||||
title: title("Profit Flow"),
|
||||
bottom: list.map(({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.realized.profitFlow,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Capitulation",
|
||||
title: title("Capitulation Flow"),
|
||||
bottom: list.map(({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.realized.capitulationFlow,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create grouped value tree (Flows, Created, Destroyed, Breakdown)
|
||||
* @template {{ color: Color, name: string, tree: { realized: AnyRealizedPattern } }} T
|
||||
* @param {readonly T[]} list
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function createGroupedValueTree(list, title) {
|
||||
return [
|
||||
{ name: "Flows", tree: groupedFlowsTree(list, title) },
|
||||
{
|
||||
name: "Created",
|
||||
title: title("Value Created"),
|
||||
bottom: list.map(({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.realized.valueCreated,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Destroyed",
|
||||
title: title("Value Destroyed"),
|
||||
bottom: list.map(({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.realized.valueDestroyed,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
},
|
||||
{ name: "Breakdown", tree: valueBreakdownTree(list, title) },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic grouped activity section builder
|
||||
* @template {readonly (UtxoCohortObject | CohortWithoutRelative)[]} T
|
||||
* @param {Object} args
|
||||
* @param {T} args.list
|
||||
* @param {(metric: string) => string} args.title
|
||||
* @param {PartialOptionsTree} [args.soprTree] - Optional SOPR tree override
|
||||
* @param {PartialOptionsTree} [args.valueTree] - Optional value tree (defaults to basic created/destroyed)
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createGroupedActivitySection({
|
||||
list,
|
||||
title,
|
||||
soprTree,
|
||||
valueTree,
|
||||
}) {
|
||||
return {
|
||||
name: "Activity",
|
||||
tree: [
|
||||
{
|
||||
name: "Volume",
|
||||
tree: [
|
||||
{
|
||||
name: "14d EMA",
|
||||
title: title("Sent Volume 14d EMA"),
|
||||
bottom: list.flatMap(({ color, name, tree }) =>
|
||||
satsBtcUsd({ pattern: tree.activity.sent14dEma, name, color }),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Sum",
|
||||
title: title("Sent Volume"),
|
||||
bottom: list.flatMap(({ color, name, tree }) =>
|
||||
satsBtcUsd({
|
||||
pattern: {
|
||||
sats: tree.activity.sent.sats.sum,
|
||||
bitcoin: tree.activity.sent.bitcoin.sum,
|
||||
dollars: tree.activity.sent.dollars.sum,
|
||||
},
|
||||
name,
|
||||
color,
|
||||
}),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "SOPR",
|
||||
tree: soprTree ?? createGroupedSoprTree(list, title),
|
||||
},
|
||||
{
|
||||
name: "Sell Side Risk",
|
||||
title: title("Sell Side Risk Ratio"),
|
||||
bottom: createGroupedSellSideRiskSeries(list),
|
||||
},
|
||||
{
|
||||
name: "Value",
|
||||
tree: valueTree ?? createGroupedValueTree(list, title),
|
||||
},
|
||||
{ name: "Coins Destroyed", tree: coinsDestroyedTree(list, title) },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create grouped value tree with adjusted values (Flows, Normal, Adjusted, Breakdown)
|
||||
* @param {readonly (CohortAll | CohortFull | CohortWithAdjusted)[]} list
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function createGroupedValueTreeWithAdjusted(list, title) {
|
||||
return [
|
||||
{ name: "Flows", tree: groupedFlowsTree(list, title) },
|
||||
{
|
||||
name: "Normal",
|
||||
tree: [
|
||||
{
|
||||
name: "Created",
|
||||
title: title("Value Created"),
|
||||
bottom: list.map(({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.realized.valueCreated,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Destroyed",
|
||||
title: title("Value Destroyed"),
|
||||
bottom: list.map(({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.realized.valueDestroyed,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Adjusted",
|
||||
tree: [
|
||||
{
|
||||
name: "Created",
|
||||
title: title("Adjusted Value Created"),
|
||||
bottom: list.map(({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.realized.adjustedValueCreated,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Destroyed",
|
||||
title: title("Adjusted Value Destroyed"),
|
||||
bottom: list.map(({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.realized.adjustedValueDestroyed,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{ name: "Breakdown", tree: valueBreakdownTree(list, title) },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Grouped activity section with adjusted values (for cohorts with RealizedPattern3/4)
|
||||
* @param {{ list: readonly (CohortAll | CohortFull | CohortWithAdjusted)[], title: (metric: string) => string }} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createGroupedActivitySectionWithAdjusted({ list, title }) {
|
||||
return createGroupedActivitySection({
|
||||
list,
|
||||
title,
|
||||
soprTree: createGroupedSoprTreeWithAdjusted(list, title),
|
||||
valueTree: createGroupedValueTreeWithAdjusted(list, title),
|
||||
});
|
||||
}
|
||||
@@ -291,13 +291,6 @@ function createRealizedPnlSection(args, title) {
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: realized.totalRealizedPnl,
|
||||
name: "Total",
|
||||
color: colors.default,
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
baseline({
|
||||
metric: realized.realizedProfitRelToRealizedCap.sum,
|
||||
name: "Profit",
|
||||
@@ -324,6 +317,13 @@ function createRealizedPnlSection(args, title) {
|
||||
unit: Unit.pctRcap,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: realized.totalRealizedPnl,
|
||||
name: "Total",
|
||||
color: colors.default,
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -450,6 +450,24 @@ function createRealizedPnlSection(args, title) {
|
||||
{
|
||||
name: "Sent In P/L",
|
||||
tree: [
|
||||
{
|
||||
name: "In Profit 14d EMA",
|
||||
title: title("Sent In Profit 14d EMA"),
|
||||
bottom: satsBtcUsd({
|
||||
pattern: realized.sentInProfit14dEma,
|
||||
name: "14d EMA",
|
||||
color: colors.profit,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "In Loss 14d EMA",
|
||||
title: title("Sent In Loss 14d EMA"),
|
||||
bottom: satsBtcUsd({
|
||||
pattern: realized.sentInLoss14dEma,
|
||||
name: "14d EMA",
|
||||
color: colors.loss,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "In Profit",
|
||||
title: title("Sent In Profit"),
|
||||
@@ -540,24 +558,6 @@ function createRealizedPnlSection(args, title) {
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "In Profit 14d EMA",
|
||||
title: title("Sent In Profit 14d EMA"),
|
||||
bottom: satsBtcUsd({
|
||||
pattern: realized.sentInProfit14dEma,
|
||||
name: "14d EMA",
|
||||
color: colors.profit,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "In Loss 14d EMA",
|
||||
title: title("Sent In Loss 14d EMA"),
|
||||
bottom: satsBtcUsd({
|
||||
pattern: realized.sentInLoss14dEma,
|
||||
name: "14d EMA",
|
||||
color: colors.loss,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -678,6 +678,28 @@ function createGroupedRealizedPnlSection(list, title) {
|
||||
{
|
||||
name: "Sent In P/L",
|
||||
tree: [
|
||||
{
|
||||
name: "In Profit 14d EMA",
|
||||
title: title("Sent In Profit 14d EMA"),
|
||||
bottom: list.flatMap(({ color, name, tree }) =>
|
||||
satsBtcUsd({
|
||||
pattern: tree.realized.sentInProfit14dEma,
|
||||
name,
|
||||
color,
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "In Loss 14d EMA",
|
||||
title: title("Sent In Loss 14d EMA"),
|
||||
bottom: list.flatMap(({ color, name, tree }) =>
|
||||
satsBtcUsd({
|
||||
pattern: tree.realized.sentInLoss14dEma,
|
||||
name,
|
||||
color,
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "In Profit",
|
||||
title: title("Sent In Profit"),
|
||||
@@ -774,28 +796,6 @@ function createGroupedRealizedPnlSection(list, title) {
|
||||
}),
|
||||
]),
|
||||
},
|
||||
{
|
||||
name: "In Profit 14d EMA",
|
||||
title: title("Sent In Profit 14d EMA"),
|
||||
bottom: list.flatMap(({ color, name, tree }) =>
|
||||
satsBtcUsd({
|
||||
pattern: tree.realized.sentInProfit14dEma,
|
||||
name,
|
||||
color,
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "In Loss 14d EMA",
|
||||
title: title("Sent In Loss 14d EMA"),
|
||||
bottom: list.flatMap(({ color, name, tree }) =>
|
||||
satsBtcUsd({
|
||||
pattern: tree.realized.sentInLoss14dEma,
|
||||
name,
|
||||
color,
|
||||
}),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -811,7 +811,7 @@ function createGroupedRealizedPnlSection(list, title) {
|
||||
function createUnrealizedSection(list, useGroupName, title) {
|
||||
return [
|
||||
{
|
||||
name: "Unrealized",
|
||||
name: "Profitability",
|
||||
tree: [
|
||||
{
|
||||
name: "Profit",
|
||||
@@ -962,7 +962,7 @@ function createUnrealizedSection(list, useGroupName, title) {
|
||||
metric: tree.relative.investedCapitalInProfitPct,
|
||||
name: useGroupName ? name : "In Profit",
|
||||
color: useGroupName ? color : colors.profit,
|
||||
unit: Unit.pctRcap,
|
||||
unit: Unit.pctOwnRcap,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
@@ -974,7 +974,7 @@ function createUnrealizedSection(list, useGroupName, title) {
|
||||
metric: tree.relative.investedCapitalInLossPct,
|
||||
name: useGroupName ? name : "In Loss",
|
||||
color: useGroupName ? color : colors.loss,
|
||||
unit: Unit.pctRcap,
|
||||
unit: Unit.pctOwnRcap,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
@@ -1104,6 +1104,13 @@ function createActivitySection(args, title) {
|
||||
{
|
||||
name: "Sent",
|
||||
tree: [
|
||||
{
|
||||
name: "14d EMA",
|
||||
title: title("Sent 14d EMA"),
|
||||
bottom: list.flatMap(({ color, name, tree }) =>
|
||||
satsBtcUsd({ pattern: tree.activity.sent14dEma, name, color }),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Sum",
|
||||
title: title("Sent"),
|
||||
@@ -1119,13 +1126,6 @@ function createActivitySection(args, title) {
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "14d EMA",
|
||||
title: title("Sent 14d EMA"),
|
||||
bottom: list.flatMap(({ color, name, tree }) =>
|
||||
satsBtcUsd({ pattern: tree.activity.sent14dEma, name, color }),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -123,13 +123,17 @@ export function buildCohortData() {
|
||||
|
||||
// Addresses above amount
|
||||
const addressesAboveAmount = entries(addressCohorts.geAmount).map(
|
||||
([key, tree]) => {
|
||||
([key, cohort]) => {
|
||||
const names = GE_AMOUNT_NAMES[key];
|
||||
return {
|
||||
name: names.short,
|
||||
title: `Addresses ${names.long}`,
|
||||
color: colors.amount[key],
|
||||
tree,
|
||||
tree: cohort,
|
||||
addrCount: {
|
||||
count: cohort.addrCount,
|
||||
_30dChange: cohort.addrCount30dChange,
|
||||
},
|
||||
};
|
||||
},
|
||||
);
|
||||
@@ -147,13 +151,17 @@ export function buildCohortData() {
|
||||
|
||||
// Addresses under amount
|
||||
const addressesUnderAmount = entries(addressCohorts.ltAmount).map(
|
||||
([key, tree]) => {
|
||||
([key, cohort]) => {
|
||||
const names = LT_AMOUNT_NAMES[key];
|
||||
return {
|
||||
name: names.short,
|
||||
title: `Addresses ${names.long}`,
|
||||
color: colors.amount[key],
|
||||
tree,
|
||||
tree: cohort,
|
||||
addrCount: {
|
||||
count: cohort.addrCount,
|
||||
_30dChange: cohort.addrCount30dChange,
|
||||
},
|
||||
};
|
||||
},
|
||||
);
|
||||
@@ -173,13 +181,17 @@ export function buildCohortData() {
|
||||
|
||||
// Addresses amount ranges
|
||||
const addressesAmountRanges = entries(addressCohorts.amountRange).map(
|
||||
([key, tree]) => {
|
||||
([key, cohort]) => {
|
||||
const names = AMOUNT_RANGE_NAMES[key];
|
||||
return {
|
||||
name: names.short,
|
||||
title: `Addresses ${names.long}`,
|
||||
color: colors.amountRange[key],
|
||||
tree,
|
||||
tree: cohort,
|
||||
addrCount: {
|
||||
count: cohort.addrCount,
|
||||
_30dChange: cohort.addrCount30dChange,
|
||||
},
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
@@ -16,14 +16,89 @@
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { line, baseline } from "../series.js";
|
||||
import { satsBtcUsd, satsBtcUsdBaseline } from "../shared.js";
|
||||
import { colors } from "../../utils/colors.js";
|
||||
import { priceLines } from "../constants.js";
|
||||
|
||||
/**
|
||||
* @param {UtxoCohortObject | CohortWithoutRelative} cohort
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
function createSingleSupplySeries(cohort) {
|
||||
const { color, tree } = cohort;
|
||||
return [...satsBtcUsd({ pattern: tree.supply.total, name: "Supply", color })];
|
||||
const { tree } = cohort;
|
||||
return [
|
||||
...satsBtcUsd({
|
||||
pattern: tree.supply.total,
|
||||
name: "Total",
|
||||
color: colors.default,
|
||||
}),
|
||||
...satsBtcUsd({
|
||||
pattern: tree.unrealized.supplyInProfit,
|
||||
name: "In Profit",
|
||||
color: colors.profit,
|
||||
}),
|
||||
...satsBtcUsd({
|
||||
pattern: tree.unrealized.supplyInLoss,
|
||||
name: "In Loss",
|
||||
color: colors.loss,
|
||||
}),
|
||||
// Halved supply (sparse line)
|
||||
...satsBtcUsd({
|
||||
pattern: tree.supply.halved,
|
||||
name: "Halved",
|
||||
color: colors.gray,
|
||||
style: 4,
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Supply series for CohortAll (has % of Own Supply but not % of Circulating)
|
||||
* @param {CohortAll} cohort
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
function createSingleSupplySeriesAll(cohort) {
|
||||
const { tree } = cohort;
|
||||
return [
|
||||
...satsBtcUsd({
|
||||
pattern: tree.supply.total,
|
||||
name: "Total",
|
||||
color: colors.default,
|
||||
}),
|
||||
...satsBtcUsd({
|
||||
pattern: tree.unrealized.supplyInProfit,
|
||||
name: "In Profit",
|
||||
color: colors.profit,
|
||||
}),
|
||||
...satsBtcUsd({
|
||||
pattern: tree.unrealized.supplyInLoss,
|
||||
name: "In Loss",
|
||||
color: colors.loss,
|
||||
}),
|
||||
// Halved supply (sparse line)
|
||||
...satsBtcUsd({
|
||||
pattern: tree.supply.halved,
|
||||
name: "Halved",
|
||||
color: colors.gray,
|
||||
style: 4,
|
||||
}),
|
||||
// % of Own Supply
|
||||
line({
|
||||
metric: tree.relative.supplyInProfitRelToOwnSupply,
|
||||
name: "In Profit",
|
||||
color: colors.profit,
|
||||
unit: Unit.pctOwn,
|
||||
}),
|
||||
line({
|
||||
metric: tree.relative.supplyInLossRelToOwnSupply,
|
||||
name: "In Loss",
|
||||
color: colors.loss,
|
||||
unit: Unit.pctOwn,
|
||||
}),
|
||||
...priceLines({
|
||||
numbers: [100, 50, 0],
|
||||
unit: Unit.pctOwn,
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -31,7 +106,10 @@ function createSingleSupplySeries(cohort) {
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
function createSingle30dChangeSeries(cohort) {
|
||||
return satsBtcUsdBaseline({ pattern: cohort.tree.supply._30dChange, name: "30d Change" });
|
||||
return satsBtcUsdBaseline({
|
||||
pattern: cohort.tree.supply._30dChange,
|
||||
name: "30d Change",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -79,18 +157,121 @@ function createSingleAddrCount30dChangeSeries(cohort) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create supply series with % of Circulating (for cohorts with relative data)
|
||||
* @param {CohortFull | CohortWithAdjusted | CohortBasicWithMarketCap | CohortMinAge} cohort
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
function createSingleRelativeSeries(cohort) {
|
||||
const { color, tree } = cohort;
|
||||
function createSingleSupplySeriesWithRelative(cohort) {
|
||||
const { tree } = cohort;
|
||||
return [
|
||||
...satsBtcUsd({
|
||||
pattern: tree.supply.total,
|
||||
name: "Total",
|
||||
color: colors.default,
|
||||
}),
|
||||
...satsBtcUsd({
|
||||
pattern: tree.unrealized.supplyInProfit,
|
||||
name: "In Profit",
|
||||
color: colors.profit,
|
||||
}),
|
||||
...satsBtcUsd({
|
||||
pattern: tree.unrealized.supplyInLoss,
|
||||
name: "In Loss",
|
||||
color: colors.loss,
|
||||
}),
|
||||
// Halved supply (sparse line)
|
||||
...satsBtcUsd({
|
||||
pattern: tree.supply.halved,
|
||||
name: "Halved",
|
||||
color: colors.gray,
|
||||
style: 4,
|
||||
}),
|
||||
// % of Circulating Supply
|
||||
line({
|
||||
metric: tree.relative.supplyRelToCirculatingSupply,
|
||||
name: "% of Circulating",
|
||||
color,
|
||||
name: "Total",
|
||||
color: colors.default,
|
||||
unit: Unit.pctSupply,
|
||||
}),
|
||||
line({
|
||||
metric: tree.relative.supplyInProfitRelToCirculatingSupply,
|
||||
name: "In Profit",
|
||||
color: colors.profit,
|
||||
unit: Unit.pctSupply,
|
||||
}),
|
||||
line({
|
||||
metric: tree.relative.supplyInLossRelToCirculatingSupply,
|
||||
name: "In Loss",
|
||||
color: colors.loss,
|
||||
unit: Unit.pctSupply,
|
||||
}),
|
||||
// % of Own Supply
|
||||
line({
|
||||
metric: tree.relative.supplyInProfitRelToOwnSupply,
|
||||
name: "In Profit",
|
||||
color: colors.profit,
|
||||
unit: Unit.pctOwn,
|
||||
}),
|
||||
line({
|
||||
metric: tree.relative.supplyInLossRelToOwnSupply,
|
||||
name: "In Loss",
|
||||
color: colors.loss,
|
||||
unit: Unit.pctOwn,
|
||||
}),
|
||||
...priceLines({
|
||||
numbers: [100, 50, 0],
|
||||
unit: Unit.pctOwn,
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Supply series with % of Own Supply only (for cohorts without % of Circulating)
|
||||
* @param {CohortAgeRange | CohortBasicWithoutMarketCap} cohort
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
function createSingleSupplySeriesWithOwnSupply(cohort) {
|
||||
const { tree } = cohort;
|
||||
return [
|
||||
...satsBtcUsd({
|
||||
pattern: tree.unrealized.supplyInProfit,
|
||||
name: "In Profit",
|
||||
color: colors.profit,
|
||||
}),
|
||||
...satsBtcUsd({
|
||||
pattern: tree.unrealized.supplyInLoss,
|
||||
name: "In Loss",
|
||||
color: colors.loss,
|
||||
}),
|
||||
...satsBtcUsd({
|
||||
pattern: tree.supply.total,
|
||||
name: "Total",
|
||||
color: colors.default,
|
||||
}),
|
||||
// Halved supply (sparse line)
|
||||
...satsBtcUsd({
|
||||
pattern: tree.supply.halved,
|
||||
name: "Halved",
|
||||
color: colors.gray,
|
||||
style: 4,
|
||||
}),
|
||||
// % of Own Supply
|
||||
line({
|
||||
metric: tree.relative.supplyInProfitRelToOwnSupply,
|
||||
name: "In Profit",
|
||||
color: colors.profit,
|
||||
unit: Unit.pctOwn,
|
||||
}),
|
||||
line({
|
||||
metric: tree.relative.supplyInLossRelToOwnSupply,
|
||||
name: "In Loss",
|
||||
color: colors.loss,
|
||||
unit: Unit.pctOwn,
|
||||
}),
|
||||
...priceLines({
|
||||
numbers: [100, 50, 0],
|
||||
unit: Unit.pctOwn,
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -132,17 +313,18 @@ export function createHoldingsSection({ cohort, title }) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{ cohort: CohortFull | CohortWithAdjusted | CohortBasicWithMarketCap | CohortMinAge, title: (metric: string) => string }} args
|
||||
* Holdings section with % of Own Supply only (for cohorts without % of Circulating)
|
||||
* @param {{ cohort: CohortAgeRange | CohortBasicWithoutMarketCap, title: (metric: string) => string }} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createHoldingsSectionWithRelative({ cohort, title }) {
|
||||
export function createHoldingsSectionWithOwnSupply({ cohort, title }) {
|
||||
return {
|
||||
name: "Holdings",
|
||||
tree: [
|
||||
{
|
||||
name: "Supply",
|
||||
title: title("Supply"),
|
||||
bottom: createSingleSupplySeries(cohort),
|
||||
bottom: createSingleSupplySeriesWithOwnSupply(cohort),
|
||||
},
|
||||
{
|
||||
name: "UTXO Count",
|
||||
@@ -164,10 +346,42 @@ export function createHoldingsSectionWithRelative({ cohort, title }) {
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{ cohort: CohortFull | CohortWithAdjusted | CohortBasicWithMarketCap | CohortMinAge, title: (metric: string) => string }} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createHoldingsSectionWithRelative({ cohort, title }) {
|
||||
return {
|
||||
name: "Holdings",
|
||||
tree: [
|
||||
{
|
||||
name: "Relative",
|
||||
title: title("Relative to Circulating Supply"),
|
||||
bottom: createSingleRelativeSeries(cohort),
|
||||
name: "Supply",
|
||||
title: title("Supply"),
|
||||
bottom: createSingleSupplySeriesWithRelative(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),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -184,7 +398,7 @@ export function createHoldingsSectionAll({ cohort, title }) {
|
||||
{
|
||||
name: "Supply",
|
||||
title: title("Supply"),
|
||||
bottom: createSingleSupplySeries(cohort),
|
||||
bottom: createSingleSupplySeriesAll(cohort),
|
||||
},
|
||||
{
|
||||
name: "UTXO Count",
|
||||
@@ -238,7 +452,62 @@ export function createHoldingsSectionAddress({ cohort, title }) {
|
||||
{
|
||||
name: "Supply",
|
||||
title: title("Supply"),
|
||||
bottom: createSingleSupplySeries(cohort),
|
||||
bottom: createSingleSupplySeriesWithOwnSupply(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),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Holdings section for address amount cohorts (has relative supply + address count)
|
||||
* @param {{ cohort: AddressCohortObject, title: (metric: string) => string }} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createHoldingsSectionAddressAmount({ cohort, title }) {
|
||||
return {
|
||||
name: "Holdings",
|
||||
tree: [
|
||||
{
|
||||
name: "Supply",
|
||||
title: title("Supply"),
|
||||
bottom: createSingleSupplySeriesWithRelative(cohort),
|
||||
},
|
||||
{
|
||||
name: "UTXO Count",
|
||||
@@ -291,10 +560,67 @@ export function createGroupedHoldingsSectionAddress({ list, title }) {
|
||||
tree: [
|
||||
{
|
||||
name: "Supply",
|
||||
title: title("Supply"),
|
||||
bottom: list.flatMap(({ name, color, tree }) =>
|
||||
satsBtcUsd({ pattern: tree.supply.total, name, color }),
|
||||
),
|
||||
tree: [
|
||||
{
|
||||
name: "Total",
|
||||
title: title("Supply"),
|
||||
bottom: list.flatMap(({ name, color, tree }) =>
|
||||
satsBtcUsd({ pattern: tree.supply.total, name, color }),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "In Profit",
|
||||
title: title("Supply In Profit"),
|
||||
bottom: [
|
||||
...list.flatMap(({ name, color, tree }) =>
|
||||
satsBtcUsd({
|
||||
pattern: tree.unrealized.supplyInProfit,
|
||||
name,
|
||||
color,
|
||||
}),
|
||||
),
|
||||
// % of Own Supply
|
||||
...list.map(({ name, color, tree }) =>
|
||||
line({
|
||||
metric: tree.relative.supplyInProfitRelToOwnSupply,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.pctOwn,
|
||||
}),
|
||||
),
|
||||
...priceLines({
|
||||
numbers: [100, 50, 0],
|
||||
unit: Unit.pctOwn,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "In Loss",
|
||||
title: title("Supply In Loss"),
|
||||
bottom: [
|
||||
...list.flatMap(({ name, color, tree }) =>
|
||||
satsBtcUsd({
|
||||
pattern: tree.unrealized.supplyInLoss,
|
||||
name,
|
||||
color,
|
||||
}),
|
||||
),
|
||||
// % of Own Supply
|
||||
...list.map(({ name, color, tree }) =>
|
||||
line({
|
||||
metric: tree.relative.supplyInLossRelToOwnSupply,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.pctOwn,
|
||||
}),
|
||||
),
|
||||
...priceLines({
|
||||
numbers: [100, 50, 0],
|
||||
unit: Unit.pctOwn,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "UTXO Count",
|
||||
@@ -322,7 +648,176 @@ export function createGroupedHoldingsSectionAddress({ list, title }) {
|
||||
name: "Supply",
|
||||
title: title("Supply 30d Change"),
|
||||
bottom: list.flatMap(({ name, color, tree }) =>
|
||||
satsBtcUsdBaseline({ pattern: tree.supply._30dChange, name, color }),
|
||||
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,
|
||||
}),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Grouped holdings section for address amount cohorts (has relative supply + address count)
|
||||
* @param {{ list: readonly AddressCohortObject[], title: (metric: string) => string }} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createGroupedHoldingsSectionAddressAmount({ list, title }) {
|
||||
return {
|
||||
name: "Holdings",
|
||||
tree: [
|
||||
{
|
||||
name: "Supply",
|
||||
tree: [
|
||||
{
|
||||
name: "Total",
|
||||
title: title("Supply"),
|
||||
bottom: [
|
||||
...list.flatMap(({ name, color, tree }) =>
|
||||
satsBtcUsd({ pattern: tree.supply.total, name, color }),
|
||||
),
|
||||
// % of Circulating
|
||||
...list.map(({ name, color, tree }) =>
|
||||
line({
|
||||
metric: tree.relative.supplyRelToCirculatingSupply,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.pctSupply,
|
||||
}),
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "In Profit",
|
||||
title: title("Supply In Profit"),
|
||||
bottom: [
|
||||
...list.flatMap(({ name, color, tree }) =>
|
||||
satsBtcUsd({
|
||||
pattern: tree.unrealized.supplyInProfit,
|
||||
name,
|
||||
color,
|
||||
}),
|
||||
),
|
||||
// % of Circulating
|
||||
...list.map(({ name, color, tree }) =>
|
||||
line({
|
||||
metric: tree.relative.supplyInProfitRelToCirculatingSupply,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.pctSupply,
|
||||
}),
|
||||
),
|
||||
// % of Own Supply
|
||||
...list.map(({ name, color, tree }) =>
|
||||
line({
|
||||
metric: tree.relative.supplyInProfitRelToOwnSupply,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.pctOwn,
|
||||
}),
|
||||
),
|
||||
...priceLines({
|
||||
numbers: [100, 50, 0],
|
||||
unit: Unit.pctOwn,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "In Loss",
|
||||
title: title("Supply In Loss"),
|
||||
bottom: [
|
||||
...list.flatMap(({ name, color, tree }) =>
|
||||
satsBtcUsd({
|
||||
pattern: tree.unrealized.supplyInLoss,
|
||||
name,
|
||||
color,
|
||||
}),
|
||||
),
|
||||
// % of Circulating
|
||||
...list.map(({ name, color, tree }) =>
|
||||
line({
|
||||
metric: tree.relative.supplyInLossRelToCirculatingSupply,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.pctSupply,
|
||||
}),
|
||||
),
|
||||
// % of Own Supply
|
||||
...list.map(({ name, color, tree }) =>
|
||||
line({
|
||||
metric: tree.relative.supplyInLossRelToOwnSupply,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.pctOwn,
|
||||
}),
|
||||
),
|
||||
...priceLines({
|
||||
numbers: [100, 50, 0],
|
||||
unit: Unit.pctOwn,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
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,
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -366,10 +861,37 @@ export function createGroupedHoldingsSection({ list, title }) {
|
||||
tree: [
|
||||
{
|
||||
name: "Supply",
|
||||
title: title("Supply"),
|
||||
bottom: list.flatMap(({ name, color, tree }) =>
|
||||
satsBtcUsd({ pattern: tree.supply.total, name, color }),
|
||||
),
|
||||
tree: [
|
||||
{
|
||||
name: "Total",
|
||||
title: title("Supply"),
|
||||
bottom: list.flatMap(({ name, color, tree }) =>
|
||||
satsBtcUsd({ pattern: tree.supply.total, name, color }),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "In Profit",
|
||||
title: title("Supply In Profit"),
|
||||
bottom: list.flatMap(({ name, color, tree }) =>
|
||||
satsBtcUsd({
|
||||
pattern: tree.unrealized.supplyInProfit,
|
||||
name,
|
||||
color,
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "In Loss",
|
||||
title: title("Supply In Loss"),
|
||||
bottom: list.flatMap(({ name, color, tree }) =>
|
||||
satsBtcUsd({
|
||||
pattern: tree.unrealized.supplyInLoss,
|
||||
name,
|
||||
color,
|
||||
}),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "UTXO Count",
|
||||
@@ -390,7 +912,128 @@ export function createGroupedHoldingsSection({ list, title }) {
|
||||
name: "Supply",
|
||||
title: title("Supply 30d Change"),
|
||||
bottom: list.flatMap(({ name, color, tree }) =>
|
||||
satsBtcUsdBaseline({ pattern: tree.supply._30dChange, name, color }),
|
||||
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,
|
||||
}),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Grouped holdings section with % of Own Supply only (for cohorts without % of Circulating)
|
||||
* @param {{ list: readonly (CohortAgeRange | CohortBasicWithoutMarketCap)[], title: (metric: string) => string }} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createGroupedHoldingsSectionWithOwnSupply({ list, title }) {
|
||||
return {
|
||||
name: "Holdings",
|
||||
tree: [
|
||||
{
|
||||
name: "Supply",
|
||||
tree: [
|
||||
{
|
||||
name: "In Profit",
|
||||
title: title("Supply In Profit"),
|
||||
bottom: [
|
||||
...list.flatMap(({ name, color, tree }) =>
|
||||
satsBtcUsd({
|
||||
pattern: tree.unrealized.supplyInProfit,
|
||||
name,
|
||||
color,
|
||||
}),
|
||||
),
|
||||
// % of Own Supply
|
||||
...list.map(({ name, color, tree }) =>
|
||||
line({
|
||||
metric: tree.relative.supplyInProfitRelToOwnSupply,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.pctOwn,
|
||||
}),
|
||||
),
|
||||
...priceLines({
|
||||
numbers: [100, 50, 0],
|
||||
unit: Unit.pctOwn,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "In Loss",
|
||||
title: title("Supply In Loss"),
|
||||
bottom: [
|
||||
...list.flatMap(({ name, color, tree }) =>
|
||||
satsBtcUsd({
|
||||
pattern: tree.unrealized.supplyInLoss,
|
||||
name,
|
||||
color,
|
||||
}),
|
||||
),
|
||||
// % of Own Supply
|
||||
...list.map(({ name, color, tree }) =>
|
||||
line({
|
||||
metric: tree.relative.supplyInLossRelToOwnSupply,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.pctOwn,
|
||||
}),
|
||||
),
|
||||
...priceLines({
|
||||
numbers: [100, 50, 0],
|
||||
unit: Unit.pctOwn,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Total",
|
||||
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,
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -421,10 +1064,96 @@ export function createGroupedHoldingsSectionWithRelative({ list, title }) {
|
||||
tree: [
|
||||
{
|
||||
name: "Supply",
|
||||
title: title("Supply"),
|
||||
bottom: list.flatMap(({ name, color, tree }) =>
|
||||
satsBtcUsd({ pattern: tree.supply.total, name, color }),
|
||||
),
|
||||
tree: [
|
||||
{
|
||||
name: "Total",
|
||||
title: title("Supply"),
|
||||
bottom: [
|
||||
...list.flatMap(({ name, color, tree }) =>
|
||||
satsBtcUsd({ pattern: tree.supply.total, name, color }),
|
||||
),
|
||||
// % of Circulating
|
||||
...list.map(({ name, color, tree }) =>
|
||||
line({
|
||||
metric: tree.relative.supplyRelToCirculatingSupply,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.pctSupply,
|
||||
}),
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "In Profit",
|
||||
title: title("Supply In Profit"),
|
||||
bottom: [
|
||||
...list.flatMap(({ name, color, tree }) =>
|
||||
satsBtcUsd({
|
||||
pattern: tree.unrealized.supplyInProfit,
|
||||
name,
|
||||
color,
|
||||
}),
|
||||
),
|
||||
// % of Circulating
|
||||
...list.map(({ name, color, tree }) =>
|
||||
line({
|
||||
metric: tree.relative.supplyInProfitRelToCirculatingSupply,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.pctSupply,
|
||||
}),
|
||||
),
|
||||
// % of Own Supply
|
||||
...list.map(({ name, color, tree }) =>
|
||||
line({
|
||||
metric: tree.relative.supplyInProfitRelToOwnSupply,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.pctOwn,
|
||||
}),
|
||||
),
|
||||
...priceLines({
|
||||
numbers: [100, 50, 0],
|
||||
unit: Unit.pctOwn,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "In Loss",
|
||||
title: title("Supply In Loss"),
|
||||
bottom: [
|
||||
...list.flatMap(({ name, color, tree }) =>
|
||||
satsBtcUsd({
|
||||
pattern: tree.unrealized.supplyInLoss,
|
||||
name,
|
||||
color,
|
||||
}),
|
||||
),
|
||||
// % of Circulating
|
||||
...list.map(({ name, color, tree }) =>
|
||||
line({
|
||||
metric: tree.relative.supplyInLossRelToCirculatingSupply,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.pctSupply,
|
||||
}),
|
||||
),
|
||||
// % of Own Supply
|
||||
...list.map(({ name, color, tree }) =>
|
||||
line({
|
||||
metric: tree.relative.supplyInLossRelToOwnSupply,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.pctOwn,
|
||||
}),
|
||||
),
|
||||
...priceLines({
|
||||
numbers: [100, 50, 0],
|
||||
unit: Unit.pctOwn,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "UTXO Count",
|
||||
@@ -445,7 +1174,11 @@ export function createGroupedHoldingsSectionWithRelative({ list, title }) {
|
||||
name: "Supply",
|
||||
title: title("Supply 30d Change"),
|
||||
bottom: list.flatMap(({ name, color, tree }) =>
|
||||
satsBtcUsdBaseline({ pattern: tree.supply._30dChange, name, color }),
|
||||
satsBtcUsdBaseline({
|
||||
pattern: tree.supply._30dChange,
|
||||
name,
|
||||
color,
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -462,18 +1195,6 @@ export function createGroupedHoldingsSectionWithRelative({ list, title }) {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Relative",
|
||||
title: title("Relative to Circulating Supply"),
|
||||
bottom: list.map(({ name, color, tree }) =>
|
||||
line({
|
||||
metric: tree.relative.supplyRelToCirculatingSupply,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.pctSupply,
|
||||
}),
|
||||
),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,26 +1,75 @@
|
||||
/**
|
||||
* Cohort module - exports all cohort-related functionality
|
||||
*
|
||||
* Folder builders compose sections from building blocks:
|
||||
* - holdings.js: Supply, UTXO Count, Address Count
|
||||
* - valuation.js: Realized Cap, Market Cap, MVRV
|
||||
* - prices.js: Realized Price, ratios
|
||||
* - cost-basis.js: Cost basis percentiles
|
||||
* - profitability.js: Unrealized/Realized P&L, Invested Capital
|
||||
* - activity.js: SOPR, Volume, Lifespan
|
||||
*/
|
||||
|
||||
// Cohort data builder
|
||||
import { formatCohortTitle } from "../shared.js";
|
||||
|
||||
// Section builders
|
||||
import {
|
||||
createHoldingsSection,
|
||||
createHoldingsSectionAll,
|
||||
createHoldingsSectionAddress,
|
||||
createHoldingsSectionAddressAmount,
|
||||
createHoldingsSectionWithRelative,
|
||||
createHoldingsSectionWithOwnSupply,
|
||||
createGroupedHoldingsSection,
|
||||
createGroupedHoldingsSectionAddress,
|
||||
createGroupedHoldingsSectionAddressAmount,
|
||||
createGroupedHoldingsSectionWithRelative,
|
||||
createGroupedHoldingsSectionWithOwnSupply,
|
||||
} from "./holdings.js";
|
||||
import {
|
||||
createValuationSection,
|
||||
createValuationSectionFull,
|
||||
createGroupedValuationSection,
|
||||
createGroupedValuationSectionWithOwnMarketCap,
|
||||
} from "./valuation.js";
|
||||
import {
|
||||
createPricesSectionFull,
|
||||
createPricesSectionBasic,
|
||||
createGroupedPricesSection,
|
||||
} from "./prices.js";
|
||||
import {
|
||||
createCostBasisSection,
|
||||
createCostBasisSectionWithPercentiles,
|
||||
createGroupedCostBasisSection,
|
||||
createGroupedCostBasisSectionWithPercentiles,
|
||||
} from "./cost-basis.js";
|
||||
import {
|
||||
createProfitabilitySection,
|
||||
createProfitabilitySectionAll,
|
||||
createProfitabilitySectionFull,
|
||||
createProfitabilitySectionWithNupl,
|
||||
createProfitabilitySectionWithPeakRegret,
|
||||
createProfitabilitySectionWithInvestedCapitalPct,
|
||||
createProfitabilitySectionBasicWithInvestedCapitalPct,
|
||||
createProfitabilitySectionLongTerm,
|
||||
createGroupedProfitabilitySection,
|
||||
createGroupedProfitabilitySectionWithNupl,
|
||||
createGroupedProfitabilitySectionWithPeakRegret,
|
||||
createGroupedProfitabilitySectionWithInvestedCapitalPct,
|
||||
createGroupedProfitabilitySectionBasicWithInvestedCapitalPct,
|
||||
createGroupedProfitabilitySectionLongTerm,
|
||||
} from "./profitability.js";
|
||||
import {
|
||||
createActivitySection,
|
||||
createActivitySectionWithAdjusted,
|
||||
createGroupedActivitySection,
|
||||
createGroupedActivitySectionWithAdjusted,
|
||||
} from "./activity.js";
|
||||
|
||||
// Re-export data builder
|
||||
export { buildCohortData } from "./data.js";
|
||||
|
||||
// Cohort folder builders (type-safe!)
|
||||
export {
|
||||
createCohortFolderAll,
|
||||
createCohortFolderFull,
|
||||
createCohortFolderWithAdjusted,
|
||||
createCohortFolderWithNupl,
|
||||
createCohortFolderAgeRange,
|
||||
createCohortFolderMinAge,
|
||||
createCohortFolderBasicWithMarketCap,
|
||||
createCohortFolderBasicWithoutMarketCap,
|
||||
createCohortFolderWithoutRelative,
|
||||
createCohortFolderAddress,
|
||||
} from "./utxo.js";
|
||||
export { createAddressCohortFolder } from "./address.js";
|
||||
|
||||
// Shared helpers
|
||||
// Re-export shared helpers
|
||||
export {
|
||||
createSingleSupplySeries,
|
||||
createGroupedSupplyTotalSeries,
|
||||
@@ -33,3 +82,426 @@ export {
|
||||
createRealizedCapSeries,
|
||||
createCostBasisPercentilesSeries,
|
||||
} from "./shared.js";
|
||||
|
||||
// ============================================================================
|
||||
// Folder Builders
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* All folder: for the special "All" cohort (adjustedSopr + percentiles + RelToMarketCap)
|
||||
* @param {CohortAll} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createCohortFolderAll(args) {
|
||||
const title = formatCohortTitle(args.name);
|
||||
return {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createHoldingsSectionAll({ cohort: args, title }),
|
||||
createValuationSectionFull({ cohort: args, title }),
|
||||
createPricesSectionFull({ cohort: args, title }),
|
||||
createCostBasisSectionWithPercentiles({ cohort: args, title }),
|
||||
createProfitabilitySectionAll({ cohort: args, title }),
|
||||
createActivitySectionWithAdjusted({ cohort: args, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Full folder: adjustedSopr + percentiles + RelToMarketCap (term.short only)
|
||||
* @param {CohortFull | CohortGroupFull} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createCohortFolderFull(args) {
|
||||
if ("list" in args) {
|
||||
const { list } = args;
|
||||
const title = formatCohortTitle(args.title);
|
||||
return {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createGroupedHoldingsSectionWithRelative({ list, title }),
|
||||
createGroupedValuationSectionWithOwnMarketCap({ list, title }),
|
||||
createGroupedPricesSection({ list, title }),
|
||||
createGroupedCostBasisSectionWithPercentiles({ list, title }),
|
||||
createGroupedProfitabilitySectionWithNupl({ list, title }),
|
||||
createGroupedActivitySectionWithAdjusted({ list, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
const title = formatCohortTitle(args.name);
|
||||
return {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createHoldingsSectionWithRelative({ cohort: args, title }),
|
||||
createValuationSectionFull({ cohort: args, title }),
|
||||
createPricesSectionFull({ cohort: args, title }),
|
||||
createCostBasisSectionWithPercentiles({ cohort: args, title }),
|
||||
createProfitabilitySectionFull({ cohort: args, title }),
|
||||
createActivitySectionWithAdjusted({ cohort: args, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusted folder: adjustedSopr only, no percentiles (maxAge.*)
|
||||
* Has Peak Regret metrics like minAge
|
||||
* @param {CohortWithAdjusted | CohortGroupWithAdjusted} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createCohortFolderWithAdjusted(args) {
|
||||
if ("list" in args) {
|
||||
const { list } = args;
|
||||
const title = formatCohortTitle(args.title);
|
||||
return {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createGroupedHoldingsSectionWithRelative({ list, title }),
|
||||
createGroupedValuationSection({ list, title }),
|
||||
createGroupedPricesSection({ list, title }),
|
||||
createGroupedCostBasisSection({ list, title }),
|
||||
createGroupedProfitabilitySectionWithPeakRegret({ list, title }),
|
||||
createGroupedActivitySectionWithAdjusted({ list, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
const title = formatCohortTitle(args.name);
|
||||
return {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createHoldingsSectionWithRelative({ cohort: args, title }),
|
||||
createValuationSection({ cohort: args, title }),
|
||||
createPricesSectionBasic({ cohort: args, title }),
|
||||
createCostBasisSection({ cohort: args, title }),
|
||||
createProfitabilitySectionWithPeakRegret({ cohort: args, title }),
|
||||
createActivitySectionWithAdjusted({ cohort: args, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Folder for cohorts with nupl + percentiles (no longer used for term.long which has own folder)
|
||||
* @param {CohortWithNuplPercentiles | CohortGroupWithNuplPercentiles} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createCohortFolderWithNupl(args) {
|
||||
if ("list" in args) {
|
||||
const { list } = args;
|
||||
const title = formatCohortTitle(args.title);
|
||||
return {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createGroupedHoldingsSectionWithRelative({ list, title }),
|
||||
createGroupedValuationSection({ list, title }),
|
||||
createGroupedPricesSection({ list, title }),
|
||||
createGroupedCostBasisSectionWithPercentiles({ list, title }),
|
||||
createGroupedProfitabilitySectionWithNupl({ list, title }),
|
||||
createGroupedActivitySection({ list, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
const title = formatCohortTitle(args.name);
|
||||
return {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createHoldingsSectionWithRelative({ cohort: args, title }),
|
||||
createValuationSectionFull({ cohort: args, title }),
|
||||
createPricesSectionFull({ cohort: args, title }),
|
||||
createCostBasisSectionWithPercentiles({ cohort: args, title }),
|
||||
createProfitabilitySectionWithNupl({ cohort: args, title }),
|
||||
createActivitySection({ cohort: args, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* LongTerm folder: term.long (has own market cap + NUPL + peak regret + P/L ratio)
|
||||
* @param {CohortLongTerm | CohortGroupLongTerm} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createCohortFolderLongTerm(args) {
|
||||
if ("list" in args) {
|
||||
const { list } = args;
|
||||
const title = formatCohortTitle(args.title);
|
||||
return {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createGroupedHoldingsSectionWithRelative({ list, title }),
|
||||
createGroupedValuationSectionWithOwnMarketCap({ list, title }),
|
||||
createGroupedPricesSection({ list, title }),
|
||||
createGroupedCostBasisSectionWithPercentiles({ list, title }),
|
||||
createGroupedProfitabilitySectionLongTerm({ list, title }),
|
||||
createGroupedActivitySection({ list, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
const title = formatCohortTitle(args.name);
|
||||
return {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createHoldingsSectionWithRelative({ cohort: args, title }),
|
||||
createValuationSectionFull({ cohort: args, title }),
|
||||
createPricesSectionFull({ cohort: args, title }),
|
||||
createCostBasisSectionWithPercentiles({ cohort: args, title }),
|
||||
createProfitabilitySectionLongTerm({ cohort: args, title }),
|
||||
createActivitySection({ cohort: args, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Age range folder: ageRange.* (no nupl via RelativePattern2)
|
||||
* @param {CohortAgeRange | CohortGroupAgeRange} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createCohortFolderAgeRange(args) {
|
||||
if ("list" in args) {
|
||||
const { list } = args;
|
||||
const title = formatCohortTitle(args.title);
|
||||
return {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createGroupedHoldingsSectionWithOwnSupply({ list, title }),
|
||||
createGroupedValuationSectionWithOwnMarketCap({ list, title }),
|
||||
createGroupedPricesSection({ list, title }),
|
||||
createGroupedCostBasisSectionWithPercentiles({ list, title }),
|
||||
createGroupedProfitabilitySectionWithInvestedCapitalPct({ list, title }),
|
||||
createGroupedActivitySection({ list, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
const title = formatCohortTitle(args.name);
|
||||
return {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createHoldingsSectionWithOwnSupply({ cohort: args, title }),
|
||||
createValuationSectionFull({ cohort: args, title }),
|
||||
createPricesSectionFull({ cohort: args, title }),
|
||||
createCostBasisSectionWithPercentiles({ cohort: args, title }),
|
||||
createProfitabilitySectionWithInvestedCapitalPct({ cohort: args, title }),
|
||||
createActivitySection({ cohort: args, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* MinAge folder - has peakRegret in unrealized (minAge.*)
|
||||
* @param {CohortMinAge | CohortGroupMinAge} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createCohortFolderMinAge(args) {
|
||||
if ("list" in args) {
|
||||
const { list } = args;
|
||||
const title = formatCohortTitle(args.title);
|
||||
return {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createGroupedHoldingsSectionWithRelative({ list, title }),
|
||||
createGroupedValuationSection({ list, title }),
|
||||
createGroupedPricesSection({ list, title }),
|
||||
createGroupedCostBasisSection({ list, title }),
|
||||
createGroupedProfitabilitySectionWithPeakRegret({ list, title }),
|
||||
createGroupedActivitySection({ list, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
const title = formatCohortTitle(args.name);
|
||||
return {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createHoldingsSectionWithRelative({ cohort: args, title }),
|
||||
createValuationSection({ cohort: args, title }),
|
||||
createPricesSectionBasic({ cohort: args, title }),
|
||||
createCostBasisSection({ cohort: args, title }),
|
||||
createProfitabilitySectionWithPeakRegret({ cohort: args, title }),
|
||||
createActivitySection({ cohort: args, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic folder WITH RelToMarketCap (geAmount.*, ltAmount.*)
|
||||
* @param {CohortBasicWithMarketCap | CohortGroupBasicWithMarketCap} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createCohortFolderBasicWithMarketCap(args) {
|
||||
if ("list" in args) {
|
||||
const { list } = args;
|
||||
const title = formatCohortTitle(args.title);
|
||||
return {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createGroupedHoldingsSectionWithRelative({ list, title }),
|
||||
createGroupedValuationSection({ list, title }),
|
||||
createGroupedPricesSection({ list, title }),
|
||||
createGroupedCostBasisSection({ list, title }),
|
||||
createGroupedProfitabilitySectionWithNupl({ list, title }),
|
||||
createGroupedActivitySection({ list, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
const title = formatCohortTitle(args.name);
|
||||
return {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createHoldingsSectionWithRelative({ cohort: args, title }),
|
||||
createValuationSection({ cohort: args, title }),
|
||||
createPricesSectionBasic({ cohort: args, title }),
|
||||
createCostBasisSection({ cohort: args, title }),
|
||||
createProfitabilitySectionWithNupl({ cohort: args, title }),
|
||||
createActivitySection({ cohort: args, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic folder WITHOUT RelToMarketCap (epoch.*, amountRange.*, year.*)
|
||||
* @param {CohortBasicWithoutMarketCap | CohortGroupBasicWithoutMarketCap} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createCohortFolderBasicWithoutMarketCap(args) {
|
||||
if ("list" in args) {
|
||||
const { list } = args;
|
||||
const title = formatCohortTitle(args.title);
|
||||
return {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createGroupedHoldingsSectionWithOwnSupply({ list, title }),
|
||||
createGroupedValuationSection({ list, title }),
|
||||
createGroupedPricesSection({ list, title }),
|
||||
createGroupedCostBasisSection({ list, title }),
|
||||
createGroupedProfitabilitySectionBasicWithInvestedCapitalPct({
|
||||
list,
|
||||
title,
|
||||
}),
|
||||
createGroupedActivitySection({ list, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
const title = formatCohortTitle(args.name);
|
||||
return {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createHoldingsSectionWithOwnSupply({ cohort: args, title }),
|
||||
createValuationSection({ cohort: args, title }),
|
||||
createPricesSectionBasic({ cohort: args, title }),
|
||||
createCostBasisSection({ cohort: args, title }),
|
||||
createProfitabilitySectionBasicWithInvestedCapitalPct({
|
||||
cohort: args,
|
||||
title,
|
||||
}),
|
||||
createActivitySection({ cohort: args, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Address folder: like basic but with address count (addressable type cohorts)
|
||||
* Has invested capital percentage metrics
|
||||
* @param {CohortAddress | CohortGroupAddress} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createCohortFolderAddress(args) {
|
||||
if ("list" in args) {
|
||||
const { list } = args;
|
||||
const title = formatCohortTitle(args.title);
|
||||
return {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createGroupedHoldingsSectionAddress({ list, title }),
|
||||
createGroupedValuationSection({ list, title }),
|
||||
createGroupedPricesSection({ list, title }),
|
||||
createGroupedCostBasisSection({ list, title }),
|
||||
createGroupedProfitabilitySectionBasicWithInvestedCapitalPct({
|
||||
list,
|
||||
title,
|
||||
}),
|
||||
createGroupedActivitySection({ list, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
const title = formatCohortTitle(args.name);
|
||||
return {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createHoldingsSectionAddress({ cohort: args, title }),
|
||||
createValuationSection({ cohort: args, title }),
|
||||
createPricesSectionBasic({ cohort: args, title }),
|
||||
createCostBasisSection({ cohort: args, title }),
|
||||
createProfitabilitySectionBasicWithInvestedCapitalPct({
|
||||
cohort: args,
|
||||
title,
|
||||
}),
|
||||
createActivitySection({ cohort: args, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Folder for cohorts WITHOUT relative section (edge case types: empty, p2ms, unknown)
|
||||
* @param {CohortWithoutRelative | CohortGroupWithoutRelative} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createCohortFolderWithoutRelative(args) {
|
||||
if ("list" in args) {
|
||||
const { list } = args;
|
||||
const title = formatCohortTitle(args.title);
|
||||
return {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createGroupedHoldingsSection({ list, title }),
|
||||
createGroupedValuationSection({ list, title }),
|
||||
createGroupedPricesSection({ list, title }),
|
||||
createGroupedCostBasisSection({ list, title }),
|
||||
createGroupedProfitabilitySection({ list, title }),
|
||||
createGroupedActivitySection({ list, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
const title = formatCohortTitle(args.name);
|
||||
return {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createHoldingsSection({ cohort: args, title }),
|
||||
createValuationSection({ cohort: args, title }),
|
||||
createPricesSectionBasic({ cohort: args, title }),
|
||||
createCostBasisSection({ cohort: args, title }),
|
||||
createProfitabilitySection({ cohort: args, title }),
|
||||
createActivitySection({ cohort: args, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Address amount cohort folder - for address balance cohorts (has NUPL + addrCount)
|
||||
* @param {AddressCohortObject | AddressCohortGroupObject} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createAddressCohortFolder(args) {
|
||||
if ("list" in args) {
|
||||
const { list } = args;
|
||||
const title = formatCohortTitle(args.title);
|
||||
return {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createGroupedHoldingsSectionAddressAmount({ list, title }),
|
||||
createGroupedValuationSection({ list, title }),
|
||||
createGroupedPricesSection({ list, title }),
|
||||
createGroupedCostBasisSection({ list, title }),
|
||||
createGroupedProfitabilitySectionWithNupl({ list, title }),
|
||||
createGroupedActivitySection({ list, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
const title = formatCohortTitle(args.name);
|
||||
return {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createHoldingsSectionAddressAmount({ cohort: args, title }),
|
||||
createValuationSection({ cohort: args, title }),
|
||||
createPricesSectionBasic({ cohort: args, title }),
|
||||
createCostBasisSection({ cohort: args, title }),
|
||||
createProfitabilitySectionWithNupl({ cohort: args, title }),
|
||||
createActivitySection({ cohort: args, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
2788
website/scripts/options/distribution/profitability.js
Normal file
2788
website/scripts/options/distribution/profitability.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -341,13 +341,6 @@ export function createGroupedSupplySection(
|
||||
return {
|
||||
name: "Supply",
|
||||
tree: [
|
||||
{
|
||||
name: "Total",
|
||||
title: title("Supply"),
|
||||
bottom: createGroupedSupplyTotalSeries(list, {
|
||||
relativeMetrics: supplyRelativeMetrics,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "30d Change",
|
||||
title: title("Supply 30d Change"),
|
||||
@@ -369,6 +362,13 @@ export function createGroupedSupplySection(
|
||||
relativeMetrics: lossRelativeMetrics,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Total",
|
||||
title: title("Supply"),
|
||||
bottom: createGroupedSupplyTotalSeries(list, {
|
||||
relativeMetrics: supplyRelativeMetrics,
|
||||
}),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -988,10 +988,10 @@ export function createSingleSentSeries(cohort) {
|
||||
*/
|
||||
export function createSingleSellSideRiskSeries(tree) {
|
||||
return [
|
||||
dots({
|
||||
metric: tree.realized.sellSideRiskRatio,
|
||||
name: "Raw",
|
||||
color: colors.bitcoin,
|
||||
line({
|
||||
metric: tree.realized.sellSideRiskRatio30dEma,
|
||||
name: "30d EMA",
|
||||
color: colors.ma._1m,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
line({
|
||||
@@ -1000,10 +1000,10 @@ export function createSingleSellSideRiskSeries(tree) {
|
||||
color: colors.ma._1w,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.sellSideRiskRatio30dEma,
|
||||
name: "30d EMA",
|
||||
color: colors.ma._1m,
|
||||
dots({
|
||||
metric: tree.realized.sellSideRiskRatio,
|
||||
name: "Raw",
|
||||
color: colors.bitcoin,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
];
|
||||
@@ -1130,7 +1130,7 @@ export function createSingleSoprSeries(tree) {
|
||||
baseline({
|
||||
metric: tree.realized.sopr7dEma,
|
||||
name: "7d EMA",
|
||||
color: colors.bi.sopr7d,
|
||||
color: colors.bi.p2,
|
||||
unit: Unit.ratio,
|
||||
defaultActive: false,
|
||||
base: 1,
|
||||
@@ -1138,7 +1138,7 @@ export function createSingleSoprSeries(tree) {
|
||||
baseline({
|
||||
metric: tree.realized.sopr30dEma,
|
||||
name: "30d EMA",
|
||||
color: colors.bi.sopr30d,
|
||||
color: colors.bi.p2,
|
||||
unit: Unit.ratio,
|
||||
defaultActive: false,
|
||||
base: 1,
|
||||
@@ -1340,12 +1340,14 @@ export function createSingleSentimentSeries(tree) {
|
||||
name: "Greed Index",
|
||||
color: colors.profit,
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: tree.unrealized.painIndex,
|
||||
name: "Pain Index",
|
||||
color: colors.loss,
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -24,13 +24,7 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
createSingleSellSideRiskSeries,
|
||||
createGroupedSellSideRiskSeries,
|
||||
createSingleValueCreatedDestroyedSeries,
|
||||
createSingleValueFlowBreakdownSeries,
|
||||
createSingleCapitulationProfitFlowSeries,
|
||||
createSingleSoprSeries,
|
||||
createSingleCoinsDestroyedSeries,
|
||||
createSingleRealizedAthRegretSeries,
|
||||
createGroupedRealizedAthRegretSeries,
|
||||
createSingleSentimentSeries,
|
||||
@@ -64,6 +58,20 @@ import {
|
||||
createValuationSectionFull,
|
||||
createGroupedValuationSection,
|
||||
} from "./valuation.js";
|
||||
import {
|
||||
createActivitySection,
|
||||
createActivitySectionWithAdjusted,
|
||||
createGroupedActivitySection,
|
||||
createGroupedActivitySectionWithAdjusted,
|
||||
} from "./activity.js";
|
||||
import {
|
||||
createProfitabilitySection,
|
||||
createProfitabilitySectionWithNupl,
|
||||
createProfitabilitySectionAll,
|
||||
createProfitabilitySectionWithPeakRegret,
|
||||
createGroupedProfitabilitySection,
|
||||
createGroupedProfitabilitySectionWithNupl,
|
||||
} from "./profitability.js";
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { line, baseline } from "../series.js";
|
||||
import { priceLine } from "../constants.js";
|
||||
@@ -84,12 +92,11 @@ export function createCohortFolderAll(args) {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createHoldingsSectionAll({ cohort: args, title }),
|
||||
createPricesSectionFull({ cohort: args, title }),
|
||||
createValuationSectionFull({ cohort: args, title }),
|
||||
createSingleRealizedSectionFull(args, title),
|
||||
createSingleUnrealizedSectionAll(args, title),
|
||||
createPricesSectionFull({ cohort: args, title }),
|
||||
createCostBasisSectionWithPercentiles({ cohort: args, title }),
|
||||
createSingleActivitySectionWithAdjusted(args, title),
|
||||
createProfitabilitySectionAll({ cohort: args, title }),
|
||||
createActivitySectionWithAdjusted({ cohort: args, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -107,14 +114,11 @@ export function createCohortFolderFull(args) {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createGroupedHoldingsSectionWithRelative({ list, title }),
|
||||
createGroupedPricesSection({ list, title }),
|
||||
createGroupedValuationSection({ list, title }),
|
||||
createGroupedRealizedSectionWithAdjusted(list, title, {
|
||||
ratioMetrics: createGroupedRealizedPnlRatioMetrics,
|
||||
}),
|
||||
createGroupedUnrealizedSectionFull(list, title),
|
||||
createGroupedPricesSection({ list, title }),
|
||||
createGroupedCostBasisSectionWithPercentiles({ list, title }),
|
||||
createGroupedActivitySectionWithAdjusted(list, title),
|
||||
createGroupedProfitabilitySectionWithNupl({ list, title }),
|
||||
createGroupedActivitySectionWithAdjusted({ list, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -123,12 +127,11 @@ export function createCohortFolderFull(args) {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createHoldingsSectionWithRelative({ cohort: args, title }),
|
||||
createPricesSectionFull({ cohort: args, title }),
|
||||
createValuationSectionFull({ cohort: args, title }),
|
||||
createSingleRealizedSectionFull(args, title),
|
||||
createSingleUnrealizedSectionFull(args, title),
|
||||
createPricesSectionFull({ cohort: args, title }),
|
||||
createCostBasisSectionWithPercentiles({ cohort: args, title }),
|
||||
createSingleActivitySectionWithAdjusted(args, title),
|
||||
createProfitabilitySectionWithNupl({ cohort: args, title }),
|
||||
createActivitySectionWithAdjusted({ cohort: args, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -146,12 +149,11 @@ export function createCohortFolderWithAdjusted(args) {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createGroupedHoldingsSectionWithRelative({ list, title }),
|
||||
createGroupedPricesSection({ list, title }),
|
||||
createGroupedValuationSection({ list, title }),
|
||||
createGroupedRealizedSectionWithAdjusted(list, title),
|
||||
createGroupedUnrealizedSectionWithMarketCap(list, title),
|
||||
createGroupedPricesSection({ list, title }),
|
||||
createGroupedCostBasisSection({ list, title }),
|
||||
createGroupedActivitySectionWithAdjusted(list, title),
|
||||
createGroupedProfitabilitySectionWithNupl({ list, title }),
|
||||
createGroupedActivitySectionWithAdjusted({ list, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -160,12 +162,11 @@ export function createCohortFolderWithAdjusted(args) {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createHoldingsSectionWithRelative({ cohort: args, title }),
|
||||
createPricesSectionBasic({ cohort: args, title }),
|
||||
createValuationSection({ cohort: args, title }),
|
||||
createSingleRealizedSectionWithAdjusted(args, title),
|
||||
createSingleUnrealizedSectionWithMarketCap(args, title),
|
||||
createPricesSectionBasic({ cohort: args, title }),
|
||||
createCostBasisSection({ cohort: args, title }),
|
||||
createSingleActivitySectionWithAdjusted(args, title),
|
||||
createProfitabilitySectionWithNupl({ cohort: args, title }),
|
||||
createActivitySectionWithAdjusted({ cohort: args, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -183,13 +184,10 @@ export function createCohortFolderWithNupl(args) {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createGroupedHoldingsSectionWithRelative({ list, title }),
|
||||
createGroupedPricesSection({ list, title }),
|
||||
createGroupedValuationSection({ list, title }),
|
||||
createGroupedRealizedSectionBasic(list, title, {
|
||||
ratioMetrics: createGroupedRealizedPnlRatioMetrics,
|
||||
}),
|
||||
createGroupedUnrealizedSectionWithNupl({ list, title }),
|
||||
createGroupedPricesSection({ list, title }),
|
||||
createGroupedCostBasisSectionWithPercentiles({ list, title }),
|
||||
createGroupedProfitabilitySectionWithNupl({ list, title }),
|
||||
createGroupedActivitySection({ list, title }),
|
||||
],
|
||||
};
|
||||
@@ -199,11 +197,10 @@ export function createCohortFolderWithNupl(args) {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createHoldingsSectionWithRelative({ cohort: args, title }),
|
||||
createPricesSectionFull({ cohort: args, title }),
|
||||
createValuationSectionFull({ cohort: args, title }),
|
||||
createSingleRealizedSectionWithPercentiles(args, title),
|
||||
createSingleUnrealizedSectionWithNupl({ cohort: args, title }),
|
||||
createPricesSectionFull({ cohort: args, title }),
|
||||
createCostBasisSectionWithPercentiles({ cohort: args, title }),
|
||||
createProfitabilitySectionWithNupl({ cohort: args, title }),
|
||||
createActivitySection({ cohort: args, title }),
|
||||
],
|
||||
};
|
||||
@@ -222,13 +219,10 @@ export function createCohortFolderAgeRange(args) {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createGroupedHoldingsSection({ list, title }),
|
||||
createGroupedPricesSection({ list, title }),
|
||||
createGroupedValuationSection({ list, title }),
|
||||
createGroupedRealizedSectionBasic(list, title, {
|
||||
ratioMetrics: createGroupedRealizedPnlRatioMetrics,
|
||||
}),
|
||||
createGroupedUnrealizedSectionAgeRange(list, title),
|
||||
createGroupedPricesSection({ list, title }),
|
||||
createGroupedCostBasisSectionWithPercentiles({ list, title }),
|
||||
createGroupedProfitabilitySection({ list, title }),
|
||||
createGroupedActivitySection({ list, title }),
|
||||
],
|
||||
};
|
||||
@@ -238,11 +232,10 @@ export function createCohortFolderAgeRange(args) {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createHoldingsSection({ cohort: args, title }),
|
||||
createPricesSectionFull({ cohort: args, title }),
|
||||
createValuationSectionFull({ cohort: args, title }),
|
||||
createSingleRealizedSectionWithPercentiles(args, title),
|
||||
createSingleUnrealizedSectionAgeRange(args, title),
|
||||
createPricesSectionFull({ cohort: args, title }),
|
||||
createCostBasisSectionWithPercentiles({ cohort: args, title }),
|
||||
createProfitabilitySection({ cohort: args, title }),
|
||||
createActivitySection({ cohort: args, title }),
|
||||
],
|
||||
};
|
||||
@@ -261,11 +254,10 @@ export function createCohortFolderMinAge(args) {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createGroupedHoldingsSectionWithRelative({ list, title }),
|
||||
createGroupedPricesSection({ list, title }),
|
||||
createGroupedValuationSection({ list, title }),
|
||||
createGroupedRealizedSectionBasic(list, title),
|
||||
createGroupedUnrealizedSectionMinAge(list, title),
|
||||
createGroupedPricesSection({ list, title }),
|
||||
createGroupedCostBasisSection({ list, title }),
|
||||
createGroupedProfitabilitySection({ list, title }),
|
||||
createGroupedActivitySection({ list, title }),
|
||||
],
|
||||
};
|
||||
@@ -275,11 +267,10 @@ export function createCohortFolderMinAge(args) {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createHoldingsSectionWithRelative({ cohort: args, title }),
|
||||
createPricesSectionBasic({ cohort: args, title }),
|
||||
createValuationSection({ cohort: args, title }),
|
||||
createSingleRealizedSectionBasic(args, title),
|
||||
createSingleUnrealizedSectionMinAge(args, title),
|
||||
createPricesSectionBasic({ cohort: args, title }),
|
||||
createCostBasisSection({ cohort: args, title }),
|
||||
createProfitabilitySectionWithPeakRegret({ cohort: args, title }),
|
||||
createActivitySection({ cohort: args, title }),
|
||||
],
|
||||
};
|
||||
@@ -298,11 +289,10 @@ export function createCohortFolderBasicWithMarketCap(args) {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createGroupedHoldingsSectionWithRelative({ list, title }),
|
||||
createGroupedPricesSection({ list, title }),
|
||||
createGroupedValuationSection({ list, title }),
|
||||
createGroupedRealizedSectionBasic(list, title),
|
||||
createGroupedUnrealizedSectionWithMarketCapOnly(list, title),
|
||||
createGroupedPricesSection({ list, title }),
|
||||
createGroupedCostBasisSection({ list, title }),
|
||||
createGroupedProfitabilitySectionWithNupl({ list, title }),
|
||||
createGroupedActivitySection({ list, title }),
|
||||
],
|
||||
};
|
||||
@@ -312,11 +302,10 @@ export function createCohortFolderBasicWithMarketCap(args) {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createHoldingsSectionWithRelative({ cohort: args, title }),
|
||||
createPricesSectionBasic({ cohort: args, title }),
|
||||
createValuationSection({ cohort: args, title }),
|
||||
createSingleRealizedSectionBasic(args, title),
|
||||
createSingleUnrealizedSectionWithMarketCapOnly(args, title),
|
||||
createPricesSectionBasic({ cohort: args, title }),
|
||||
createCostBasisSection({ cohort: args, title }),
|
||||
createProfitabilitySectionWithNupl({ cohort: args, title }),
|
||||
createActivitySection({ cohort: args, title }),
|
||||
],
|
||||
};
|
||||
@@ -335,11 +324,10 @@ export function createCohortFolderBasicWithoutMarketCap(args) {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createGroupedHoldingsSection({ list, title }),
|
||||
createGroupedPricesSection({ list, title }),
|
||||
createGroupedValuationSection({ list, title }),
|
||||
createGroupedRealizedSectionBasic(list, title),
|
||||
createGroupedUnrealizedSectionBase(list, title),
|
||||
createGroupedPricesSection({ list, title }),
|
||||
createGroupedCostBasisSection({ list, title }),
|
||||
createGroupedProfitabilitySection({ list, title }),
|
||||
createGroupedActivitySection({ list, title }),
|
||||
],
|
||||
};
|
||||
@@ -349,11 +337,10 @@ export function createCohortFolderBasicWithoutMarketCap(args) {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createHoldingsSection({ cohort: args, title }),
|
||||
createPricesSectionBasic({ cohort: args, title }),
|
||||
createValuationSection({ cohort: args, title }),
|
||||
createSingleRealizedSectionBasic(args, title),
|
||||
createSingleUnrealizedSectionBase(args, title),
|
||||
createPricesSectionBasic({ cohort: args, title }),
|
||||
createCostBasisSection({ cohort: args, title }),
|
||||
createProfitabilitySection({ cohort: args, title }),
|
||||
createActivitySection({ cohort: args, title }),
|
||||
],
|
||||
};
|
||||
@@ -373,11 +360,10 @@ export function createCohortFolderAddress(args) {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createGroupedHoldingsSectionAddress({ list, title }),
|
||||
createGroupedPricesSection({ list, title }),
|
||||
createGroupedValuationSection({ list, title }),
|
||||
createGroupedRealizedSectionBasic(list, title),
|
||||
createGroupedUnrealizedSectionBase(list, title),
|
||||
createGroupedPricesSection({ list, title }),
|
||||
createGroupedCostBasisSection({ list, title }),
|
||||
createGroupedProfitabilitySection({ list, title }),
|
||||
createGroupedActivitySection({ list, title }),
|
||||
],
|
||||
};
|
||||
@@ -387,11 +373,10 @@ export function createCohortFolderAddress(args) {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createHoldingsSectionAddress({ cohort: args, title }),
|
||||
createPricesSectionBasic({ cohort: args, title }),
|
||||
createValuationSection({ cohort: args, title }),
|
||||
createSingleRealizedSectionBasic(args, title),
|
||||
createSingleUnrealizedSectionBase(args, title),
|
||||
createPricesSectionBasic({ cohort: args, title }),
|
||||
createCostBasisSection({ cohort: args, title }),
|
||||
createProfitabilitySection({ cohort: args, title }),
|
||||
createActivitySection({ cohort: args, title }),
|
||||
],
|
||||
};
|
||||
@@ -410,11 +395,10 @@ export function createCohortFolderWithoutRelative(args) {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createGroupedHoldingsSection({ list, title }),
|
||||
createGroupedPricesSection({ list, title }),
|
||||
createGroupedValuationSection({ list, title }),
|
||||
createGroupedRealizedSectionBasic(list, title),
|
||||
createGroupedUnrealizedSectionWithoutRelative(list, title),
|
||||
createGroupedPricesSection({ list, title }),
|
||||
createGroupedCostBasisSection({ list, title }),
|
||||
createGroupedProfitabilitySection({ list, title }),
|
||||
createGroupedActivitySection({ list, title }),
|
||||
],
|
||||
};
|
||||
@@ -424,11 +408,10 @@ export function createCohortFolderWithoutRelative(args) {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createHoldingsSection({ cohort: args, title }),
|
||||
createPricesSectionBasic({ cohort: args, title }),
|
||||
createValuationSection({ cohort: args, title }),
|
||||
createSingleRealizedSectionBasic(args, title),
|
||||
createSingleUnrealizedSectionWithoutRelative(args, title),
|
||||
createPricesSectionBasic({ cohort: args, title }),
|
||||
createCostBasisSection({ cohort: args, title }),
|
||||
createProfitabilitySection({ cohort: args, title }),
|
||||
createActivitySection({ cohort: args, title }),
|
||||
],
|
||||
};
|
||||
@@ -734,6 +717,7 @@ function createSingleRealizedPnlSection(cohort, title, { extra = [] } = {}) {
|
||||
name: "Sum",
|
||||
title: title("Realized P&L"),
|
||||
bottom: [
|
||||
// USD
|
||||
line({
|
||||
metric: tree.realized.realizedProfit.sum,
|
||||
name: "Profit",
|
||||
@@ -773,12 +757,26 @@ function createSingleRealizedPnlSection(cohort, title, { extra = [] } = {}) {
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
// % of R.Cap
|
||||
baseline({
|
||||
metric: tree.realized.realizedProfitRelToRealizedCap.sum,
|
||||
name: "Profit",
|
||||
color: colors.profit,
|
||||
unit: Unit.pctRcap,
|
||||
}),
|
||||
baseline({
|
||||
metric: tree.realized.realizedLossRelToRealizedCap.sum,
|
||||
name: "Loss",
|
||||
color: colors.loss,
|
||||
unit: Unit.pctRcap,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: title("Realized P&L (Total)"),
|
||||
bottom: [
|
||||
// USD
|
||||
line({
|
||||
metric: tree.realized.realizedProfit.cumulative,
|
||||
name: "Profit",
|
||||
@@ -798,42 +796,23 @@ function createSingleRealizedPnlSection(cohort, title, { extra = [] } = {}) {
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
// % of R.Cap
|
||||
baseline({
|
||||
metric: tree.realized.realizedProfitRelToRealizedCap.cumulative,
|
||||
name: "Profit",
|
||||
color: colors.profit,
|
||||
unit: Unit.pctRcap,
|
||||
}),
|
||||
baseline({
|
||||
metric: tree.realized.realizedLossRelToRealizedCap.cumulative,
|
||||
name: "Loss",
|
||||
color: colors.loss,
|
||||
unit: Unit.pctRcap,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "P&L Relative",
|
||||
title: title("Realized P&L"),
|
||||
bottom: [
|
||||
baseline({
|
||||
metric: tree.realized.realizedProfitRelToRealizedCap.sum,
|
||||
name: "Profit",
|
||||
color: colors.profit,
|
||||
unit: Unit.pctRcap,
|
||||
}),
|
||||
baseline({
|
||||
metric: tree.realized.realizedProfitRelToRealizedCap.cumulative,
|
||||
name: "Profit Cumulative",
|
||||
color: colors.profit,
|
||||
unit: Unit.pctRcap,
|
||||
defaultActive: false,
|
||||
}),
|
||||
baseline({
|
||||
metric: tree.realized.realizedLossRelToRealizedCap.sum,
|
||||
name: "Loss",
|
||||
color: colors.loss,
|
||||
unit: Unit.pctRcap,
|
||||
}),
|
||||
baseline({
|
||||
metric: tree.realized.realizedLossRelToRealizedCap.cumulative,
|
||||
name: "Loss Cumulative",
|
||||
color: colors.loss,
|
||||
unit: Unit.pctRcap,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Net P&L",
|
||||
tree: [
|
||||
@@ -841,6 +820,7 @@ function createSingleRealizedPnlSection(cohort, title, { extra = [] } = {}) {
|
||||
name: "Sum",
|
||||
title: title("Net Realized P&L"),
|
||||
bottom: [
|
||||
// USD
|
||||
baseline({
|
||||
metric: tree.realized.netRealizedPnl.sum,
|
||||
name: "Net",
|
||||
@@ -851,23 +831,19 @@ function createSingleRealizedPnlSection(cohort, title, { extra = [] } = {}) {
|
||||
name: "Net 7d EMA",
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
// % of R.Cap
|
||||
baseline({
|
||||
metric: tree.realized.netRealizedPnlRelToRealizedCap.sum,
|
||||
name: "Net",
|
||||
unit: Unit.pctRcap,
|
||||
}),
|
||||
baseline({
|
||||
metric:
|
||||
tree.realized.netRealizedPnlCumulative30dDeltaRelToMarketCap,
|
||||
name: "30d Change",
|
||||
unit: Unit.pctMcap,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: title("Net Realized P&L (Total)"),
|
||||
bottom: [
|
||||
// USD
|
||||
baseline({
|
||||
metric: tree.realized.netRealizedPnl.cumulative,
|
||||
name: "Net",
|
||||
@@ -879,6 +855,7 @@ function createSingleRealizedPnlSection(cohort, title, { extra = [] } = {}) {
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
// % of R.Cap
|
||||
baseline({
|
||||
metric: tree.realized.netRealizedPnlRelToRealizedCap.cumulative,
|
||||
name: "Net",
|
||||
@@ -891,6 +868,13 @@ function createSingleRealizedPnlSection(cohort, title, { extra = [] } = {}) {
|
||||
unit: Unit.pctRcap,
|
||||
defaultActive: false,
|
||||
}),
|
||||
// % of M.Cap
|
||||
baseline({
|
||||
metric:
|
||||
tree.realized.netRealizedPnlCumulative30dDeltaRelToMarketCap,
|
||||
name: "30d Change",
|
||||
unit: Unit.pctMcap,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -1330,14 +1314,14 @@ function createSingleAdjustedSoprChart(cohort, title) {
|
||||
baseline({
|
||||
metric: tree.realized.adjustedSopr,
|
||||
name: "Adjusted",
|
||||
color: colors.bi.adjustedSopr,
|
||||
color: colors.bi.p1,
|
||||
unit: Unit.ratio,
|
||||
base: 1,
|
||||
}),
|
||||
baseline({
|
||||
metric: tree.realized.adjustedSopr7dEma,
|
||||
name: "Adj. 7d EMA",
|
||||
color: colors.bi.adjustedSopr7d,
|
||||
color: colors.bi.p2,
|
||||
unit: Unit.ratio,
|
||||
defaultActive: false,
|
||||
base: 1,
|
||||
@@ -1345,7 +1329,7 @@ function createSingleAdjustedSoprChart(cohort, title) {
|
||||
baseline({
|
||||
metric: tree.realized.adjustedSopr30dEma,
|
||||
name: "Adj. 30d EMA",
|
||||
color: colors.bi.adjustedSopr30d,
|
||||
color: colors.bi.p3,
|
||||
unit: Unit.ratio,
|
||||
defaultActive: false,
|
||||
base: 1,
|
||||
@@ -1639,13 +1623,13 @@ function createInvestedCapitalRelMetrics(rel) {
|
||||
metric: rel.investedCapitalInProfitPct,
|
||||
name: "In Profit",
|
||||
color: colors.profit,
|
||||
unit: Unit.pctRcap,
|
||||
unit: Unit.pctOwnRcap,
|
||||
}),
|
||||
baseline({
|
||||
metric: rel.investedCapitalInLossPct,
|
||||
name: "In Loss",
|
||||
color: colors.loss,
|
||||
unit: Unit.pctRcap,
|
||||
unit: Unit.pctOwnRcap,
|
||||
}),
|
||||
];
|
||||
}
|
||||
@@ -1656,12 +1640,6 @@ function createInvestedCapitalRelMetrics(rel) {
|
||||
*/
|
||||
function createUnrealizedPnlBaseMetrics(tree) {
|
||||
return [
|
||||
line({
|
||||
metric: tree.unrealized.totalUnrealizedPnl,
|
||||
name: "Total",
|
||||
color: colors.default,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: tree.unrealized.unrealizedProfit,
|
||||
name: "Profit",
|
||||
@@ -1682,6 +1660,12 @@ function createUnrealizedPnlBaseMetrics(tree) {
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: tree.unrealized.totalUnrealizedPnl,
|
||||
name: "Total",
|
||||
color: colors.default,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1934,7 +1918,7 @@ function createUnrealizedSection({
|
||||
charts = [],
|
||||
}) {
|
||||
return {
|
||||
name: "Unrealized",
|
||||
name: "Profitability",
|
||||
tree: [
|
||||
{
|
||||
name: "P&L",
|
||||
@@ -2008,7 +1992,7 @@ function createGroupedInvestedCapitalRelativeCharts(list, title) {
|
||||
metric: tree.relative.investedCapitalInProfitPct,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.pctRcap,
|
||||
unit: Unit.pctOwnRcap,
|
||||
}),
|
||||
),
|
||||
},
|
||||
@@ -2020,7 +2004,7 @@ function createGroupedInvestedCapitalRelativeCharts(list, title) {
|
||||
metric: tree.relative.investedCapitalInLossPct,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.pctRcap,
|
||||
unit: Unit.pctOwnRcap,
|
||||
}),
|
||||
),
|
||||
},
|
||||
@@ -2044,7 +2028,7 @@ function createGroupedUnrealizedSection({
|
||||
charts = [],
|
||||
}) {
|
||||
return {
|
||||
name: "Unrealized",
|
||||
name: "Profitability",
|
||||
tree: [
|
||||
...createGroupedUnrealizedBaseCharts(list, title),
|
||||
{
|
||||
@@ -2102,7 +2086,7 @@ function createGroupedUnrealizedSection({
|
||||
*/
|
||||
function createGroupedUnrealizedSectionWithoutRelative(list, title) {
|
||||
return {
|
||||
name: "Unrealized",
|
||||
name: "Profitability",
|
||||
tree: [
|
||||
...createGroupedUnrealizedBaseCharts(list, title),
|
||||
{
|
||||
@@ -2563,439 +2547,3 @@ function createGroupedUnrealizedSectionAgeRange(list, title) {
|
||||
charts: [createGroupedPeakRegretChartBasic(list, title)],
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Cost Basis Section Builders (generic, type-safe composition)
|
||||
// ============================================================================
|
||||
// ============================================================================
|
||||
// Activity Section Builders (generic, type-safe composition)
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Generic single activity section builder - callers pass optional extra value metrics
|
||||
* @param {Object} args
|
||||
* @param {UtxoCohortObject | CohortWithoutRelative} args.cohort
|
||||
* @param {(metric: string) => string} args.title
|
||||
* @param {AnyFetchedSeriesBlueprint[]} [args.valueMetrics] - Extra value metrics (e.g., adjusted)
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
function createActivitySection({ cohort, title, valueMetrics = [] }) {
|
||||
const { tree, color } = cohort;
|
||||
|
||||
return {
|
||||
name: "Activity",
|
||||
tree: [
|
||||
{
|
||||
name: "Sent",
|
||||
tree: [
|
||||
{
|
||||
name: "Sum",
|
||||
title: title("Sent"),
|
||||
bottom: [
|
||||
line({
|
||||
metric: tree.activity.sent.sats.sum,
|
||||
name: "sum",
|
||||
color,
|
||||
unit: Unit.sats,
|
||||
}),
|
||||
line({
|
||||
metric: tree.activity.sent.bitcoin.sum,
|
||||
name: "sum",
|
||||
color,
|
||||
unit: Unit.btc,
|
||||
}),
|
||||
line({
|
||||
metric: tree.activity.sent.dollars.sum,
|
||||
name: "sum",
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: tree.activity.sent14dEma.sats,
|
||||
name: "14d EMA",
|
||||
unit: Unit.sats,
|
||||
}),
|
||||
line({
|
||||
metric: tree.activity.sent14dEma.bitcoin,
|
||||
name: "14d EMA",
|
||||
unit: Unit.btc,
|
||||
}),
|
||||
line({
|
||||
metric: tree.activity.sent14dEma.dollars,
|
||||
name: "14d EMA",
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: title("Sent (Total)"),
|
||||
bottom: [
|
||||
line({
|
||||
metric: tree.activity.sent.sats.cumulative,
|
||||
name: "all-time",
|
||||
color,
|
||||
unit: Unit.sats,
|
||||
}),
|
||||
line({
|
||||
metric: tree.activity.sent.bitcoin.cumulative,
|
||||
name: "all-time",
|
||||
color,
|
||||
unit: Unit.btc,
|
||||
}),
|
||||
line({
|
||||
metric: tree.activity.sent.dollars.cumulative,
|
||||
name: "all-time",
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Sell Side Risk",
|
||||
title: title("Sell Side Risk Ratio"),
|
||||
bottom: createSingleSellSideRiskSeries(tree),
|
||||
},
|
||||
{
|
||||
name: "Value",
|
||||
tree: [
|
||||
{
|
||||
name: "Created & Destroyed",
|
||||
title: title("Value Created & Destroyed"),
|
||||
bottom: [
|
||||
...createSingleValueCreatedDestroyedSeries(tree),
|
||||
...valueMetrics,
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Breakdown",
|
||||
title: title("Value Flow Breakdown"),
|
||||
bottom: createSingleValueFlowBreakdownSeries(tree),
|
||||
},
|
||||
{
|
||||
name: "Flow",
|
||||
title: title("Capitulation & Profit Flow"),
|
||||
bottom: createSingleCapitulationProfitFlowSeries(tree),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Coins Destroyed",
|
||||
title: title("Coins Destroyed"),
|
||||
bottom: createSingleCoinsDestroyedSeries(cohort),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create grouped value flow charts (profit/loss created/destroyed, profit/capitulation flow)
|
||||
* @template {readonly (UtxoCohortObject | CohortWithoutRelative)[]} T
|
||||
* @param {T} list
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function createGroupedValueFlowCharts(list, title) {
|
||||
return [
|
||||
{
|
||||
name: "Profit Created",
|
||||
title: title("Profit Value Created"),
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
line({
|
||||
metric: tree.realized.profitValueCreated,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
{
|
||||
name: "Profit Destroyed",
|
||||
title: title("Profit Value Destroyed"),
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
line({
|
||||
metric: tree.realized.profitValueDestroyed,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
{
|
||||
name: "Loss Created",
|
||||
title: title("Loss Value Created"),
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
line({
|
||||
metric: tree.realized.lossValueCreated,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
{
|
||||
name: "Loss Destroyed",
|
||||
title: title("Loss Value Destroyed"),
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
line({
|
||||
metric: tree.realized.lossValueDestroyed,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
{
|
||||
name: "Profit Flow",
|
||||
title: title("Profit Flow"),
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
line({
|
||||
metric: tree.realized.profitFlow,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
{
|
||||
name: "Capitulation Flow",
|
||||
title: title("Capitulation Flow"),
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
line({
|
||||
metric: tree.realized.capitulationFlow,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic grouped activity section builder - callers pass optional value tree
|
||||
* @template {readonly (UtxoCohortObject | CohortWithoutRelative)[]} T
|
||||
* @param {Object} args
|
||||
* @param {T} args.list
|
||||
* @param {(metric: string) => string} args.title
|
||||
* @param {PartialOptionsTree} [args.valueTree] - Optional value tree (defaults to basic created/destroyed)
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
function createGroupedActivitySection({ list, title, valueTree }) {
|
||||
return {
|
||||
name: "Activity",
|
||||
tree: [
|
||||
{
|
||||
name: "Sent",
|
||||
tree: [
|
||||
{
|
||||
name: "Sum",
|
||||
title: title("Sent"),
|
||||
bottom: list.flatMap(({ color, name, tree }) =>
|
||||
satsBtcUsd({
|
||||
pattern: {
|
||||
sats: tree.activity.sent.sats.sum,
|
||||
bitcoin: tree.activity.sent.bitcoin.sum,
|
||||
dollars: tree.activity.sent.dollars.sum,
|
||||
},
|
||||
name,
|
||||
color,
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "14d EMA",
|
||||
title: title("Sent 14d EMA"),
|
||||
bottom: list.flatMap(({ color, name, tree }) =>
|
||||
satsBtcUsd({ pattern: tree.activity.sent14dEma, name, color }),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Sell Side Risk",
|
||||
title: title("Sell Side Risk Ratio"),
|
||||
bottom: createGroupedSellSideRiskSeries(list),
|
||||
},
|
||||
{
|
||||
name: "Value",
|
||||
tree: valueTree ?? [
|
||||
{
|
||||
name: "Created",
|
||||
title: title("Value Created"),
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
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,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
...createGroupedValueFlowCharts(list, title),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Coins Destroyed",
|
||||
tree: [
|
||||
{
|
||||
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,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
{
|
||||
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,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Activity Section Variants (by cohort capability)
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Create activity section with adjusted values (for cohorts with RealizedPattern3/4)
|
||||
* @param {CohortAll | CohortFull | CohortWithAdjusted} cohort
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
function createSingleActivitySectionWithAdjusted(cohort, title) {
|
||||
const { tree } = cohort;
|
||||
return createActivitySection({
|
||||
cohort,
|
||||
title,
|
||||
valueMetrics: [
|
||||
line({
|
||||
metric: tree.realized.adjustedValueCreated,
|
||||
name: "Adjusted Created",
|
||||
color: colors.adjustedCreated,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.adjustedValueDestroyed,
|
||||
name: "Adjusted Destroyed",
|
||||
color: colors.adjustedDestroyed,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create activity section for grouped cohorts with adjusted values (for cohorts with RealizedPattern3/4)
|
||||
* @param {readonly (CohortFull | CohortWithAdjusted)[]} list
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
function createGroupedActivitySectionWithAdjusted(list, title) {
|
||||
return createGroupedActivitySection({
|
||||
list,
|
||||
title,
|
||||
valueTree: [
|
||||
{
|
||||
name: "Created",
|
||||
tree: [
|
||||
{
|
||||
name: "Normal",
|
||||
title: title("Value Created"),
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
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,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Destroyed",
|
||||
tree: [
|
||||
{
|
||||
name: "Normal",
|
||||
title: title("Value Destroyed"),
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
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,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
],
|
||||
},
|
||||
...createGroupedValueFlowCharts(list, title),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -58,7 +58,15 @@ export function createValuationSectionFull({ cohort, title }) {
|
||||
{
|
||||
name: "Realized Cap",
|
||||
title: title("Realized Cap"),
|
||||
bottom: createSingleRealizedCapSeries(cohort),
|
||||
bottom: [
|
||||
...createSingleRealizedCapSeries(cohort),
|
||||
baseline({
|
||||
metric: tree.realized.realizedCapRelToOwnMarketCap,
|
||||
name: "Rel. to Own M.Cap",
|
||||
color,
|
||||
unit: Unit.pctOwnMcap,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "30d Change",
|
||||
@@ -163,3 +171,63 @@ export function createGroupedValuationSection({ list, title }) {
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {{ name: string, color: Color, tree: { realized: { realizedCap: AnyMetricPattern, realizedCap30dDelta: AnyMetricPattern, realizedCapRelToOwnMarketCap: AnyMetricPattern, realizedPriceExtra: { ratio: AnyMetricPattern } } } }} T
|
||||
* @param {{ list: readonly T[], title: (metric: string) => string }} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createGroupedValuationSectionWithOwnMarketCap({ 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,
|
||||
}),
|
||||
),
|
||||
...list.map(({ name, color, tree }) =>
|
||||
baseline({
|
||||
metric: tree.realized.realizedCapRelToOwnMarketCap,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.pctOwnMcap,
|
||||
}),
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
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,
|
||||
}),
|
||||
),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user