mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-25 07:09:59 -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,
|
||||
}),
|
||||
),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -327,7 +327,7 @@ export function createDcaVsLumpSumSection({ dca, lookback, returns }) {
|
||||
baseline({
|
||||
metric: dca.periodLumpSumMaxReturn[key],
|
||||
name: "Lump Sum",
|
||||
color: colors.bi.lumpSum,
|
||||
color: colors.bi.p2,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
],
|
||||
@@ -345,7 +345,7 @@ export function createDcaVsLumpSumSection({ dca, lookback, returns }) {
|
||||
baseline({
|
||||
metric: dca.periodLumpSumMinReturn[key],
|
||||
name: "Lump Sum",
|
||||
color: colors.bi.lumpSum,
|
||||
color: colors.bi.p2,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
],
|
||||
@@ -369,7 +369,7 @@ export function createDcaVsLumpSumSection({ dca, lookback, returns }) {
|
||||
baseline({
|
||||
metric: dca.periodLumpSumReturns[key],
|
||||
name: "Lump Sum",
|
||||
color: colors.bi.lumpSum,
|
||||
color: colors.bi.p2,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
],
|
||||
@@ -395,7 +395,7 @@ export function createDcaVsLumpSumSection({ dca, lookback, returns }) {
|
||||
baseline({
|
||||
metric: dca.periodLumpSumReturns[key],
|
||||
name: "Lump Sum",
|
||||
color: colors.bi.lumpSum,
|
||||
color: colors.bi.p2,
|
||||
unit: Unit.percentage,
|
||||
}),
|
||||
baseline({
|
||||
@@ -406,7 +406,7 @@ export function createDcaVsLumpSumSection({ dca, lookback, returns }) {
|
||||
baseline({
|
||||
metric: returns.cagr[key],
|
||||
name: "Lump Sum",
|
||||
color: colors.bi.lumpSum,
|
||||
color: colors.bi.p2,
|
||||
unit: Unit.cagr,
|
||||
}),
|
||||
],
|
||||
|
||||
@@ -415,7 +415,7 @@ export function createMiningSection() {
|
||||
},
|
||||
{
|
||||
name: "Distribution",
|
||||
title: "Coinbase Rewards Distribution",
|
||||
title: "Coinbase Rewards per Block Distribution",
|
||||
bottom: distributionBtcSatsUsd(blocks.rewards.coinbase),
|
||||
},
|
||||
{
|
||||
@@ -476,7 +476,7 @@ export function createMiningSection() {
|
||||
tree: [
|
||||
{
|
||||
name: "Sum",
|
||||
title: "Transaction Fee Revenue",
|
||||
title: "Transaction Fee Revenue per Block",
|
||||
bottom: satsBtcUsdFrom({
|
||||
source: transactions.fees.fee,
|
||||
key: "sum",
|
||||
@@ -485,7 +485,7 @@ export function createMiningSection() {
|
||||
},
|
||||
{
|
||||
name: "Distribution",
|
||||
title: "Transaction Fee Revenue Distribution",
|
||||
title: "Transaction Fee Revenue per Block Distribution",
|
||||
bottom: distributionBtcSatsUsd(transactions.fees.fee),
|
||||
},
|
||||
{
|
||||
|
||||
@@ -10,10 +10,10 @@ import {
|
||||
baseline,
|
||||
fromSupplyPattern,
|
||||
fromBaseStatsPattern,
|
||||
chartsFromFull,
|
||||
chartsFromFullPerBlock,
|
||||
chartsFromValueFull,
|
||||
fromStatsPattern,
|
||||
chartsFromSum,
|
||||
chartsFromSumPerBlock,
|
||||
} from "./series.js";
|
||||
import { satsBtcUsd, satsBtcUsdFrom } from "./shared.js";
|
||||
|
||||
@@ -93,20 +93,20 @@ export function createNetworkSection() {
|
||||
{
|
||||
key: "sending",
|
||||
name: "Sending",
|
||||
title: "Sending Address Count",
|
||||
compareTitle: "Sending Address Count by Type",
|
||||
title: "Unique Sending Addresses per Block",
|
||||
compareTitle: "Unique Sending Addresses per Block by Type",
|
||||
},
|
||||
{
|
||||
key: "receiving",
|
||||
name: "Receiving",
|
||||
title: "Receiving Address Count",
|
||||
compareTitle: "Receiving Address Count by Type",
|
||||
title: "Unique Receiving Addresses per Block",
|
||||
compareTitle: "Unique Receiving Addresses per Block by Type",
|
||||
},
|
||||
{
|
||||
key: "both",
|
||||
name: "Sending & Receiving",
|
||||
title: "Addresses Sending & Receiving",
|
||||
compareTitle: "Addresses Sending & Receiving by Type",
|
||||
title: "Unique Addresses Sending & Receiving per Block",
|
||||
compareTitle: "Unique Addresses Sending & Receiving per Block by Type",
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -114,15 +114,15 @@ export function createNetworkSection() {
|
||||
const balanceTypes = /** @type {const} */ ([
|
||||
{
|
||||
key: "balanceIncreased",
|
||||
name: "Increased",
|
||||
title: "Addresses with Increased Balance",
|
||||
compareTitle: "Addresses with Increased Balance by Type",
|
||||
name: "Accumulating",
|
||||
title: "Accumulating Addresses per Block",
|
||||
compareTitle: "Accumulating Addresses per Block by Type",
|
||||
},
|
||||
{
|
||||
key: "balanceDecreased",
|
||||
name: "Decreased",
|
||||
title: "Addresses with Decreased Balance",
|
||||
compareTitle: "Addresses with Decreased Balance by Type",
|
||||
name: "Distributing",
|
||||
title: "Distributing Addresses per Block",
|
||||
compareTitle: "Distributing Addresses per Block by Type",
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -165,16 +165,16 @@ export function createNetworkSection() {
|
||||
unit: Unit.count,
|
||||
}),
|
||||
line({
|
||||
metric: distribution.totalAddrCount[key],
|
||||
name: "Total",
|
||||
color: colors.default,
|
||||
metric: distribution.emptyAddrCount[key].count,
|
||||
name: "Empty",
|
||||
color: colors.gray,
|
||||
unit: Unit.count,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: distribution.emptyAddrCount[key].count,
|
||||
name: "Empty",
|
||||
color: colors.gray,
|
||||
metric: distribution.totalAddrCount[key],
|
||||
name: "Total",
|
||||
color: colors.default,
|
||||
unit: Unit.count,
|
||||
defaultActive: false,
|
||||
}),
|
||||
@@ -189,14 +189,21 @@ export function createNetworkSection() {
|
||||
bottom: [
|
||||
baseline({
|
||||
metric: distribution.addrCount[key]._30dChange,
|
||||
name: "30d Change",
|
||||
name: "Funded",
|
||||
unit: Unit.count,
|
||||
}),
|
||||
baseline({
|
||||
metric: distribution.emptyAddrCount[key]._30dChange,
|
||||
name: "Empty",
|
||||
color: colors.gray,
|
||||
unit: Unit.count,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "New",
|
||||
tree: chartsFromFull({
|
||||
tree: chartsFromFullPerBlock({
|
||||
pattern: distribution.newAddrCount[key],
|
||||
title: `${titlePrefix}New Address Count`,
|
||||
unit: Unit.count,
|
||||
@@ -204,7 +211,7 @@ export function createNetworkSection() {
|
||||
},
|
||||
{
|
||||
name: "Reactivated",
|
||||
title: `${titlePrefix}Reactivated Address Count`,
|
||||
title: `${titlePrefix}Reactivated Addresses per Block`,
|
||||
bottom: fromBaseStatsPattern({
|
||||
pattern: distribution.addressActivity[key].reactivated,
|
||||
unit: Unit.count,
|
||||
@@ -212,7 +219,7 @@ export function createNetworkSection() {
|
||||
},
|
||||
{
|
||||
name: "Growth Rate",
|
||||
title: `${titlePrefix}Address Growth Rate`,
|
||||
title: `${titlePrefix}Address Growth Rate per Block`,
|
||||
bottom: fromBaseStatsPattern({
|
||||
pattern: distribution.growthRate[key],
|
||||
unit: Unit.ratio,
|
||||
@@ -288,7 +295,7 @@ export function createNetworkSection() {
|
||||
},
|
||||
{
|
||||
name: "Reactivated",
|
||||
title: `${groupName} Reactivated Address Count`,
|
||||
title: `${groupName} Reactivated Addresses per Block`,
|
||||
bottom: types.flatMap((t) => [
|
||||
line({
|
||||
metric: distribution.addressActivity[t.key].reactivated.base,
|
||||
@@ -306,7 +313,7 @@ export function createNetworkSection() {
|
||||
},
|
||||
{
|
||||
name: "Growth Rate",
|
||||
title: `${groupName} Address Growth Rate`,
|
||||
title: `${groupName} Address Growth Rate per Block`,
|
||||
bottom: types.flatMap((t) => [
|
||||
dots({
|
||||
metric: distribution.growthRate[t.key].base,
|
||||
@@ -489,7 +496,7 @@ export function createNetworkSection() {
|
||||
tree: [
|
||||
{
|
||||
name: "Count",
|
||||
tree: chartsFromFull({
|
||||
tree: chartsFromFullPerBlock({
|
||||
pattern: transactions.count.txCount,
|
||||
title: "Transaction Count",
|
||||
unit: Unit.count,
|
||||
@@ -497,7 +504,7 @@ export function createNetworkSection() {
|
||||
},
|
||||
{
|
||||
name: "Fee Rate",
|
||||
title: "Fee Rate",
|
||||
title: "Transaction Fee Rate",
|
||||
bottom: fromStatsPattern({
|
||||
pattern: transactions.fees.feeRate,
|
||||
unit: Unit.feeRate,
|
||||
@@ -897,7 +904,7 @@ export function createNetworkSection() {
|
||||
},
|
||||
{
|
||||
name: "Inputs",
|
||||
tree: chartsFromSum({
|
||||
tree: chartsFromSumPerBlock({
|
||||
pattern: inputs.count,
|
||||
title: "Input Count",
|
||||
unit: Unit.count,
|
||||
@@ -905,7 +912,7 @@ export function createNetworkSection() {
|
||||
},
|
||||
{
|
||||
name: "Outputs",
|
||||
tree: chartsFromSum({
|
||||
tree: chartsFromSumPerBlock({
|
||||
pattern: outputs.count.totalCount,
|
||||
title: "Output Count",
|
||||
unit: Unit.count,
|
||||
@@ -985,7 +992,7 @@ export function createNetworkSection() {
|
||||
},
|
||||
{
|
||||
name: "Reactivated",
|
||||
title: "Reactivated Address Count by Type",
|
||||
title: "Reactivated Addresses per Block by Type",
|
||||
bottom: addressTypes.flatMap((t) => [
|
||||
line({
|
||||
metric:
|
||||
@@ -1007,7 +1014,7 @@ export function createNetworkSection() {
|
||||
},
|
||||
{
|
||||
name: "Growth Rate",
|
||||
title: "Address Growth Rate by Type",
|
||||
title: "Address Growth Rate per Block by Type",
|
||||
bottom: addressTypes.flatMap((t) => [
|
||||
dots({
|
||||
metric: distribution.growthRate[t.key].base,
|
||||
@@ -1159,7 +1166,7 @@ export function createNetworkSection() {
|
||||
createScriptCompare("Legacy", legacyScripts),
|
||||
...legacyScripts.map((t) => ({
|
||||
name: t.name,
|
||||
tree: chartsFromFull({
|
||||
tree: chartsFromFullPerBlock({
|
||||
pattern: scripts.count[t.key],
|
||||
title: `${t.name} Output Count`,
|
||||
unit: Unit.count,
|
||||
@@ -1173,7 +1180,7 @@ export function createNetworkSection() {
|
||||
createScriptCompare("Script Hash", scriptHashScripts),
|
||||
...scriptHashScripts.map((t) => ({
|
||||
name: t.name,
|
||||
tree: chartsFromFull({
|
||||
tree: chartsFromFullPerBlock({
|
||||
pattern: scripts.count[t.key],
|
||||
title: `${t.name} Output Count`,
|
||||
unit: Unit.count,
|
||||
@@ -1187,7 +1194,7 @@ export function createNetworkSection() {
|
||||
createScriptCompare("SegWit", segwitScripts),
|
||||
...segwitScripts.map((t) => ({
|
||||
name: t.name,
|
||||
tree: chartsFromFull({
|
||||
tree: chartsFromFullPerBlock({
|
||||
pattern: scripts.count[t.key],
|
||||
title: `${t.name} Output Count`,
|
||||
unit: Unit.count,
|
||||
@@ -1201,7 +1208,7 @@ export function createNetworkSection() {
|
||||
createScriptCompare("Taproot", taprootAddresses),
|
||||
...taprootAddresses.map((t) => ({
|
||||
name: t.name,
|
||||
tree: chartsFromFull({
|
||||
tree: chartsFromFullPerBlock({
|
||||
pattern: scripts.count[t.key],
|
||||
title: `${t.name} Output Count`,
|
||||
unit: Unit.count,
|
||||
@@ -1215,7 +1222,7 @@ export function createNetworkSection() {
|
||||
createScriptCompare("Other", otherScripts),
|
||||
...otherScripts.map((t) => ({
|
||||
name: t.name,
|
||||
tree: chartsFromFull({
|
||||
tree: chartsFromFullPerBlock({
|
||||
pattern: scripts.count[t.key],
|
||||
title: `${t.name} Output Count`,
|
||||
unit: Unit.count,
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
createCohortFolderFull,
|
||||
createCohortFolderWithAdjusted,
|
||||
createCohortFolderWithNupl,
|
||||
createCohortFolderLongTerm,
|
||||
createCohortFolderAgeRange,
|
||||
createCohortFolderMinAge,
|
||||
createCohortFolderBasicWithMarketCap,
|
||||
@@ -90,8 +91,8 @@ export function createPartialOptions() {
|
||||
// STH - Short term holder cohort (Full capability)
|
||||
createCohortFolderFull(termShort),
|
||||
|
||||
// LTH - Long term holder cohort (nupl)
|
||||
createCohortFolderWithNupl(termLong),
|
||||
// LTH - Long term holder cohort (own market cap + nupl + peak regret + P/L ratio)
|
||||
createCohortFolderLongTerm(termLong),
|
||||
|
||||
// Ages cohorts
|
||||
{
|
||||
|
||||
@@ -211,7 +211,6 @@ export function candlestick({
|
||||
metric,
|
||||
name,
|
||||
key,
|
||||
|
||||
defaultActive,
|
||||
unit,
|
||||
options,
|
||||
@@ -221,7 +220,6 @@ export function candlestick({
|
||||
metric,
|
||||
title: name,
|
||||
key,
|
||||
|
||||
unit,
|
||||
defaultActive,
|
||||
options,
|
||||
@@ -238,6 +236,7 @@ export function candlestick({
|
||||
* @param {Color | [Color, Color]} [args.color]
|
||||
* @param {boolean} [args.defaultActive]
|
||||
* @param {number | undefined} [args.base]
|
||||
* @param {number} [args.style] - Line style (0: Solid, 1: Dotted, 2: Dashed, 3: LargeDashed, 4: SparseDotted)
|
||||
* @param {BaselineSeriesPartialOptions} [args.options]
|
||||
* @returns {FetchedBaselineSeriesBlueprint}
|
||||
*/
|
||||
@@ -249,6 +248,7 @@ export function baseline({
|
||||
defaultActive,
|
||||
unit,
|
||||
base,
|
||||
style,
|
||||
options,
|
||||
}) {
|
||||
const isTuple = Array.isArray(color);
|
||||
@@ -261,6 +261,58 @@ export function baseline({
|
||||
colors: isTuple ? color : undefined,
|
||||
unit,
|
||||
defaultActive,
|
||||
options: {
|
||||
baseValue: {
|
||||
price: base,
|
||||
},
|
||||
lineStyle: style,
|
||||
...options,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Omit<Parameters<typeof baseline>[0], 'style'>} args
|
||||
*/
|
||||
export function dottedBaseline(args) {
|
||||
const _args = /** @type {Parameters<typeof baseline>[0]} */ (args);
|
||||
_args.style = 1;
|
||||
return baseline(_args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Baseline series rendered as dots (points only, no line)
|
||||
* @param {Object} args
|
||||
* @param {AnyMetricPattern} args.metric
|
||||
* @param {string} args.name
|
||||
* @param {Unit} args.unit
|
||||
* @param {string} [args.key]
|
||||
* @param {Color | [Color, Color]} [args.color]
|
||||
* @param {boolean} [args.defaultActive]
|
||||
* @param {number | undefined} [args.base]
|
||||
* @param {BaselineSeriesPartialOptions} [args.options]
|
||||
* @returns {FetchedDotsBaselineSeriesBlueprint}
|
||||
*/
|
||||
export function dotsBaseline({
|
||||
metric,
|
||||
name,
|
||||
key,
|
||||
color,
|
||||
defaultActive,
|
||||
unit,
|
||||
base,
|
||||
options,
|
||||
}) {
|
||||
const isTuple = Array.isArray(color);
|
||||
return {
|
||||
type: /** @type {const} */ ("DotsBaseline"),
|
||||
metric,
|
||||
title: name,
|
||||
key,
|
||||
color: isTuple ? undefined : color,
|
||||
colors: isTuple ? color : undefined,
|
||||
unit,
|
||||
defaultActive,
|
||||
options: {
|
||||
baseValue: {
|
||||
price: base,
|
||||
@@ -509,9 +561,18 @@ function btcSatsUsdSeries({ metrics, name, color, defaultActive }) {
|
||||
* @param {FullStatsPattern<any>} args.pattern
|
||||
* @param {string} args.title
|
||||
* @param {Unit} args.unit
|
||||
* @param {string} [args.distributionSuffix]
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
export function chartsFromFull({ pattern, title, unit }) {
|
||||
export function chartsFromFull({
|
||||
pattern,
|
||||
title,
|
||||
unit,
|
||||
distributionSuffix = "",
|
||||
}) {
|
||||
const distTitle = distributionSuffix
|
||||
? `${title} ${distributionSuffix} Distribution`
|
||||
: `${title} Distribution`;
|
||||
return [
|
||||
{
|
||||
name: "Sum",
|
||||
@@ -523,7 +584,7 @@ export function chartsFromFull({ pattern, title, unit }) {
|
||||
},
|
||||
{
|
||||
name: "Distribution",
|
||||
title: `${title} Distribution`,
|
||||
title: distTitle,
|
||||
bottom: distributionSeries(pattern, unit),
|
||||
},
|
||||
{
|
||||
@@ -534,16 +595,36 @@ export function chartsFromFull({ pattern, title, unit }) {
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Split pattern into 3 charts with "per Block" in distribution title
|
||||
* @param {Object} args
|
||||
* @param {FullStatsPattern<any>} args.pattern
|
||||
* @param {string} args.title
|
||||
* @param {Unit} args.unit
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
export const chartsFromFullPerBlock = (args) =>
|
||||
chartsFromFull({ ...args, distributionSuffix: "per Block" });
|
||||
|
||||
/**
|
||||
* Split pattern with sum + distribution + cumulative into 3 charts (no base)
|
||||
* @param {Object} args
|
||||
* @param {AnyStatsPattern} args.pattern
|
||||
* @param {string} args.title
|
||||
* @param {Unit} args.unit
|
||||
* @param {string} [args.distributionSuffix]
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
export function chartsFromSum({ pattern, title, unit }) {
|
||||
export function chartsFromSum({
|
||||
pattern,
|
||||
title,
|
||||
unit,
|
||||
distributionSuffix = "",
|
||||
}) {
|
||||
const { stat } = colors;
|
||||
const distTitle = distributionSuffix
|
||||
? `${title} ${distributionSuffix} Distribution`
|
||||
: `${title} Distribution`;
|
||||
return [
|
||||
{
|
||||
name: "Sum",
|
||||
@@ -552,7 +633,7 @@ export function chartsFromSum({ pattern, title, unit }) {
|
||||
},
|
||||
{
|
||||
name: "Distribution",
|
||||
title: `${title} Distribution`,
|
||||
title: distTitle,
|
||||
bottom: distributionSeries(pattern, unit),
|
||||
},
|
||||
{
|
||||
@@ -563,6 +644,17 @@ export function chartsFromSum({ pattern, title, unit }) {
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Split pattern into 3 charts with "per Block" in distribution title (no base)
|
||||
* @param {Object} args
|
||||
* @param {AnyStatsPattern} args.pattern
|
||||
* @param {string} args.title
|
||||
* @param {Unit} args.unit
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
export const chartsFromSumPerBlock = (args) =>
|
||||
chartsFromSum({ ...args, distributionSuffix: "per Block" });
|
||||
|
||||
/**
|
||||
* Split pattern with sum + cumulative into 2 charts
|
||||
* @param {Object} args
|
||||
|
||||
@@ -20,9 +20,10 @@ export const formatCohortTitle = (cohortTitle) => (metric) =>
|
||||
* @param {string} args.name
|
||||
* @param {Color} [args.color]
|
||||
* @param {boolean} [args.defaultActive]
|
||||
* @param {number} [args.style]
|
||||
* @returns {FetchedLineSeriesBlueprint[]}
|
||||
*/
|
||||
export function satsBtcUsd({ pattern, name, color, defaultActive }) {
|
||||
export function satsBtcUsd({ pattern, name, color, defaultActive, style }) {
|
||||
return [
|
||||
line({
|
||||
metric: pattern.bitcoin,
|
||||
@@ -30,14 +31,23 @@ export function satsBtcUsd({ pattern, name, color, defaultActive }) {
|
||||
color,
|
||||
unit: Unit.btc,
|
||||
defaultActive,
|
||||
style,
|
||||
}),
|
||||
line({
|
||||
metric: pattern.sats,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.sats,
|
||||
defaultActive,
|
||||
style,
|
||||
}),
|
||||
line({ metric: pattern.sats, name, color, unit: Unit.sats, defaultActive }),
|
||||
line({
|
||||
metric: pattern.dollars,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
defaultActive,
|
||||
style,
|
||||
}),
|
||||
];
|
||||
}
|
||||
@@ -60,7 +70,13 @@ export function satsBtcUsdBaseline({ pattern, name, color, defaultActive }) {
|
||||
unit: Unit.btc,
|
||||
defaultActive,
|
||||
}),
|
||||
baseline({ metric: pattern.sats, name, color, unit: Unit.sats, defaultActive }),
|
||||
baseline({
|
||||
metric: pattern.sats,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.sats,
|
||||
defaultActive,
|
||||
}),
|
||||
baseline({
|
||||
metric: pattern.dollars,
|
||||
name,
|
||||
@@ -146,7 +162,12 @@ export function revenueBtcSatsUsd({ coinbase, subsidy, fee, key }) {
|
||||
name: "Subsidy",
|
||||
color: colors.mining.subsidy,
|
||||
}),
|
||||
...satsBtcUsdFrom({ source: fee, key, name: "Fees", color: colors.mining.fee }),
|
||||
...satsBtcUsdFrom({
|
||||
source: fee,
|
||||
key,
|
||||
name: "Fees",
|
||||
color: colors.mining.fee,
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -477,8 +498,7 @@ export function createPriceRatioCharts({
|
||||
],
|
||||
},
|
||||
createRatioChart({
|
||||
title: (name) =>
|
||||
titleFn(titlePrefix ? `${titlePrefix} ${name}` : name),
|
||||
title: (name) => titleFn(titlePrefix ? `${titlePrefix} ${name}` : name),
|
||||
pricePattern,
|
||||
ratio,
|
||||
color,
|
||||
|
||||
@@ -35,7 +35,14 @@
|
||||
* @property {LineSeriesPartialOptions} [options]
|
||||
* @typedef {BaseSeriesBlueprint & DotsSeriesBlueprintSpecific} DotsSeriesBlueprint
|
||||
*
|
||||
* @typedef {BaselineSeriesBlueprint | CandlestickSeriesBlueprint | LineSeriesBlueprint | HistogramSeriesBlueprint | DotsSeriesBlueprint} AnySeriesBlueprint
|
||||
* @typedef {Object} DotsBaselineSeriesBlueprintSpecific
|
||||
* @property {"DotsBaseline"} type
|
||||
* @property {Color} [color]
|
||||
* @property {[Color, Color]} [colors]
|
||||
* @property {BaselineSeriesPartialOptions} [options]
|
||||
* @typedef {BaseSeriesBlueprint & DotsBaselineSeriesBlueprintSpecific} DotsBaselineSeriesBlueprint
|
||||
*
|
||||
* @typedef {BaselineSeriesBlueprint | CandlestickSeriesBlueprint | LineSeriesBlueprint | HistogramSeriesBlueprint | DotsSeriesBlueprint | DotsBaselineSeriesBlueprint} AnySeriesBlueprint
|
||||
*
|
||||
* @typedef {AnySeriesBlueprint["type"]} SeriesType
|
||||
*
|
||||
@@ -46,6 +53,7 @@
|
||||
* @typedef {LineSeriesBlueprint & FetchedAnySeriesOptions} FetchedLineSeriesBlueprint
|
||||
* @typedef {HistogramSeriesBlueprint & FetchedAnySeriesOptions} FetchedHistogramSeriesBlueprint
|
||||
* @typedef {DotsSeriesBlueprint & FetchedAnySeriesOptions} FetchedDotsSeriesBlueprint
|
||||
* @typedef {DotsBaselineSeriesBlueprint & FetchedAnySeriesOptions} FetchedDotsBaselineSeriesBlueprint
|
||||
* @typedef {AnySeriesBlueprint & FetchedAnySeriesOptions} AnyFetchedSeriesBlueprint
|
||||
*
|
||||
* Any pattern with dollars and sats sub-metrics (auto-expands to USD + sats)
|
||||
@@ -317,6 +325,7 @@
|
||||
* @property {string} title
|
||||
* @property {Color} color
|
||||
* @property {AddressCohortPattern} tree
|
||||
* @property {Brk._30dCountPattern} addrCount
|
||||
*
|
||||
* @typedef {UtxoCohortObject | AddressCohortObject | CohortWithoutRelative} CohortObject
|
||||
*
|
||||
|
||||
@@ -28,22 +28,27 @@ function walk(node, map, path) {
|
||||
for (const [key, value] of Object.entries(node)) {
|
||||
const kn = key.toLowerCase();
|
||||
if (
|
||||
// kn === "mvrv" ||
|
||||
key.endsWith("Raw") ||
|
||||
key.endsWith("Cents") ||
|
||||
key.endsWith("State") ||
|
||||
key.endsWith("Start") ||
|
||||
kn === "mvrv" ||
|
||||
// kn === "time" ||
|
||||
// kn === "height" ||
|
||||
// kn === "constants" ||
|
||||
kn === "constants" ||
|
||||
kn === "blockhash" ||
|
||||
kn === "date" ||
|
||||
// kn === "oracle" ||
|
||||
// kn === "split" ||
|
||||
kn === "split" ||
|
||||
// kn === "ohlc" ||
|
||||
// kn === "outpoint" ||
|
||||
kn === "outpoint" ||
|
||||
kn === "positions" ||
|
||||
// kn === "outputtype" ||
|
||||
kn === "heighttopool" ||
|
||||
// kn === "txid" ||
|
||||
kn === "txid" ||
|
||||
kn.startsWith("timestamp") ||
|
||||
// kn.startsWith("satdays") ||
|
||||
kn.startsWith("satdays") ||
|
||||
kn.startsWith("satblocks") ||
|
||||
// kn.endsWith("state") ||
|
||||
// kn.endsWith("cents") ||
|
||||
kn.endsWith("index") ||
|
||||
|
||||
Reference in New Issue
Block a user