mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-26 15:49:58 -07:00
website: snapshot
This commit is contained in:
@@ -13,7 +13,7 @@
|
||||
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { line, baseline, dotsBaseline, dots } from "../series.js";
|
||||
import { satsBtcUsd } from "../shared.js";
|
||||
import { satsBtcUsd, mapCohortsWithAll, flatMapCohortsWithAll } from "../shared.js";
|
||||
import { colors } from "../../utils/colors.js";
|
||||
|
||||
// ============================================================================
|
||||
@@ -56,6 +56,7 @@ function soprSeries(realized, rawName = "SOPR") {
|
||||
* Create grouped SOPR chart entries (Raw, 7d EMA, 30d EMA)
|
||||
* @template {{ color: Color, name: string }} T
|
||||
* @param {readonly T[]} list
|
||||
* @param {T} all
|
||||
* @param {(item: T) => AnyMetricPattern} getSopr
|
||||
* @param {(item: T) => AnyMetricPattern} getSopr7d
|
||||
* @param {(item: T) => AnyMetricPattern} getSopr30d
|
||||
@@ -65,6 +66,7 @@ function soprSeries(realized, rawName = "SOPR") {
|
||||
*/
|
||||
function groupedSoprCharts(
|
||||
list,
|
||||
all,
|
||||
getSopr,
|
||||
getSopr7d,
|
||||
getSopr30d,
|
||||
@@ -75,7 +77,7 @@ function groupedSoprCharts(
|
||||
{
|
||||
name: "Raw",
|
||||
title: title(`${titlePrefix}SOPR`),
|
||||
bottom: list.map((item) =>
|
||||
bottom: mapCohortsWithAll(list, all, (item) =>
|
||||
baseline({
|
||||
metric: getSopr(item),
|
||||
name: item.name,
|
||||
@@ -88,7 +90,7 @@ function groupedSoprCharts(
|
||||
{
|
||||
name: "7d EMA",
|
||||
title: title(`${titlePrefix}SOPR 7d EMA`),
|
||||
bottom: list.map((item) =>
|
||||
bottom: mapCohortsWithAll(list, all, (item) =>
|
||||
baseline({
|
||||
metric: getSopr7d(item),
|
||||
name: item.name,
|
||||
@@ -101,7 +103,7 @@ function groupedSoprCharts(
|
||||
{
|
||||
name: "30d EMA",
|
||||
title: title(`${titlePrefix}SOPR 30d EMA`),
|
||||
bottom: list.map((item) =>
|
||||
bottom: mapCohortsWithAll(list, all, (item) =>
|
||||
baseline({
|
||||
metric: getSopr30d(item),
|
||||
name: item.name,
|
||||
@@ -118,10 +120,11 @@ function groupedSoprCharts(
|
||||
* Create value breakdown tree (Profit/Loss Created/Destroyed)
|
||||
* @template {{ color: Color, name: string, tree: { realized: AnyRealizedPattern } }} T
|
||||
* @param {readonly T[]} list
|
||||
* @param {T} all
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function valueBreakdownTree(list, title) {
|
||||
function valueBreakdownTree(list, all, title) {
|
||||
return [
|
||||
{
|
||||
name: "Profit",
|
||||
@@ -129,7 +132,7 @@ function valueBreakdownTree(list, title) {
|
||||
{
|
||||
name: "Created",
|
||||
title: title("Profit Value Created"),
|
||||
bottom: list.map(({ color, name, tree }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.realized.profitValueCreated,
|
||||
name,
|
||||
@@ -141,7 +144,7 @@ function valueBreakdownTree(list, title) {
|
||||
{
|
||||
name: "Destroyed",
|
||||
title: title("Profit Value Destroyed"),
|
||||
bottom: list.map(({ color, name, tree }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.realized.profitValueDestroyed,
|
||||
name,
|
||||
@@ -158,7 +161,7 @@ function valueBreakdownTree(list, title) {
|
||||
{
|
||||
name: "Created",
|
||||
title: title("Loss Value Created"),
|
||||
bottom: list.map(({ color, name, tree }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.realized.lossValueCreated,
|
||||
name,
|
||||
@@ -170,7 +173,7 @@ function valueBreakdownTree(list, title) {
|
||||
{
|
||||
name: "Destroyed",
|
||||
title: title("Loss Value Destroyed"),
|
||||
bottom: list.map(({ color, name, tree }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.realized.lossValueDestroyed,
|
||||
name,
|
||||
@@ -188,15 +191,16 @@ function valueBreakdownTree(list, title) {
|
||||
* 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 {T} all
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function coinsDestroyedTree(list, title) {
|
||||
function coinsDestroyedTree(list, all, title) {
|
||||
return [
|
||||
{
|
||||
name: "Sum",
|
||||
title: title("Coins Destroyed"),
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
bottom: flatMapCohortsWithAll(list, all, ({ color, name, tree }) => [
|
||||
line({
|
||||
metric: tree.activity.coinblocksDestroyed.sum,
|
||||
name,
|
||||
@@ -214,7 +218,7 @@ function coinsDestroyedTree(list, title) {
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: title("Cumulative Coins Destroyed"),
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
bottom: flatMapCohortsWithAll(list, all, ({ color, name, tree }) => [
|
||||
line({
|
||||
metric: tree.activity.coinblocksDestroyed.cumulative,
|
||||
name,
|
||||
@@ -276,14 +280,15 @@ function createSingleSoprTreeWithAdjusted(cohort, title) {
|
||||
|
||||
/**
|
||||
* Create grouped SOPR tree with separate charts for each variant
|
||||
* @template {readonly (UtxoCohortObject | CohortWithoutRelative)[]} T
|
||||
* @param {T} list
|
||||
* @param {readonly (UtxoCohortObject | CohortWithoutRelative)[]} list
|
||||
* @param {CohortAll} all
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function createGroupedSoprTree(list, title) {
|
||||
function createGroupedSoprTree(list, all, title) {
|
||||
return groupedSoprCharts(
|
||||
list,
|
||||
all,
|
||||
(c) => c.tree.realized.sopr,
|
||||
(c) => c.tree.realized.sopr7dEma,
|
||||
(c) => c.tree.realized.sopr30dEma,
|
||||
@@ -295,15 +300,17 @@ function createGroupedSoprTree(list, title) {
|
||||
/**
|
||||
* Create grouped SOPR tree with Normal and Adjusted sub-sections
|
||||
* @param {readonly (CohortAll | CohortFull | CohortWithAdjusted)[]} list
|
||||
* @param {CohortAll | CohortFull | CohortWithAdjusted} all
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function createGroupedSoprTreeWithAdjusted(list, title) {
|
||||
function createGroupedSoprTreeWithAdjusted(list, all, title) {
|
||||
return [
|
||||
{
|
||||
name: "Normal",
|
||||
tree: groupedSoprCharts(
|
||||
list,
|
||||
all,
|
||||
(c) => c.tree.realized.sopr,
|
||||
(c) => c.tree.realized.sopr7dEma,
|
||||
(c) => c.tree.realized.sopr30dEma,
|
||||
@@ -315,6 +322,7 @@ function createGroupedSoprTreeWithAdjusted(list, title) {
|
||||
name: "Adjusted",
|
||||
tree: groupedSoprCharts(
|
||||
list,
|
||||
all,
|
||||
(c) => c.tree.realized.adjustedSopr,
|
||||
(c) => c.tree.realized.adjustedSopr7dEma,
|
||||
(c) => c.tree.realized.adjustedSopr30dEma,
|
||||
@@ -577,15 +585,16 @@ export function createActivitySectionWithAdjusted({ cohort, title }) {
|
||||
* Create grouped flows tree (Profit Flow, Capitulation Flow)
|
||||
* @template {{ color: Color, name: string, tree: { realized: AnyRealizedPattern } }} T
|
||||
* @param {readonly T[]} list
|
||||
* @param {T} all
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function groupedFlowsTree(list, title) {
|
||||
function groupedFlowsTree(list, all, title) {
|
||||
return [
|
||||
{
|
||||
name: "Profit",
|
||||
title: title("Profit Flow"),
|
||||
bottom: list.map(({ color, name, tree }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.realized.profitFlow,
|
||||
name,
|
||||
@@ -597,7 +606,7 @@ function groupedFlowsTree(list, title) {
|
||||
{
|
||||
name: "Capitulation",
|
||||
title: title("Capitulation Flow"),
|
||||
bottom: list.map(({ color, name, tree }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.realized.capitulationFlow,
|
||||
name,
|
||||
@@ -613,16 +622,17 @@ function groupedFlowsTree(list, title) {
|
||||
* Create grouped value tree (Flows, Created, Destroyed, Breakdown)
|
||||
* @template {{ color: Color, name: string, tree: { realized: AnyRealizedPattern } }} T
|
||||
* @param {readonly T[]} list
|
||||
* @param {T} all
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function createGroupedValueTree(list, title) {
|
||||
function createGroupedValueTree(list, all, title) {
|
||||
return [
|
||||
{ name: "Flows", tree: groupedFlowsTree(list, title) },
|
||||
{ name: "Flows", tree: groupedFlowsTree(list, all, title) },
|
||||
{
|
||||
name: "Created",
|
||||
title: title("Value Created"),
|
||||
bottom: list.map(({ color, name, tree }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.realized.valueCreated,
|
||||
name,
|
||||
@@ -634,7 +644,7 @@ function createGroupedValueTree(list, title) {
|
||||
{
|
||||
name: "Destroyed",
|
||||
title: title("Value Destroyed"),
|
||||
bottom: list.map(({ color, name, tree }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.realized.valueDestroyed,
|
||||
name,
|
||||
@@ -643,22 +653,18 @@ function createGroupedValueTree(list, title) {
|
||||
}),
|
||||
),
|
||||
},
|
||||
{ name: "Breakdown", tree: valueBreakdownTree(list, title) },
|
||||
{ name: "Breakdown", tree: valueBreakdownTree(list, all, 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)
|
||||
* Grouped activity section builder
|
||||
* @param {{ list: readonly (UtxoCohortObject | CohortWithoutRelative)[], all: CohortAll, title: (metric: string) => string, soprTree?: PartialOptionsTree, valueTree?: PartialOptionsTree }} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createGroupedActivitySection({
|
||||
list,
|
||||
all,
|
||||
title,
|
||||
soprTree,
|
||||
valueTree,
|
||||
@@ -672,14 +678,14 @@ export function createGroupedActivitySection({
|
||||
{
|
||||
name: "14d EMA",
|
||||
title: title("Sent Volume 14d EMA"),
|
||||
bottom: list.flatMap(({ color, name, tree }) =>
|
||||
bottom: flatMapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
satsBtcUsd({ pattern: tree.activity.sent14dEma, name, color }),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Sum",
|
||||
title: title("Sent Volume"),
|
||||
bottom: list.flatMap(({ color, name, tree }) =>
|
||||
bottom: flatMapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
satsBtcUsd({
|
||||
pattern: {
|
||||
sats: tree.activity.sent.sats.sum,
|
||||
@@ -695,18 +701,18 @@ export function createGroupedActivitySection({
|
||||
},
|
||||
{
|
||||
name: "SOPR",
|
||||
tree: soprTree ?? createGroupedSoprTree(list, title),
|
||||
tree: soprTree ?? createGroupedSoprTree(list, all, title),
|
||||
},
|
||||
{
|
||||
name: "Sell Side Risk",
|
||||
title: title("Sell Side Risk Ratio"),
|
||||
bottom: createGroupedSellSideRiskSeries(list),
|
||||
bottom: createGroupedSellSideRiskSeries(list, all),
|
||||
},
|
||||
{
|
||||
name: "Value",
|
||||
tree: valueTree ?? createGroupedValueTree(list, title),
|
||||
tree: valueTree ?? createGroupedValueTree(list, all, title),
|
||||
},
|
||||
{ name: "Coins Destroyed", tree: coinsDestroyedTree(list, title) },
|
||||
{ name: "Coins Destroyed", tree: coinsDestroyedTree(list, all, title) },
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -714,19 +720,20 @@ export function createGroupedActivitySection({
|
||||
/**
|
||||
* Create grouped value tree with adjusted values (Flows, Normal, Adjusted, Breakdown)
|
||||
* @param {readonly (CohortAll | CohortFull | CohortWithAdjusted)[]} list
|
||||
* @param {CohortAll | CohortFull | CohortWithAdjusted} all
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function createGroupedValueTreeWithAdjusted(list, title) {
|
||||
function createGroupedValueTreeWithAdjusted(list, all, title) {
|
||||
return [
|
||||
{ name: "Flows", tree: groupedFlowsTree(list, title) },
|
||||
{ name: "Flows", tree: groupedFlowsTree(list, all, title) },
|
||||
{
|
||||
name: "Normal",
|
||||
tree: [
|
||||
{
|
||||
name: "Created",
|
||||
title: title("Value Created"),
|
||||
bottom: list.map(({ color, name, tree }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.realized.valueCreated,
|
||||
name,
|
||||
@@ -738,7 +745,7 @@ function createGroupedValueTreeWithAdjusted(list, title) {
|
||||
{
|
||||
name: "Destroyed",
|
||||
title: title("Value Destroyed"),
|
||||
bottom: list.map(({ color, name, tree }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.realized.valueDestroyed,
|
||||
name,
|
||||
@@ -755,7 +762,7 @@ function createGroupedValueTreeWithAdjusted(list, title) {
|
||||
{
|
||||
name: "Created",
|
||||
title: title("Adjusted Value Created"),
|
||||
bottom: list.map(({ color, name, tree }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.realized.adjustedValueCreated,
|
||||
name,
|
||||
@@ -767,7 +774,7 @@ function createGroupedValueTreeWithAdjusted(list, title) {
|
||||
{
|
||||
name: "Destroyed",
|
||||
title: title("Adjusted Value Destroyed"),
|
||||
bottom: list.map(({ color, name, tree }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.realized.adjustedValueDestroyed,
|
||||
name,
|
||||
@@ -778,21 +785,22 @@ function createGroupedValueTreeWithAdjusted(list, title) {
|
||||
},
|
||||
],
|
||||
},
|
||||
{ name: "Breakdown", tree: valueBreakdownTree(list, title) },
|
||||
{ name: "Breakdown", tree: valueBreakdownTree(list, all, title) },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Grouped activity section with adjusted values (for cohorts with RealizedPattern3/4)
|
||||
* @param {{ list: readonly (CohortAll | CohortFull | CohortWithAdjusted)[], title: (metric: string) => string }} args
|
||||
* @param {{ list: readonly (CohortAll | CohortFull | CohortWithAdjusted)[], all: CohortAll, title: (metric: string) => string }} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createGroupedActivitySectionWithAdjusted({ list, title }) {
|
||||
export function createGroupedActivitySectionWithAdjusted({ list, all, title }) {
|
||||
return createGroupedActivitySection({
|
||||
list,
|
||||
all,
|
||||
title,
|
||||
soprTree: createGroupedSoprTreeWithAdjusted(list, title),
|
||||
valueTree: createGroupedValueTreeWithAdjusted(list, title),
|
||||
soprTree: createGroupedSoprTreeWithAdjusted(list, all, title),
|
||||
valueTree: createGroupedValueTreeWithAdjusted(list, all, title),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -827,10 +835,11 @@ function createSingleSellSideRiskSeries(tree) {
|
||||
/**
|
||||
* Create sell side risk ratio series for grouped cohorts
|
||||
* @param {readonly CohortObject[]} list
|
||||
* @param {CohortObject} all
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
function createGroupedSellSideRiskSeries(list) {
|
||||
return list.flatMap(({ color, name, tree }) => [
|
||||
function createGroupedSellSideRiskSeries(list, all) {
|
||||
return flatMapCohortsWithAll(list, all, ({ color, name, tree }) => [
|
||||
line({
|
||||
metric: tree.realized.sellSideRiskRatio,
|
||||
name,
|
||||
|
||||
@@ -14,6 +14,7 @@ import { colors } from "../../utils/colors.js";
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { priceLines } from "../constants.js";
|
||||
import { line, price } from "../series.js";
|
||||
import { mapCohortsWithAll } from "../shared.js";
|
||||
|
||||
/**
|
||||
* @param {PercentilesPattern} p
|
||||
@@ -185,12 +186,12 @@ function createSingleSummarySeriesWithPercentiles(cohort) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {readonly { name: string, color: Color, tree: { realized: { realizedPrice: ActivePricePattern } } }[]} T
|
||||
* @param {T} list
|
||||
* @param {readonly CohortObject[]} list
|
||||
* @param {CohortAll} all
|
||||
* @returns {FetchedPriceSeriesBlueprint[]}
|
||||
*/
|
||||
function createGroupedSummarySeries(list) {
|
||||
return list.map(({ name, color, tree }) =>
|
||||
function createGroupedSummarySeries(list, all) {
|
||||
return mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
price({ metric: tree.realized.realizedPrice, name, color }),
|
||||
);
|
||||
}
|
||||
@@ -305,35 +306,34 @@ export function createCostBasisSectionWithPercentiles({ cohort, title }) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {readonly (UtxoCohortObject | CohortWithoutRelative)[]} T
|
||||
* @param {{ list: T, title: (metric: string) => string }} args
|
||||
* @param {{ list: readonly (UtxoCohortObject | CohortWithoutRelative)[], all: CohortAll, title: (metric: string) => string }} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createGroupedCostBasisSection({ list, title }) {
|
||||
export function createGroupedCostBasisSection({ list, all, title }) {
|
||||
return {
|
||||
name: "Cost Basis",
|
||||
tree: [
|
||||
{
|
||||
name: "Summary",
|
||||
title: title("Cost Basis Summary"),
|
||||
top: createGroupedSummarySeries(list),
|
||||
top: createGroupedSummarySeries(list, all),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{ list: readonly (CohortAll | CohortFull | CohortWithPercentiles)[], title: (metric: string) => string }} args
|
||||
* @param {{ list: readonly (CohortAll | CohortFull | CohortWithPercentiles)[], all: CohortAll, title: (metric: string) => string }} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createGroupedCostBasisSectionWithPercentiles({ list, title }) {
|
||||
export function createGroupedCostBasisSectionWithPercentiles({ list, all, title }) {
|
||||
return {
|
||||
name: "Cost Basis",
|
||||
tree: [
|
||||
{
|
||||
name: "Summary",
|
||||
title: title("Cost Basis Summary"),
|
||||
top: createGroupedSummarySeries(list),
|
||||
top: createGroupedSummarySeries(list, all),
|
||||
},
|
||||
{
|
||||
name: "By Coin",
|
||||
@@ -341,28 +341,28 @@ export function createGroupedCostBasisSectionWithPercentiles({ list, title }) {
|
||||
{
|
||||
name: "Average",
|
||||
title: title("Realized Price Comparison"),
|
||||
top: list.map(({ name, color, tree }) =>
|
||||
top: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
price({ metric: tree.realized.realizedPrice, name, color }),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Median",
|
||||
title: title("Cost Basis Median (BTC-weighted)"),
|
||||
top: list.map(({ name, color, tree }) =>
|
||||
top: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
price({ metric: tree.costBasis.percentiles.pct50, name, color }),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Q3",
|
||||
title: title("Cost Basis Q3 (BTC-weighted)"),
|
||||
top: list.map(({ name, color, tree }) =>
|
||||
top: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
price({ metric: tree.costBasis.percentiles.pct75, name, color }),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Q1",
|
||||
title: title("Cost Basis Q1 (BTC-weighted)"),
|
||||
top: list.map(({ name, color, tree }) =>
|
||||
top: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
price({ metric: tree.costBasis.percentiles.pct25, name, color }),
|
||||
),
|
||||
},
|
||||
@@ -374,14 +374,14 @@ export function createGroupedCostBasisSectionWithPercentiles({ list, title }) {
|
||||
{
|
||||
name: "Average",
|
||||
title: title("Investor Price Comparison"),
|
||||
top: list.map(({ name, color, tree }) =>
|
||||
top: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
price({ metric: tree.realized.investorPrice, name, color }),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Median",
|
||||
title: title("Cost Basis Median (USD-weighted)"),
|
||||
top: list.map(({ name, color, tree }) =>
|
||||
top: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
price({
|
||||
metric: tree.costBasis.investedCapital.pct50,
|
||||
name,
|
||||
@@ -392,7 +392,7 @@ export function createGroupedCostBasisSectionWithPercentiles({ list, title }) {
|
||||
{
|
||||
name: "Q3",
|
||||
title: title("Cost Basis Q3 (USD-weighted)"),
|
||||
top: list.map(({ name, color, tree }) =>
|
||||
top: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
price({
|
||||
metric: tree.costBasis.investedCapital.pct75,
|
||||
name,
|
||||
@@ -403,7 +403,7 @@ export function createGroupedCostBasisSectionWithPercentiles({ list, title }) {
|
||||
{
|
||||
name: "Q1",
|
||||
title: title("Cost Basis Q1 (USD-weighted)"),
|
||||
top: list.map(({ name, color, tree }) =>
|
||||
top: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
price({
|
||||
metric: tree.costBasis.investedCapital.pct25,
|
||||
name,
|
||||
@@ -420,7 +420,7 @@ export function createGroupedCostBasisSectionWithPercentiles({ list, title }) {
|
||||
name: "By Coin",
|
||||
title: title("Price Position (BTC-weighted)"),
|
||||
bottom: [
|
||||
...list.map(({ name, color, tree }) =>
|
||||
...mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
line({
|
||||
metric: tree.costBasis.spotCostBasisPercentile,
|
||||
name,
|
||||
@@ -435,7 +435,7 @@ export function createGroupedCostBasisSectionWithPercentiles({ list, title }) {
|
||||
name: "By Capital",
|
||||
title: title("Price Position (USD-weighted)"),
|
||||
bottom: [
|
||||
...list.map(({ name, color, tree }) =>
|
||||
...mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
line({
|
||||
metric: tree.costBasis.spotInvestedCapitalPercentile,
|
||||
name,
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { line, baseline } from "../series.js";
|
||||
import { satsBtcUsd, satsBtcUsdBaseline } from "../shared.js";
|
||||
import { satsBtcUsd, satsBtcUsdBaseline, mapCohorts, mapCohortsWithAll, flatMapCohortsWithAll } from "../shared.js";
|
||||
import { colors } from "../../utils/colors.js";
|
||||
import { priceLines } from "../constants.js";
|
||||
|
||||
@@ -60,51 +60,45 @@ function circulatingSupplyPctSeries(tree) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Grouped UTXO count chart
|
||||
* @template {{ name: string, color: Color, tree: { outputs: { utxoCount: AnyMetricPattern } } }} T
|
||||
* @param {readonly T[]} list
|
||||
* @param {readonly (UtxoCohortObject | CohortWithoutRelative)[]} list
|
||||
* @param {CohortAll} all
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialChartOption}
|
||||
*/
|
||||
function groupedUtxoCountChart(list, title) {
|
||||
function groupedUtxoCountChart(list, all, title) {
|
||||
return {
|
||||
name: "UTXO Count",
|
||||
title: title("UTXO Count"),
|
||||
bottom: list.map(({ name, color, tree }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
line({ metric: tree.outputs.utxoCount, name, color, unit: Unit.count }),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Grouped 30d supply change chart
|
||||
* @template {{ name: string, color: Color, tree: { supply: { _30dChange: AnyValuePattern } } }} T
|
||||
* @param {readonly T[]} list
|
||||
* @param {readonly (UtxoCohortObject | CohortWithoutRelative)[]} list
|
||||
* @param {CohortAll} all
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialChartOption}
|
||||
*/
|
||||
function grouped30dSupplyChangeChart(list, title) {
|
||||
function grouped30dSupplyChangeChart(list, all, title) {
|
||||
return {
|
||||
name: "Supply",
|
||||
title: title("Supply 30d Change"),
|
||||
bottom: list.flatMap(({ name, color, tree }) =>
|
||||
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
satsBtcUsdBaseline({ pattern: tree.supply._30dChange, name, color }),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Grouped 30d UTXO count change chart
|
||||
* @template {{ name: string, color: Color, tree: { outputs: { utxoCount30dChange: AnyMetricPattern } } }} T
|
||||
* @param {readonly T[]} list
|
||||
* @param {readonly (UtxoCohortObject | CohortWithoutRelative)[]} list
|
||||
* @param {CohortAll} all
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialChartOption}
|
||||
*/
|
||||
function grouped30dUtxoCountChangeChart(list, title) {
|
||||
function grouped30dUtxoCountChangeChart(list, all, title) {
|
||||
return {
|
||||
name: "UTXO Count",
|
||||
title: title("UTXO Count 30d Change"),
|
||||
bottom: list.map(({ name, color, tree }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
baseline({ metric: tree.outputs.utxoCount30dChange, name, unit: Unit.count, color }),
|
||||
),
|
||||
};
|
||||
@@ -444,10 +438,10 @@ export function createHoldingsSectionAddressAmount({ cohort, title }) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{ list: readonly CohortAddress[], title: (metric: string) => string }} args
|
||||
* @param {{ list: readonly CohortAddress[], all: CohortAll, title: (metric: string) => string }} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createGroupedHoldingsSectionAddress({ list, title }) {
|
||||
export function createGroupedHoldingsSectionAddress({ list, all, title }) {
|
||||
return {
|
||||
name: "Holdings",
|
||||
tree: [
|
||||
@@ -457,7 +451,7 @@ export function createGroupedHoldingsSectionAddress({ list, title }) {
|
||||
{
|
||||
name: "Total",
|
||||
title: title("Supply"),
|
||||
bottom: list.flatMap(({ name, color, tree }) =>
|
||||
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
satsBtcUsd({ pattern: tree.supply.total, name, color }),
|
||||
),
|
||||
},
|
||||
@@ -465,14 +459,14 @@ export function createGroupedHoldingsSectionAddress({ list, title }) {
|
||||
name: "In Profit",
|
||||
title: title("Supply In Profit"),
|
||||
bottom: [
|
||||
...list.flatMap(({ name, color, tree }) =>
|
||||
...flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
satsBtcUsd({
|
||||
pattern: tree.unrealized.supplyInProfit,
|
||||
name,
|
||||
color,
|
||||
}),
|
||||
),
|
||||
...list.map(({ name, color, tree }) =>
|
||||
...mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
line({
|
||||
metric: tree.relative.supplyInProfitRelToOwnSupply,
|
||||
name,
|
||||
@@ -487,14 +481,14 @@ export function createGroupedHoldingsSectionAddress({ list, title }) {
|
||||
name: "In Loss",
|
||||
title: title("Supply In Loss"),
|
||||
bottom: [
|
||||
...list.flatMap(({ name, color, tree }) =>
|
||||
...flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
satsBtcUsd({
|
||||
pattern: tree.unrealized.supplyInLoss,
|
||||
name,
|
||||
color,
|
||||
}),
|
||||
),
|
||||
...list.map(({ name, color, tree }) =>
|
||||
...mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
line({
|
||||
metric: tree.relative.supplyInLossRelToOwnSupply,
|
||||
name,
|
||||
@@ -507,23 +501,23 @@ export function createGroupedHoldingsSectionAddress({ list, title }) {
|
||||
},
|
||||
],
|
||||
},
|
||||
groupedUtxoCountChart(list, title),
|
||||
groupedUtxoCountChart(list, all, title),
|
||||
{
|
||||
name: "Address Count",
|
||||
title: title("Address Count"),
|
||||
bottom: list.map(({ name, color, addrCount }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ name, color, addrCount }) =>
|
||||
line({ metric: addrCount.count, name, color, unit: Unit.count }),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "30d Changes",
|
||||
tree: [
|
||||
grouped30dSupplyChangeChart(list, title),
|
||||
grouped30dUtxoCountChangeChart(list, title),
|
||||
grouped30dSupplyChangeChart(list, all, title),
|
||||
grouped30dUtxoCountChangeChart(list, all, title),
|
||||
{
|
||||
name: "Address Count",
|
||||
title: title("Address Count 30d Change"),
|
||||
bottom: list.map(({ name, color, addrCount }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ name, color, addrCount }) =>
|
||||
baseline({ metric: addrCount._30dChange, name, unit: Unit.count, color }),
|
||||
),
|
||||
},
|
||||
@@ -535,10 +529,10 @@ export function createGroupedHoldingsSectionAddress({ list, title }) {
|
||||
|
||||
/**
|
||||
* Grouped holdings section for address amount cohorts (has relative supply + address count)
|
||||
* @param {{ list: readonly AddressCohortObject[], title: (metric: string) => string }} args
|
||||
* @param {{ list: readonly AddressCohortObject[], all: CohortAll, title: (metric: string) => string }} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createGroupedHoldingsSectionAddressAmount({ list, title }) {
|
||||
export function createGroupedHoldingsSectionAddressAmount({ list, all, title }) {
|
||||
return {
|
||||
name: "Holdings",
|
||||
tree: [
|
||||
@@ -549,10 +543,10 @@ export function createGroupedHoldingsSectionAddressAmount({ list, title }) {
|
||||
name: "Total",
|
||||
title: title("Supply"),
|
||||
bottom: [
|
||||
...list.flatMap(({ name, color, tree }) =>
|
||||
...flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
satsBtcUsd({ pattern: tree.supply.total, name, color }),
|
||||
),
|
||||
...list.map(({ name, color, tree }) =>
|
||||
...mapCohorts(list, ({ name, color, tree }) =>
|
||||
line({
|
||||
metric: tree.relative.supplyRelToCirculatingSupply,
|
||||
name,
|
||||
@@ -566,14 +560,14 @@ export function createGroupedHoldingsSectionAddressAmount({ list, title }) {
|
||||
name: "In Profit",
|
||||
title: title("Supply In Profit"),
|
||||
bottom: [
|
||||
...list.flatMap(({ name, color, tree }) =>
|
||||
...flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
satsBtcUsd({
|
||||
pattern: tree.unrealized.supplyInProfit,
|
||||
name,
|
||||
color,
|
||||
}),
|
||||
),
|
||||
...list.map(({ name, color, tree }) =>
|
||||
...mapCohorts(list, ({ name, color, tree }) =>
|
||||
line({
|
||||
metric: tree.relative.supplyInProfitRelToCirculatingSupply,
|
||||
name,
|
||||
@@ -581,7 +575,7 @@ export function createGroupedHoldingsSectionAddressAmount({ list, title }) {
|
||||
unit: Unit.pctSupply,
|
||||
}),
|
||||
),
|
||||
...list.map(({ name, color, tree }) =>
|
||||
...mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
line({
|
||||
metric: tree.relative.supplyInProfitRelToOwnSupply,
|
||||
name,
|
||||
@@ -596,14 +590,14 @@ export function createGroupedHoldingsSectionAddressAmount({ list, title }) {
|
||||
name: "In Loss",
|
||||
title: title("Supply In Loss"),
|
||||
bottom: [
|
||||
...list.flatMap(({ name, color, tree }) =>
|
||||
...flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
satsBtcUsd({
|
||||
pattern: tree.unrealized.supplyInLoss,
|
||||
name,
|
||||
color,
|
||||
}),
|
||||
),
|
||||
...list.map(({ name, color, tree }) =>
|
||||
...mapCohorts(list, ({ name, color, tree }) =>
|
||||
line({
|
||||
metric: tree.relative.supplyInLossRelToCirculatingSupply,
|
||||
name,
|
||||
@@ -611,7 +605,7 @@ export function createGroupedHoldingsSectionAddressAmount({ list, title }) {
|
||||
unit: Unit.pctSupply,
|
||||
}),
|
||||
),
|
||||
...list.map(({ name, color, tree }) =>
|
||||
...mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
line({
|
||||
metric: tree.relative.supplyInLossRelToOwnSupply,
|
||||
name,
|
||||
@@ -624,23 +618,23 @@ export function createGroupedHoldingsSectionAddressAmount({ list, title }) {
|
||||
},
|
||||
],
|
||||
},
|
||||
groupedUtxoCountChart(list, title),
|
||||
groupedUtxoCountChart(list, all, title),
|
||||
{
|
||||
name: "Address Count",
|
||||
title: title("Address Count"),
|
||||
bottom: list.map(({ name, color, addrCount }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ name, color, addrCount }) =>
|
||||
line({ metric: addrCount.count, name, color, unit: Unit.count }),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "30d Changes",
|
||||
tree: [
|
||||
grouped30dSupplyChangeChart(list, title),
|
||||
grouped30dUtxoCountChangeChart(list, title),
|
||||
grouped30dSupplyChangeChart(list, all, title),
|
||||
grouped30dUtxoCountChangeChart(list, all, title),
|
||||
{
|
||||
name: "Address Count",
|
||||
title: title("Address Count 30d Change"),
|
||||
bottom: list.map(({ name, color, addrCount }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ name, color, addrCount }) =>
|
||||
baseline({ metric: addrCount._30dChange, name, unit: Unit.count, color }),
|
||||
),
|
||||
},
|
||||
@@ -651,11 +645,10 @@ export function createGroupedHoldingsSectionAddressAmount({ list, title }) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {readonly (UtxoCohortObject | CohortWithoutRelative)[]} T
|
||||
* @param {{ list: T, title: (metric: string) => string }} args
|
||||
* @param {{ list: readonly (UtxoCohortObject | CohortWithoutRelative)[], all: CohortAll, title: (metric: string) => string }} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createGroupedHoldingsSection({ list, title }) {
|
||||
export function createGroupedHoldingsSection({ list, all, title }) {
|
||||
return {
|
||||
name: "Holdings",
|
||||
tree: [
|
||||
@@ -665,14 +658,14 @@ export function createGroupedHoldingsSection({ list, title }) {
|
||||
{
|
||||
name: "Total",
|
||||
title: title("Supply"),
|
||||
bottom: list.flatMap(({ name, color, tree }) =>
|
||||
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
satsBtcUsd({ pattern: tree.supply.total, name, color }),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "In Profit",
|
||||
title: title("Supply In Profit"),
|
||||
bottom: list.flatMap(({ name, color, tree }) =>
|
||||
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
satsBtcUsd({
|
||||
pattern: tree.unrealized.supplyInProfit,
|
||||
name,
|
||||
@@ -683,7 +676,7 @@ export function createGroupedHoldingsSection({ list, title }) {
|
||||
{
|
||||
name: "In Loss",
|
||||
title: title("Supply In Loss"),
|
||||
bottom: list.flatMap(({ name, color, tree }) =>
|
||||
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
satsBtcUsd({
|
||||
pattern: tree.unrealized.supplyInLoss,
|
||||
name,
|
||||
@@ -693,12 +686,12 @@ export function createGroupedHoldingsSection({ list, title }) {
|
||||
},
|
||||
],
|
||||
},
|
||||
groupedUtxoCountChart(list, title),
|
||||
groupedUtxoCountChart(list, all, title),
|
||||
{
|
||||
name: "30d Changes",
|
||||
tree: [
|
||||
grouped30dSupplyChangeChart(list, title),
|
||||
grouped30dUtxoCountChangeChart(list, title),
|
||||
grouped30dSupplyChangeChart(list, all, title),
|
||||
grouped30dUtxoCountChangeChart(list, all, title),
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -707,10 +700,10 @@ export function createGroupedHoldingsSection({ list, title }) {
|
||||
|
||||
/**
|
||||
* Grouped holdings section with % of Own Supply only (for cohorts without % of Circulating)
|
||||
* @param {{ list: readonly (CohortAgeRange | CohortBasicWithoutMarketCap)[], title: (metric: string) => string }} args
|
||||
* @param {{ list: readonly (CohortAgeRange | CohortBasicWithoutMarketCap)[], all: CohortAll, title: (metric: string) => string }} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createGroupedHoldingsSectionWithOwnSupply({ list, title }) {
|
||||
export function createGroupedHoldingsSectionWithOwnSupply({ list, all, title }) {
|
||||
return {
|
||||
name: "Holdings",
|
||||
tree: [
|
||||
@@ -721,14 +714,14 @@ export function createGroupedHoldingsSectionWithOwnSupply({ list, title }) {
|
||||
name: "In Profit",
|
||||
title: title("Supply In Profit"),
|
||||
bottom: [
|
||||
...list.flatMap(({ name, color, tree }) =>
|
||||
...flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
satsBtcUsd({
|
||||
pattern: tree.unrealized.supplyInProfit,
|
||||
name,
|
||||
color,
|
||||
}),
|
||||
),
|
||||
...list.map(({ name, color, tree }) =>
|
||||
...mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
line({
|
||||
metric: tree.relative.supplyInProfitRelToOwnSupply,
|
||||
name,
|
||||
@@ -743,14 +736,14 @@ export function createGroupedHoldingsSectionWithOwnSupply({ list, title }) {
|
||||
name: "In Loss",
|
||||
title: title("Supply In Loss"),
|
||||
bottom: [
|
||||
...list.flatMap(({ name, color, tree }) =>
|
||||
...flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
satsBtcUsd({
|
||||
pattern: tree.unrealized.supplyInLoss,
|
||||
name,
|
||||
color,
|
||||
}),
|
||||
),
|
||||
...list.map(({ name, color, tree }) =>
|
||||
...mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
line({
|
||||
metric: tree.relative.supplyInLossRelToOwnSupply,
|
||||
name,
|
||||
@@ -764,18 +757,18 @@ export function createGroupedHoldingsSectionWithOwnSupply({ list, title }) {
|
||||
{
|
||||
name: "Total",
|
||||
title: title("Supply"),
|
||||
bottom: list.flatMap(({ name, color, tree }) =>
|
||||
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
satsBtcUsd({ pattern: tree.supply.total, name, color }),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
groupedUtxoCountChart(list, title),
|
||||
groupedUtxoCountChart(list, all, title),
|
||||
{
|
||||
name: "30d Changes",
|
||||
tree: [
|
||||
grouped30dSupplyChangeChart(list, title),
|
||||
grouped30dUtxoCountChangeChart(list, title),
|
||||
grouped30dSupplyChangeChart(list, all, title),
|
||||
grouped30dUtxoCountChangeChart(list, all, title),
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -783,10 +776,10 @@ export function createGroupedHoldingsSectionWithOwnSupply({ list, title }) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{ list: readonly (CohortFull | CohortWithAdjusted | CohortBasicWithMarketCap | CohortMinAge)[], title: (metric: string) => string }} args
|
||||
* @param {{ list: readonly (CohortFull | CohortWithAdjusted | CohortBasicWithMarketCap | CohortMinAge)[], all: CohortAll, title: (metric: string) => string }} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createGroupedHoldingsSectionWithRelative({ list, title }) {
|
||||
export function createGroupedHoldingsSectionWithRelative({ list, all, title }) {
|
||||
return {
|
||||
name: "Holdings",
|
||||
tree: [
|
||||
@@ -797,10 +790,10 @@ export function createGroupedHoldingsSectionWithRelative({ list, title }) {
|
||||
name: "Total",
|
||||
title: title("Supply"),
|
||||
bottom: [
|
||||
...list.flatMap(({ name, color, tree }) =>
|
||||
...flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
satsBtcUsd({ pattern: tree.supply.total, name, color }),
|
||||
),
|
||||
...list.map(({ name, color, tree }) =>
|
||||
...mapCohorts(list, ({ name, color, tree }) =>
|
||||
line({
|
||||
metric: tree.relative.supplyRelToCirculatingSupply,
|
||||
name,
|
||||
@@ -814,14 +807,14 @@ export function createGroupedHoldingsSectionWithRelative({ list, title }) {
|
||||
name: "In Profit",
|
||||
title: title("Supply In Profit"),
|
||||
bottom: [
|
||||
...list.flatMap(({ name, color, tree }) =>
|
||||
...flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
satsBtcUsd({
|
||||
pattern: tree.unrealized.supplyInProfit,
|
||||
name,
|
||||
color,
|
||||
}),
|
||||
),
|
||||
...list.map(({ name, color, tree }) =>
|
||||
...mapCohorts(list, ({ name, color, tree }) =>
|
||||
line({
|
||||
metric: tree.relative.supplyInProfitRelToCirculatingSupply,
|
||||
name,
|
||||
@@ -829,7 +822,7 @@ export function createGroupedHoldingsSectionWithRelative({ list, title }) {
|
||||
unit: Unit.pctSupply,
|
||||
}),
|
||||
),
|
||||
...list.map(({ name, color, tree }) =>
|
||||
...mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
line({
|
||||
metric: tree.relative.supplyInProfitRelToOwnSupply,
|
||||
name,
|
||||
@@ -844,14 +837,14 @@ export function createGroupedHoldingsSectionWithRelative({ list, title }) {
|
||||
name: "In Loss",
|
||||
title: title("Supply In Loss"),
|
||||
bottom: [
|
||||
...list.flatMap(({ name, color, tree }) =>
|
||||
...flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
satsBtcUsd({
|
||||
pattern: tree.unrealized.supplyInLoss,
|
||||
name,
|
||||
color,
|
||||
}),
|
||||
),
|
||||
...list.map(({ name, color, tree }) =>
|
||||
...mapCohorts(list, ({ name, color, tree }) =>
|
||||
line({
|
||||
metric: tree.relative.supplyInLossRelToCirculatingSupply,
|
||||
name,
|
||||
@@ -859,7 +852,7 @@ export function createGroupedHoldingsSectionWithRelative({ list, title }) {
|
||||
unit: Unit.pctSupply,
|
||||
}),
|
||||
),
|
||||
...list.map(({ name, color, tree }) =>
|
||||
...mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
line({
|
||||
metric: tree.relative.supplyInLossRelToOwnSupply,
|
||||
name,
|
||||
@@ -872,12 +865,12 @@ export function createGroupedHoldingsSectionWithRelative({ list, title }) {
|
||||
},
|
||||
],
|
||||
},
|
||||
groupedUtxoCountChart(list, title),
|
||||
groupedUtxoCountChart(list, all, title),
|
||||
{
|
||||
name: "30d Changes",
|
||||
tree: [
|
||||
grouped30dSupplyChangeChart(list, title),
|
||||
grouped30dUtxoCountChangeChart(list, title),
|
||||
grouped30dSupplyChangeChart(list, all, title),
|
||||
grouped30dUtxoCountChangeChart(list, all, title),
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -70,427 +70,458 @@ import {
|
||||
export { buildCohortData } from "./data.js";
|
||||
|
||||
// ============================================================================
|
||||
// Folder Builders
|
||||
// Single Cohort Folder Builders
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* All folder: for the special "All" cohort (adjustedSopr + percentiles + RelToMarketCap)
|
||||
* @param {CohortAll} args
|
||||
* All folder: for the special "All" cohort
|
||||
* @param {CohortAll} cohort
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createCohortFolderAll(args) {
|
||||
const title = formatCohortTitle(args.name);
|
||||
export function createCohortFolderAll(cohort) {
|
||||
const title = formatCohortTitle(cohort.name);
|
||||
return {
|
||||
name: args.name || "all",
|
||||
name: cohort.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 }),
|
||||
createHoldingsSectionAll({ cohort, title }),
|
||||
createValuationSectionFull({ cohort, title }),
|
||||
createPricesSectionFull({ cohort, title }),
|
||||
createCostBasisSectionWithPercentiles({ cohort, title }),
|
||||
createProfitabilitySectionAll({ cohort, title }),
|
||||
createActivitySectionWithAdjusted({ cohort, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Full folder: adjustedSopr + percentiles + RelToMarketCap (term.short only)
|
||||
* @param {CohortFull | CohortGroupFull} args
|
||||
* Full folder: adjustedSopr + percentiles + RelToMarketCap
|
||||
* @param {CohortFull} cohort
|
||||
* @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);
|
||||
export function createCohortFolderFull(cohort) {
|
||||
const title = formatCohortTitle(cohort.name);
|
||||
return {
|
||||
name: args.name || "all",
|
||||
name: cohort.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 }),
|
||||
createHoldingsSectionWithRelative({ cohort, title }),
|
||||
createValuationSectionFull({ cohort, title }),
|
||||
createPricesSectionFull({ cohort, title }),
|
||||
createCostBasisSectionWithPercentiles({ cohort, title }),
|
||||
createProfitabilitySectionFull({ cohort, title }),
|
||||
createActivitySectionWithAdjusted({ cohort, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusted folder: adjustedSopr only, no percentiles (maxAge.*)
|
||||
* Has Peak Regret metrics like minAge
|
||||
* @param {CohortWithAdjusted | CohortGroupWithAdjusted} args
|
||||
* Adjusted folder: adjustedSopr only, no percentiles
|
||||
* @param {CohortWithAdjusted} cohort
|
||||
* @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);
|
||||
export function createCohortFolderWithAdjusted(cohort) {
|
||||
const title = formatCohortTitle(cohort.name);
|
||||
return {
|
||||
name: args.name || "all",
|
||||
name: cohort.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 }),
|
||||
createHoldingsSectionWithRelative({ cohort, title }),
|
||||
createValuationSection({ cohort, title }),
|
||||
createPricesSectionBasic({ cohort, title }),
|
||||
createCostBasisSection({ cohort, title }),
|
||||
createProfitabilitySectionWithPeakRegret({ cohort, title }),
|
||||
createActivitySectionWithAdjusted({ cohort, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Folder for cohorts with nupl + percentiles (no longer used for term.long which has own folder)
|
||||
* @param {CohortWithNuplPercentiles | CohortGroupWithNuplPercentiles} args
|
||||
* Folder for cohorts with nupl + percentiles
|
||||
* @param {CohortWithNuplPercentiles} cohort
|
||||
* @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);
|
||||
export function createCohortFolderWithNupl(cohort) {
|
||||
const title = formatCohortTitle(cohort.name);
|
||||
return {
|
||||
name: args.name || "all",
|
||||
name: cohort.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 }),
|
||||
createHoldingsSectionWithRelative({ cohort, title }),
|
||||
createValuationSectionFull({ cohort, title }),
|
||||
createPricesSectionFull({ cohort, title }),
|
||||
createCostBasisSectionWithPercentiles({ cohort, title }),
|
||||
createProfitabilitySectionWithNupl({ cohort, title }),
|
||||
createActivitySection({ cohort, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* LongTerm folder: term.long (has own market cap + NUPL + peak regret + P/L ratio)
|
||||
* @param {CohortLongTerm | CohortGroupLongTerm} args
|
||||
* LongTerm folder: has own market cap + NUPL + peak regret + P/L ratio
|
||||
* @param {CohortLongTerm} cohort
|
||||
* @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);
|
||||
export function createCohortFolderLongTerm(cohort) {
|
||||
const title = formatCohortTitle(cohort.name);
|
||||
return {
|
||||
name: args.name || "all",
|
||||
name: cohort.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 }),
|
||||
createHoldingsSectionWithRelative({ cohort, title }),
|
||||
createValuationSectionFull({ cohort, title }),
|
||||
createPricesSectionFull({ cohort, title }),
|
||||
createCostBasisSectionWithPercentiles({ cohort, title }),
|
||||
createProfitabilitySectionLongTerm({ cohort, title }),
|
||||
createActivitySection({ cohort, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Age range folder: ageRange.* (no nupl via RelativePattern2)
|
||||
* @param {CohortAgeRange | CohortGroupAgeRange} args
|
||||
* Age range folder: no nupl
|
||||
* @param {CohortAgeRange} cohort
|
||||
* @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);
|
||||
export function createCohortFolderAgeRange(cohort) {
|
||||
const title = formatCohortTitle(cohort.name);
|
||||
return {
|
||||
name: args.name || "all",
|
||||
name: cohort.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 }),
|
||||
createHoldingsSectionWithOwnSupply({ cohort, title }),
|
||||
createValuationSectionFull({ cohort, title }),
|
||||
createPricesSectionFull({ cohort, title }),
|
||||
createCostBasisSectionWithPercentiles({ cohort, title }),
|
||||
createProfitabilitySectionWithInvestedCapitalPct({ cohort, title }),
|
||||
createActivitySection({ cohort, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* MinAge folder - has peakRegret in unrealized (minAge.*)
|
||||
* @param {CohortMinAge | CohortGroupMinAge} args
|
||||
* MinAge folder: has peakRegret in unrealized
|
||||
* @param {CohortMinAge} cohort
|
||||
* @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);
|
||||
export function createCohortFolderMinAge(cohort) {
|
||||
const title = formatCohortTitle(cohort.name);
|
||||
return {
|
||||
name: args.name || "all",
|
||||
name: cohort.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 }),
|
||||
createHoldingsSectionWithRelative({ cohort, title }),
|
||||
createValuationSection({ cohort, title }),
|
||||
createPricesSectionBasic({ cohort, title }),
|
||||
createCostBasisSection({ cohort, title }),
|
||||
createProfitabilitySectionWithPeakRegret({ cohort, title }),
|
||||
createActivitySection({ cohort, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic folder WITH RelToMarketCap (geAmount.*, ltAmount.*)
|
||||
* @param {CohortBasicWithMarketCap | CohortGroupBasicWithMarketCap} args
|
||||
* Basic folder WITH RelToMarketCap
|
||||
* @param {CohortBasicWithMarketCap} cohort
|
||||
* @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);
|
||||
export function createCohortFolderBasicWithMarketCap(cohort) {
|
||||
const title = formatCohortTitle(cohort.name);
|
||||
return {
|
||||
name: args.name || "all",
|
||||
name: cohort.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 }),
|
||||
createHoldingsSectionWithRelative({ cohort, title }),
|
||||
createValuationSection({ cohort, title }),
|
||||
createPricesSectionBasic({ cohort, title }),
|
||||
createCostBasisSection({ cohort, title }),
|
||||
createProfitabilitySectionWithNupl({ cohort, title }),
|
||||
createActivitySection({ cohort, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic folder WITHOUT RelToMarketCap (epoch.*, amountRange.*, year.*)
|
||||
* @param {CohortBasicWithoutMarketCap | CohortGroupBasicWithoutMarketCap} args
|
||||
* Basic folder WITHOUT RelToMarketCap
|
||||
* @param {CohortBasicWithoutMarketCap} cohort
|
||||
* @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);
|
||||
export function createCohortFolderBasicWithoutMarketCap(cohort) {
|
||||
const title = formatCohortTitle(cohort.name);
|
||||
return {
|
||||
name: args.name || "all",
|
||||
name: cohort.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 }),
|
||||
createHoldingsSectionWithOwnSupply({ cohort, title }),
|
||||
createValuationSection({ cohort, title }),
|
||||
createPricesSectionBasic({ cohort, title }),
|
||||
createCostBasisSection({ cohort, title }),
|
||||
createProfitabilitySectionBasicWithInvestedCapitalPct({ cohort, title }),
|
||||
createActivitySection({ cohort, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Address folder: like basic but with address count (addressable type cohorts)
|
||||
* Has invested capital percentage metrics
|
||||
* @param {CohortAddress | CohortGroupAddress} args
|
||||
* Address folder: like basic but with address count
|
||||
* @param {CohortAddress} cohort
|
||||
* @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);
|
||||
export function createCohortFolderAddress(cohort) {
|
||||
const title = formatCohortTitle(cohort.name);
|
||||
return {
|
||||
name: args.name || "all",
|
||||
name: cohort.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 }),
|
||||
createHoldingsSectionAddress({ cohort, title }),
|
||||
createValuationSection({ cohort, title }),
|
||||
createPricesSectionBasic({ cohort, title }),
|
||||
createCostBasisSection({ cohort, title }),
|
||||
createProfitabilitySectionBasicWithInvestedCapitalPct({ cohort, title }),
|
||||
createActivitySection({ cohort, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Folder for cohorts WITHOUT relative section (edge case types: empty, p2ms, unknown)
|
||||
* @param {CohortWithoutRelative | CohortGroupWithoutRelative} args
|
||||
* Folder for cohorts WITHOUT relative section
|
||||
* @param {CohortWithoutRelative} cohort
|
||||
* @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);
|
||||
export function createCohortFolderWithoutRelative(cohort) {
|
||||
const title = formatCohortTitle(cohort.name);
|
||||
return {
|
||||
name: args.name || "all",
|
||||
name: cohort.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 }),
|
||||
createHoldingsSection({ cohort, title }),
|
||||
createValuationSection({ cohort, title }),
|
||||
createPricesSectionBasic({ cohort, title }),
|
||||
createCostBasisSection({ cohort, title }),
|
||||
createProfitabilitySection({ cohort, title }),
|
||||
createActivitySection({ cohort, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Address amount cohort folder - for address balance cohorts (has NUPL + addrCount)
|
||||
* @param {AddressCohortObject | AddressCohortGroupObject} args
|
||||
* Address amount cohort folder: has NUPL + addrCount
|
||||
* @param {AddressCohortObject} cohort
|
||||
* @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);
|
||||
export function createAddressCohortFolder(cohort) {
|
||||
const title = formatCohortTitle(cohort.name);
|
||||
return {
|
||||
name: args.name || "all",
|
||||
name: cohort.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 }),
|
||||
createHoldingsSectionAddressAmount({ cohort, title }),
|
||||
createValuationSection({ cohort, title }),
|
||||
createPricesSectionBasic({ cohort, title }),
|
||||
createCostBasisSection({ cohort, title }),
|
||||
createProfitabilitySectionWithNupl({ cohort, title }),
|
||||
createActivitySection({ cohort, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Grouped Cohort Folder Builders
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @param {CohortGroupFull} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createGroupedCohortFolderFull({ name, title: groupTitle, list, all }) {
|
||||
const title = formatCohortTitle(groupTitle);
|
||||
return {
|
||||
name: name || "all",
|
||||
tree: [
|
||||
createGroupedHoldingsSectionWithRelative({ list, all, title }),
|
||||
createGroupedValuationSectionWithOwnMarketCap({ list, all, title }),
|
||||
createGroupedPricesSection({ list, all, title }),
|
||||
createGroupedCostBasisSectionWithPercentiles({ list, all, title }),
|
||||
createGroupedProfitabilitySectionWithNupl({ list, all, title }),
|
||||
createGroupedActivitySectionWithAdjusted({ list, all, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {CohortGroupWithAdjusted} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createGroupedCohortFolderWithAdjusted({ name, title: groupTitle, list, all }) {
|
||||
const title = formatCohortTitle(groupTitle);
|
||||
return {
|
||||
name: name || "all",
|
||||
tree: [
|
||||
createGroupedHoldingsSectionWithRelative({ list, all, title }),
|
||||
createGroupedValuationSection({ list, all, title }),
|
||||
createGroupedPricesSection({ list, all, title }),
|
||||
createGroupedCostBasisSection({ list, all, title }),
|
||||
createGroupedProfitabilitySectionWithPeakRegret({ list, all, title }),
|
||||
createGroupedActivitySectionWithAdjusted({ list, all, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {CohortGroupWithNuplPercentiles} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createGroupedCohortFolderWithNupl({ name, title: groupTitle, list, all }) {
|
||||
const title = formatCohortTitle(groupTitle);
|
||||
return {
|
||||
name: name || "all",
|
||||
tree: [
|
||||
createGroupedHoldingsSectionWithRelative({ list, all, title }),
|
||||
createGroupedValuationSection({ list, all, title }),
|
||||
createGroupedPricesSection({ list, all, title }),
|
||||
createGroupedCostBasisSectionWithPercentiles({ list, all, title }),
|
||||
createGroupedProfitabilitySectionWithNupl({ list, all, title }),
|
||||
createGroupedActivitySection({ list, all, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {CohortGroupLongTerm} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createGroupedCohortFolderLongTerm({ name, title: groupTitle, list, all }) {
|
||||
const title = formatCohortTitle(groupTitle);
|
||||
return {
|
||||
name: name || "all",
|
||||
tree: [
|
||||
createGroupedHoldingsSectionWithRelative({ list, all, title }),
|
||||
createGroupedValuationSectionWithOwnMarketCap({ list, all, title }),
|
||||
createGroupedPricesSection({ list, all, title }),
|
||||
createGroupedCostBasisSectionWithPercentiles({ list, all, title }),
|
||||
createGroupedProfitabilitySectionLongTerm({ list, all, title }),
|
||||
createGroupedActivitySection({ list, all, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {CohortGroupAgeRange} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createGroupedCohortFolderAgeRange({ name, title: groupTitle, list, all }) {
|
||||
const title = formatCohortTitle(groupTitle);
|
||||
return {
|
||||
name: name || "all",
|
||||
tree: [
|
||||
createGroupedHoldingsSectionWithOwnSupply({ list, all, title }),
|
||||
createGroupedValuationSectionWithOwnMarketCap({ list, all, title }),
|
||||
createGroupedPricesSection({ list, all, title }),
|
||||
createGroupedCostBasisSectionWithPercentiles({ list, all, title }),
|
||||
createGroupedProfitabilitySectionWithInvestedCapitalPct({ list, all, title }),
|
||||
createGroupedActivitySection({ list, all, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {CohortGroupMinAge} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createGroupedCohortFolderMinAge({ name, title: groupTitle, list, all }) {
|
||||
const title = formatCohortTitle(groupTitle);
|
||||
return {
|
||||
name: name || "all",
|
||||
tree: [
|
||||
createGroupedHoldingsSectionWithRelative({ list, all, title }),
|
||||
createGroupedValuationSection({ list, all, title }),
|
||||
createGroupedPricesSection({ list, all, title }),
|
||||
createGroupedCostBasisSection({ list, all, title }),
|
||||
createGroupedProfitabilitySectionWithPeakRegret({ list, all, title }),
|
||||
createGroupedActivitySection({ list, all, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {CohortGroupBasicWithMarketCap} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createGroupedCohortFolderBasicWithMarketCap({ name, title: groupTitle, list, all }) {
|
||||
const title = formatCohortTitle(groupTitle);
|
||||
return {
|
||||
name: name || "all",
|
||||
tree: [
|
||||
createGroupedHoldingsSectionWithRelative({ list, all, title }),
|
||||
createGroupedValuationSection({ list, all, title }),
|
||||
createGroupedPricesSection({ list, all, title }),
|
||||
createGroupedCostBasisSection({ list, all, title }),
|
||||
createGroupedProfitabilitySectionWithNupl({ list, all, title }),
|
||||
createGroupedActivitySection({ list, all, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {CohortGroupBasicWithoutMarketCap} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createGroupedCohortFolderBasicWithoutMarketCap({ name, title: groupTitle, list, all }) {
|
||||
const title = formatCohortTitle(groupTitle);
|
||||
return {
|
||||
name: name || "all",
|
||||
tree: [
|
||||
createGroupedHoldingsSectionWithOwnSupply({ list, all, title }),
|
||||
createGroupedValuationSection({ list, all, title }),
|
||||
createGroupedPricesSection({ list, all, title }),
|
||||
createGroupedCostBasisSection({ list, all, title }),
|
||||
createGroupedProfitabilitySectionBasicWithInvestedCapitalPct({ list, all, title }),
|
||||
createGroupedActivitySection({ list, all, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {CohortGroupAddress} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createGroupedCohortFolderAddress({ name, title: groupTitle, list, all }) {
|
||||
const title = formatCohortTitle(groupTitle);
|
||||
return {
|
||||
name: name || "all",
|
||||
tree: [
|
||||
createGroupedHoldingsSectionAddress({ list, all, title }),
|
||||
createGroupedValuationSection({ list, all, title }),
|
||||
createGroupedPricesSection({ list, all, title }),
|
||||
createGroupedCostBasisSection({ list, all, title }),
|
||||
createGroupedProfitabilitySectionBasicWithInvestedCapitalPct({ list, all, title }),
|
||||
createGroupedActivitySection({ list, all, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {CohortGroupWithoutRelative} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createGroupedCohortFolderWithoutRelative({ name, title: groupTitle, list, all }) {
|
||||
const title = formatCohortTitle(groupTitle);
|
||||
return {
|
||||
name: name || "all",
|
||||
tree: [
|
||||
createGroupedHoldingsSection({ list, all, title }),
|
||||
createGroupedValuationSection({ list, all, title }),
|
||||
createGroupedPricesSection({ list, all, title }),
|
||||
createGroupedCostBasisSection({ list, all, title }),
|
||||
createGroupedProfitabilitySection({ list, all, title }),
|
||||
createGroupedActivitySection({ list, all, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AddressCohortGroupObject} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createGroupedAddressCohortFolder({ name, title: groupTitle, list, all }) {
|
||||
const title = formatCohortTitle(groupTitle);
|
||||
return {
|
||||
name: name || "all",
|
||||
tree: [
|
||||
createGroupedHoldingsSectionAddressAmount({ list, all, title }),
|
||||
createGroupedValuationSection({ list, all, title }),
|
||||
createGroupedPricesSection({ list, all, title }),
|
||||
createGroupedCostBasisSection({ list, all, title }),
|
||||
createGroupedProfitabilitySectionWithNupl({ list, all, title }),
|
||||
createGroupedActivitySection({ list, all, title }),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
*/
|
||||
|
||||
import { colors } from "../../utils/colors.js";
|
||||
import { createPriceRatioCharts } from "../shared.js";
|
||||
import { createPriceRatioCharts, mapCohortsWithAll } from "../shared.js";
|
||||
import { baseline, price } from "../series.js";
|
||||
import { Unit } from "../../utils/units.js";
|
||||
|
||||
@@ -148,11 +148,10 @@ export function createPricesSectionBasic({ cohort, title }) {
|
||||
|
||||
/**
|
||||
* Create prices section for grouped cohorts
|
||||
* @template {readonly (CohortAll | CohortFull | CohortWithPercentiles | CohortWithAdjusted | CohortBasic | CohortAddress | CohortWithoutRelative)[]} T
|
||||
* @param {{ list: T, title: (metric: string) => string }} args
|
||||
* @param {{ list: readonly CohortObject[], all: CohortAll, title: (metric: string) => string }} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createGroupedPricesSection({ list, title }) {
|
||||
export function createGroupedPricesSection({ list, all, title }) {
|
||||
return {
|
||||
name: "Prices",
|
||||
tree: [
|
||||
@@ -162,14 +161,14 @@ export function createGroupedPricesSection({ list, title }) {
|
||||
{
|
||||
name: "Price",
|
||||
title: title("Realized Price"),
|
||||
top: list.map(({ name, color, tree }) =>
|
||||
top: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
price({ metric: tree.realized.realizedPrice, name, color }),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Ratio",
|
||||
title: title("Realized Price Ratio"),
|
||||
bottom: list.map(({ name, color, tree }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
baseline({
|
||||
metric: tree.realized.realizedPriceExtra.ratio,
|
||||
name,
|
||||
@@ -187,14 +186,14 @@ export function createGroupedPricesSection({ list, title }) {
|
||||
{
|
||||
name: "Price",
|
||||
title: title("Investor Price"),
|
||||
top: list.map(({ name, color, tree }) =>
|
||||
top: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
price({ metric: tree.realized.investorPrice, name, color }),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Ratio",
|
||||
title: title("Investor Price Ratio"),
|
||||
bottom: list.map(({ name, color, tree }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
baseline({
|
||||
metric: tree.realized.investorPriceExtra.ratio,
|
||||
name,
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Unit } from "../../utils/units.js";
|
||||
import { line, baseline, dots, dotsBaseline } from "../series.js";
|
||||
import { colors } from "../../utils/colors.js";
|
||||
import { priceLine, priceLines } from "../constants.js";
|
||||
import { satsBtcUsd, satsBtcUsdFrom } from "../shared.js";
|
||||
import { satsBtcUsd, satsBtcUsdFrom, mapCohorts, mapCohortsWithAll, flatMapCohortsWithAll } from "../shared.js";
|
||||
|
||||
// ============================================================================
|
||||
// Core Series Builders (Composable Primitives)
|
||||
@@ -824,11 +824,6 @@ function realizedSubfolderWithExtras(tree, title) {
|
||||
// Single Cohort Section Builders
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @typedef {Object} SectionConfig
|
||||
* @property {(metric: string) => string} title
|
||||
*/
|
||||
|
||||
/**
|
||||
* Basic profitability section (USD only unrealized)
|
||||
* @param {{ cohort: UtxoCohortObject | CohortWithoutRelative, title: (metric: string) => string }} args
|
||||
@@ -1198,36 +1193,19 @@ export function createProfitabilitySectionWithPeakRegret({ cohort, title }) {
|
||||
// Grouped Cohort Helpers
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {{ color: Color, name: string, tree: T }} CohortItem
|
||||
*/
|
||||
|
||||
/**
|
||||
* Map cohorts to series
|
||||
* @template T
|
||||
* @template R
|
||||
* @param {readonly CohortItem<T>[]} list
|
||||
* @param {(item: CohortItem<T>) => R} fn
|
||||
* @returns {R[]}
|
||||
*/
|
||||
function mapCohorts(list, fn) {
|
||||
return list.map(fn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Grouped P&L charts (USD only)
|
||||
* @template {{ unrealized: UnrealizedPattern }} T
|
||||
* @param {readonly CohortItem<T>[]} list
|
||||
* @param {readonly CohortObject[]} list
|
||||
* @param {CohortAll} all
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function groupedPnlCharts(list, title) {
|
||||
function groupedPnlCharts(list, all, title) {
|
||||
return [
|
||||
{
|
||||
name: "Profit",
|
||||
title: title("Unrealized Profit"),
|
||||
bottom: mapCohorts(list, ({ color, name, tree }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.unrealized.unrealizedProfit,
|
||||
name,
|
||||
@@ -1239,7 +1217,7 @@ function groupedPnlCharts(list, title) {
|
||||
{
|
||||
name: "Loss",
|
||||
title: title("Unrealized Loss"),
|
||||
bottom: mapCohorts(list, ({ color, name, tree }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.unrealized.negUnrealizedLoss,
|
||||
name,
|
||||
@@ -1251,7 +1229,7 @@ function groupedPnlCharts(list, title) {
|
||||
{
|
||||
name: "Net P&L",
|
||||
title: title("Net Unrealized P&L"),
|
||||
bottom: mapCohorts(list, ({ color, name, tree }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
baseline({
|
||||
metric: tree.unrealized.netUnrealizedPnl,
|
||||
name,
|
||||
@@ -1265,18 +1243,18 @@ function groupedPnlCharts(list, title) {
|
||||
|
||||
/**
|
||||
* Grouped P&L with % of Market Cap
|
||||
* @template {{ unrealized: UnrealizedPattern, relative: RelativeWithNupl }} T
|
||||
* @param {readonly CohortItem<T>[]} list
|
||||
* @param {readonly (CohortFull | CohortBasicWithMarketCap | CohortMinAge | CohortLongTerm)[]} list
|
||||
* @param {CohortAll} all
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function groupedPnlChartsWithMarketCap(list, title) {
|
||||
function groupedPnlChartsWithMarketCap(list, all, title) {
|
||||
return [
|
||||
{
|
||||
name: "Profit",
|
||||
title: title("Unrealized Profit"),
|
||||
bottom: [
|
||||
...mapCohorts(list, ({ color, name, tree }) =>
|
||||
...mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.unrealized.unrealizedProfit,
|
||||
name,
|
||||
@@ -1284,7 +1262,7 @@ function groupedPnlChartsWithMarketCap(list, title) {
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
...mapCohorts(list, ({ color, name, tree }) =>
|
||||
...mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
baseline({
|
||||
metric: tree.relative.unrealizedProfitRelToMarketCap,
|
||||
name,
|
||||
@@ -1298,7 +1276,7 @@ function groupedPnlChartsWithMarketCap(list, title) {
|
||||
name: "Loss",
|
||||
title: title("Unrealized Loss"),
|
||||
bottom: [
|
||||
...mapCohorts(list, ({ color, name, tree }) =>
|
||||
...mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.unrealized.negUnrealizedLoss,
|
||||
name,
|
||||
@@ -1306,7 +1284,7 @@ function groupedPnlChartsWithMarketCap(list, title) {
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
...mapCohorts(list, ({ color, name, tree }) =>
|
||||
...mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
baseline({
|
||||
metric: tree.relative.negUnrealizedLossRelToMarketCap,
|
||||
name,
|
||||
@@ -1320,7 +1298,7 @@ function groupedPnlChartsWithMarketCap(list, title) {
|
||||
name: "Net P&L",
|
||||
title: title("Net Unrealized P&L"),
|
||||
bottom: [
|
||||
...mapCohorts(list, ({ color, name, tree }) =>
|
||||
...mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
baseline({
|
||||
metric: tree.unrealized.netUnrealizedPnl,
|
||||
name,
|
||||
@@ -1328,7 +1306,7 @@ function groupedPnlChartsWithMarketCap(list, title) {
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
...mapCohorts(list, ({ color, name, tree }) =>
|
||||
...mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
baseline({
|
||||
metric: tree.relative.netUnrealizedPnlRelToMarketCap,
|
||||
name,
|
||||
@@ -1343,18 +1321,18 @@ function groupedPnlChartsWithMarketCap(list, title) {
|
||||
|
||||
/**
|
||||
* Grouped P&L with % of Own Market Cap
|
||||
* @template {{ unrealized: UnrealizedPattern, relative: RelativeWithOwnMarketCap }} T
|
||||
* @param {readonly CohortItem<T>[]} list
|
||||
* @param {readonly CohortAgeRange[]} list
|
||||
* @param {CohortAll} all
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function groupedPnlChartsWithOwnMarketCap(list, title) {
|
||||
function groupedPnlChartsWithOwnMarketCap(list, all, title) {
|
||||
return [
|
||||
{
|
||||
name: "Profit",
|
||||
title: title("Unrealized Profit"),
|
||||
bottom: [
|
||||
...mapCohorts(list, ({ color, name, tree }) =>
|
||||
...mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.unrealized.unrealizedProfit,
|
||||
name,
|
||||
@@ -1362,6 +1340,7 @@ function groupedPnlChartsWithOwnMarketCap(list, title) {
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
// OwnMarketCap properties don't exist on CohortAll - use mapCohorts
|
||||
...mapCohorts(list, ({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.relative.unrealizedProfitRelToOwnMarketCap,
|
||||
@@ -1370,7 +1349,7 @@ function groupedPnlChartsWithOwnMarketCap(list, title) {
|
||||
unit: Unit.pctOwnMcap,
|
||||
}),
|
||||
),
|
||||
...mapCohorts(list, ({ color, name, tree }) =>
|
||||
...mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.relative.unrealizedProfitRelToOwnTotalUnrealizedPnl,
|
||||
name,
|
||||
@@ -1384,7 +1363,7 @@ function groupedPnlChartsWithOwnMarketCap(list, title) {
|
||||
name: "Loss",
|
||||
title: title("Unrealized Loss"),
|
||||
bottom: [
|
||||
...mapCohorts(list, ({ color, name, tree }) =>
|
||||
...mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.unrealized.negUnrealizedLoss,
|
||||
name,
|
||||
@@ -1392,6 +1371,7 @@ function groupedPnlChartsWithOwnMarketCap(list, title) {
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
// OwnMarketCap properties don't exist on CohortAll - use mapCohorts
|
||||
...mapCohorts(list, ({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.relative.negUnrealizedLossRelToOwnMarketCap,
|
||||
@@ -1400,7 +1380,7 @@ function groupedPnlChartsWithOwnMarketCap(list, title) {
|
||||
unit: Unit.pctOwnMcap,
|
||||
}),
|
||||
),
|
||||
...mapCohorts(list, ({ color, name, tree }) =>
|
||||
...mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.relative.negUnrealizedLossRelToOwnTotalUnrealizedPnl,
|
||||
name,
|
||||
@@ -1414,7 +1394,7 @@ function groupedPnlChartsWithOwnMarketCap(list, title) {
|
||||
name: "Net P&L",
|
||||
title: title("Net Unrealized P&L"),
|
||||
bottom: [
|
||||
...mapCohorts(list, ({ color, name, tree }) =>
|
||||
...mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
baseline({
|
||||
metric: tree.unrealized.netUnrealizedPnl,
|
||||
name,
|
||||
@@ -1422,6 +1402,7 @@ function groupedPnlChartsWithOwnMarketCap(list, title) {
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
// OwnMarketCap properties don't exist on CohortAll - use mapCohorts
|
||||
...mapCohorts(list, ({ color, name, tree }) =>
|
||||
baseline({
|
||||
metric: tree.relative.netUnrealizedPnlRelToOwnMarketCap,
|
||||
@@ -1430,7 +1411,7 @@ function groupedPnlChartsWithOwnMarketCap(list, title) {
|
||||
unit: Unit.pctOwnMcap,
|
||||
}),
|
||||
),
|
||||
...mapCohorts(list, ({ color, name, tree }) =>
|
||||
...mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
baseline({
|
||||
metric: tree.relative.netUnrealizedPnlRelToOwnTotalUnrealizedPnl,
|
||||
name,
|
||||
@@ -1446,16 +1427,17 @@ function groupedPnlChartsWithOwnMarketCap(list, title) {
|
||||
/**
|
||||
* Grouped P&L for LongTerm cohorts
|
||||
* @param {readonly CohortLongTerm[]} list
|
||||
* @param {CohortAll} all
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function groupedPnlChartsLongTerm(list, title) {
|
||||
function groupedPnlChartsLongTerm(list, all, title) {
|
||||
return [
|
||||
{
|
||||
name: "Profit",
|
||||
title: title("Unrealized Profit"),
|
||||
bottom: [
|
||||
...mapCohorts(list, ({ color, name, tree }) =>
|
||||
...mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.unrealized.unrealizedProfit,
|
||||
name,
|
||||
@@ -1463,6 +1445,7 @@ function groupedPnlChartsLongTerm(list, title) {
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
// OwnMarketCap properties don't exist on CohortAll - use mapCohorts
|
||||
...mapCohorts(list, ({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.relative.unrealizedProfitRelToOwnMarketCap,
|
||||
@@ -1471,7 +1454,7 @@ function groupedPnlChartsLongTerm(list, title) {
|
||||
unit: Unit.pctOwnMcap,
|
||||
}),
|
||||
),
|
||||
...mapCohorts(list, ({ color, name, tree }) =>
|
||||
...mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.relative.unrealizedProfitRelToOwnTotalUnrealizedPnl,
|
||||
name,
|
||||
@@ -1485,7 +1468,7 @@ function groupedPnlChartsLongTerm(list, title) {
|
||||
name: "Loss",
|
||||
title: title("Unrealized Loss"),
|
||||
bottom: [
|
||||
...mapCohorts(list, ({ color, name, tree }) =>
|
||||
...mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.unrealized.negUnrealizedLoss,
|
||||
name,
|
||||
@@ -1493,7 +1476,7 @@ function groupedPnlChartsLongTerm(list, title) {
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
...mapCohorts(list, ({ color, name, tree }) =>
|
||||
...mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.relative.unrealizedLossRelToMarketCap,
|
||||
name,
|
||||
@@ -1501,6 +1484,7 @@ function groupedPnlChartsLongTerm(list, title) {
|
||||
unit: Unit.pctMcap,
|
||||
}),
|
||||
),
|
||||
// OwnMarketCap properties don't exist on CohortAll - use mapCohorts
|
||||
...mapCohorts(list, ({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.relative.negUnrealizedLossRelToOwnMarketCap,
|
||||
@@ -1509,7 +1493,7 @@ function groupedPnlChartsLongTerm(list, title) {
|
||||
unit: Unit.pctOwnMcap,
|
||||
}),
|
||||
),
|
||||
...mapCohorts(list, ({ color, name, tree }) =>
|
||||
...mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.relative.negUnrealizedLossRelToOwnTotalUnrealizedPnl,
|
||||
name,
|
||||
@@ -1523,7 +1507,7 @@ function groupedPnlChartsLongTerm(list, title) {
|
||||
name: "Net P&L",
|
||||
title: title("Net Unrealized P&L"),
|
||||
bottom: [
|
||||
...mapCohorts(list, ({ color, name, tree }) =>
|
||||
...mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
baseline({
|
||||
metric: tree.unrealized.netUnrealizedPnl,
|
||||
name,
|
||||
@@ -1531,6 +1515,7 @@ function groupedPnlChartsLongTerm(list, title) {
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
// OwnMarketCap properties don't exist on CohortAll - use mapCohorts
|
||||
...mapCohorts(list, ({ color, name, tree }) =>
|
||||
baseline({
|
||||
metric: tree.relative.netUnrealizedPnlRelToOwnMarketCap,
|
||||
@@ -1539,7 +1524,7 @@ function groupedPnlChartsLongTerm(list, title) {
|
||||
unit: Unit.pctOwnMcap,
|
||||
}),
|
||||
),
|
||||
...mapCohorts(list, ({ color, name, tree }) =>
|
||||
...mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
baseline({
|
||||
metric: tree.relative.netUnrealizedPnlRelToOwnTotalUnrealizedPnl,
|
||||
name,
|
||||
@@ -1554,17 +1539,17 @@ function groupedPnlChartsLongTerm(list, title) {
|
||||
|
||||
/**
|
||||
* Grouped invested capital (absolute only)
|
||||
* @template {{ unrealized: UnrealizedPattern }} T
|
||||
* @param {readonly CohortItem<T>[]} list
|
||||
* @param {readonly CohortObject[]} list
|
||||
* @param {CohortAll} all
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function groupedInvestedCapitalAbsolute(list, title) {
|
||||
function groupedInvestedCapitalAbsolute(list, all, title) {
|
||||
return [
|
||||
{
|
||||
name: "In Profit",
|
||||
title: title("Invested Capital In Profit"),
|
||||
bottom: mapCohorts(list, ({ color, name, tree }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.unrealized.investedCapitalInProfit,
|
||||
name,
|
||||
@@ -1576,7 +1561,7 @@ function groupedInvestedCapitalAbsolute(list, title) {
|
||||
{
|
||||
name: "In Loss",
|
||||
title: title("Invested Capital In Loss"),
|
||||
bottom: mapCohorts(list, ({ color, name, tree }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.unrealized.investedCapitalInLoss,
|
||||
name,
|
||||
@@ -1590,18 +1575,18 @@ function groupedInvestedCapitalAbsolute(list, title) {
|
||||
|
||||
/**
|
||||
* Grouped invested capital with %
|
||||
* @template {{ unrealized: UnrealizedPattern, relative: RelativeWithInvestedCapitalPct }} T
|
||||
* @param {readonly CohortItem<T>[]} list
|
||||
* @param {readonly (CohortBasicWithoutMarketCap | CohortAgeRange | CohortFull | CohortBasicWithMarketCap | CohortLongTerm | CohortMinAge)[]} list
|
||||
* @param {CohortAll} all
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function groupedInvestedCapital(list, title) {
|
||||
function groupedInvestedCapital(list, all, title) {
|
||||
return [
|
||||
{
|
||||
name: "In Profit",
|
||||
title: title("Invested Capital In Profit"),
|
||||
bottom: [
|
||||
...mapCohorts(list, ({ color, name, tree }) =>
|
||||
...mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.unrealized.investedCapitalInProfit,
|
||||
name,
|
||||
@@ -1609,7 +1594,7 @@ function groupedInvestedCapital(list, title) {
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
...mapCohorts(list, ({ color, name, tree }) =>
|
||||
...mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
baseline({
|
||||
metric: tree.relative.investedCapitalInProfitPct,
|
||||
name,
|
||||
@@ -1624,7 +1609,7 @@ function groupedInvestedCapital(list, title) {
|
||||
name: "In Loss",
|
||||
title: title("Invested Capital In Loss"),
|
||||
bottom: [
|
||||
...mapCohorts(list, ({ color, name, tree }) =>
|
||||
...mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.unrealized.investedCapitalInLoss,
|
||||
name,
|
||||
@@ -1632,7 +1617,7 @@ function groupedInvestedCapital(list, title) {
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
...mapCohorts(list, ({ color, name, tree }) =>
|
||||
...mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
baseline({
|
||||
metric: tree.relative.investedCapitalInLossPct,
|
||||
name,
|
||||
@@ -1648,17 +1633,17 @@ function groupedInvestedCapital(list, title) {
|
||||
|
||||
/**
|
||||
* Grouped realized P&L sum
|
||||
* @template {{ realized: AnyRealizedPattern }} T
|
||||
* @param {readonly CohortItem<T>[]} list
|
||||
* @param {readonly CohortObject[]} list
|
||||
* @param {CohortAll} all
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function groupedRealizedPnlSum(list, title) {
|
||||
function groupedRealizedPnlSum(list, all, title) {
|
||||
return [
|
||||
{
|
||||
name: "Profit",
|
||||
title: title("Realized Profit"),
|
||||
bottom: mapCohorts(list, ({ color, name, tree }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.realized.realizedProfit.sum,
|
||||
name,
|
||||
@@ -1670,7 +1655,7 @@ function groupedRealizedPnlSum(list, title) {
|
||||
{
|
||||
name: "Loss",
|
||||
title: title("Realized Loss"),
|
||||
bottom: mapCohorts(list, ({ color, name, tree }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.realized.negRealizedLoss.sum,
|
||||
name,
|
||||
@@ -1682,7 +1667,7 @@ function groupedRealizedPnlSum(list, title) {
|
||||
{
|
||||
name: "Total",
|
||||
title: title("Total Realized P&L"),
|
||||
bottom: mapCohorts(list, ({ color, name, tree }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.realized.totalRealizedPnl,
|
||||
name,
|
||||
@@ -1694,7 +1679,7 @@ function groupedRealizedPnlSum(list, title) {
|
||||
{
|
||||
name: "Value",
|
||||
title: title("Realized Value"),
|
||||
bottom: mapCohorts(list, ({ color, name, tree }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.realized.realizedValue,
|
||||
name,
|
||||
@@ -1708,18 +1693,18 @@ function groupedRealizedPnlSum(list, title) {
|
||||
|
||||
/**
|
||||
* Grouped realized P&L sum with P/L ratio
|
||||
* @template {{ realized: RealizedWithExtras }} T
|
||||
* @param {readonly CohortItem<T>[]} list
|
||||
* @param {readonly (CohortAgeRange | CohortLongTerm | CohortAll | CohortFull)[]} list
|
||||
* @param {CohortAll} all
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function groupedRealizedPnlSumWithExtras(list, title) {
|
||||
function groupedRealizedPnlSumWithExtras(list, all, title) {
|
||||
return [
|
||||
...groupedRealizedPnlSum(list, title),
|
||||
...groupedRealizedPnlSum(list, all, title),
|
||||
{
|
||||
name: "P/L Ratio",
|
||||
title: title("Realized Profit/Loss Ratio"),
|
||||
bottom: mapCohorts(list, ({ color, name, tree }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
baseline({
|
||||
metric: tree.realized.realizedProfitToLossRatio,
|
||||
name,
|
||||
@@ -1733,17 +1718,17 @@ function groupedRealizedPnlSumWithExtras(list, title) {
|
||||
|
||||
/**
|
||||
* Grouped realized cumulative
|
||||
* @template {{ realized: AnyRealizedPattern }} T
|
||||
* @param {readonly CohortItem<T>[]} list
|
||||
* @param {readonly CohortObject[]} list
|
||||
* @param {CohortAll} all
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function groupedRealizedPnlCumulative(list, title) {
|
||||
function groupedRealizedPnlCumulative(list, all, title) {
|
||||
return [
|
||||
{
|
||||
name: "Profit",
|
||||
title: title("Cumulative Realized Profit"),
|
||||
bottom: mapCohorts(list, ({ color, name, tree }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.realized.realizedProfit.cumulative,
|
||||
name,
|
||||
@@ -1755,7 +1740,7 @@ function groupedRealizedPnlCumulative(list, title) {
|
||||
{
|
||||
name: "Loss",
|
||||
title: title("Cumulative Realized Loss"),
|
||||
bottom: mapCohorts(list, ({ color, name, tree }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.realized.negRealizedLoss.cumulative,
|
||||
name,
|
||||
@@ -1769,12 +1754,12 @@ function groupedRealizedPnlCumulative(list, title) {
|
||||
|
||||
/**
|
||||
* Grouped sent in P/L
|
||||
* @template {{ realized: AnyRealizedPattern }} T
|
||||
* @param {readonly CohortItem<T>[]} list
|
||||
* @param {readonly CohortObject[]} list
|
||||
* @param {CohortAll} all
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function groupedSentInPnl(list, title) {
|
||||
function groupedSentInPnl(list, all, title) {
|
||||
return [
|
||||
{
|
||||
name: "Sum",
|
||||
@@ -1783,7 +1768,7 @@ function groupedSentInPnl(list, title) {
|
||||
name: "In Profit",
|
||||
title: title("Sent In Profit"),
|
||||
bottom: [
|
||||
...list.flatMap(({ color, name, tree }) =>
|
||||
...flatMapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
satsBtcUsd({
|
||||
pattern: tree.realized.sentInProfit14dEma,
|
||||
name: `${name} 14d EMA`,
|
||||
@@ -1791,7 +1776,7 @@ function groupedSentInPnl(list, title) {
|
||||
defaultActive: false,
|
||||
}),
|
||||
),
|
||||
...list.flatMap(({ color, name, tree }) =>
|
||||
...flatMapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
satsBtcUsdFrom({
|
||||
source: tree.realized.sentInProfit,
|
||||
key: "sum",
|
||||
@@ -1805,7 +1790,7 @@ function groupedSentInPnl(list, title) {
|
||||
name: "In Loss",
|
||||
title: title("Sent In Loss"),
|
||||
bottom: [
|
||||
...list.flatMap(({ color, name, tree }) =>
|
||||
...flatMapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
satsBtcUsd({
|
||||
pattern: tree.realized.sentInLoss14dEma,
|
||||
name: `${name} 14d EMA`,
|
||||
@@ -1813,7 +1798,7 @@ function groupedSentInPnl(list, title) {
|
||||
defaultActive: false,
|
||||
}),
|
||||
),
|
||||
...list.flatMap(({ color, name, tree }) =>
|
||||
...flatMapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
satsBtcUsdFrom({
|
||||
source: tree.realized.sentInLoss,
|
||||
key: "sum",
|
||||
@@ -1831,7 +1816,7 @@ function groupedSentInPnl(list, title) {
|
||||
{
|
||||
name: "In Profit",
|
||||
title: title("Cumulative Sent In Profit"),
|
||||
bottom: list.flatMap(({ color, name, tree }) =>
|
||||
bottom: flatMapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
satsBtcUsdFrom({
|
||||
source: tree.realized.sentInProfit,
|
||||
key: "cumulative",
|
||||
@@ -1843,7 +1828,7 @@ function groupedSentInPnl(list, title) {
|
||||
{
|
||||
name: "In Loss",
|
||||
title: title("Cumulative Sent In Loss"),
|
||||
bottom: list.flatMap(({ color, name, tree }) =>
|
||||
bottom: flatMapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
satsBtcUsdFrom({
|
||||
source: tree.realized.sentInLoss,
|
||||
key: "cumulative",
|
||||
@@ -1859,19 +1844,19 @@ function groupedSentInPnl(list, title) {
|
||||
|
||||
/**
|
||||
* Grouped sentiment
|
||||
* @template {{ unrealized: UnrealizedPattern }} T
|
||||
* @param {readonly CohortItem<T>[]} list
|
||||
* @param {readonly CohortObject[]} list
|
||||
* @param {CohortAll} all
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
function groupedSentiment(list, title) {
|
||||
function groupedSentiment(list, all, title) {
|
||||
return {
|
||||
name: "Sentiment",
|
||||
tree: [
|
||||
{
|
||||
name: "Net",
|
||||
title: title("Net Sentiment"),
|
||||
bottom: mapCohorts(list, ({ color, name, tree }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
baseline({
|
||||
metric: tree.unrealized.netSentiment,
|
||||
name,
|
||||
@@ -1883,7 +1868,7 @@ function groupedSentiment(list, title) {
|
||||
{
|
||||
name: "Greed",
|
||||
title: title("Greed Index"),
|
||||
bottom: mapCohorts(list, ({ color, name, tree }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.unrealized.greedIndex,
|
||||
name,
|
||||
@@ -1895,7 +1880,7 @@ function groupedSentiment(list, title) {
|
||||
{
|
||||
name: "Pain",
|
||||
title: title("Pain Index"),
|
||||
bottom: mapCohorts(list, ({ color, name, tree }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.unrealized.painIndex,
|
||||
name,
|
||||
@@ -1910,20 +1895,20 @@ function groupedSentiment(list, title) {
|
||||
|
||||
/**
|
||||
* Grouped realized subfolder
|
||||
* @template {{ realized: AnyRealizedPattern }} T
|
||||
* @param {readonly CohortItem<T>[]} list
|
||||
* @param {readonly CohortObject[]} list
|
||||
* @param {CohortAll} all
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
function groupedRealizedSubfolder(list, title) {
|
||||
function groupedRealizedSubfolder(list, all, title) {
|
||||
return {
|
||||
name: "Realized",
|
||||
tree: [
|
||||
{ name: "P&L", tree: groupedRealizedPnlSum(list, title) },
|
||||
{ name: "P&L", tree: groupedRealizedPnlSum(list, all, title) },
|
||||
{
|
||||
name: "Net",
|
||||
title: title("Net Realized P&L"),
|
||||
bottom: mapCohorts(list, ({ color, name, tree }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
baseline({
|
||||
metric: tree.realized.netRealizedPnl.sum,
|
||||
name,
|
||||
@@ -1935,7 +1920,7 @@ function groupedRealizedSubfolder(list, title) {
|
||||
{
|
||||
name: "30d Change",
|
||||
title: title("Realized P&L 30d Change"),
|
||||
bottom: mapCohorts(list, ({ color, name, tree }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
baseline({
|
||||
metric: tree.realized.netRealizedPnlCumulative30dDelta,
|
||||
name,
|
||||
@@ -1947,11 +1932,11 @@ function groupedRealizedSubfolder(list, title) {
|
||||
{
|
||||
name: "Cumulative",
|
||||
tree: [
|
||||
{ name: "P&L", tree: groupedRealizedPnlCumulative(list, title) },
|
||||
{ name: "P&L", tree: groupedRealizedPnlCumulative(list, all, title) },
|
||||
{
|
||||
name: "Net",
|
||||
title: title("Cumulative Net Realized P&L"),
|
||||
bottom: mapCohorts(list, ({ color, name, tree }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
baseline({
|
||||
metric: tree.realized.netRealizedPnl.cumulative,
|
||||
name,
|
||||
@@ -1968,20 +1953,20 @@ function groupedRealizedSubfolder(list, title) {
|
||||
|
||||
/**
|
||||
* Grouped realized with extras
|
||||
* @template {{ realized: RealizedWithExtras }} T
|
||||
* @param {readonly CohortItem<T>[]} list
|
||||
* @param {readonly (CohortAgeRange | CohortLongTerm | CohortAll | CohortFull)[]} list
|
||||
* @param {CohortAll} all
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
function groupedRealizedSubfolderWithExtras(list, title) {
|
||||
function groupedRealizedSubfolderWithExtras(list, all, title) {
|
||||
return {
|
||||
name: "Realized",
|
||||
tree: [
|
||||
{ name: "P&L", tree: groupedRealizedPnlSumWithExtras(list, title) },
|
||||
{ name: "P&L", tree: groupedRealizedPnlSumWithExtras(list, all, title) },
|
||||
{
|
||||
name: "Net",
|
||||
title: title("Net Realized P&L"),
|
||||
bottom: mapCohorts(list, ({ color, name, tree }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
baseline({
|
||||
metric: tree.realized.netRealizedPnl.sum,
|
||||
name,
|
||||
@@ -1993,7 +1978,7 @@ function groupedRealizedSubfolderWithExtras(list, title) {
|
||||
{
|
||||
name: "30d Change",
|
||||
title: title("Realized P&L 30d Change"),
|
||||
bottom: mapCohorts(list, ({ color, name, tree }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
baseline({
|
||||
metric: tree.realized.netRealizedPnlCumulative30dDelta,
|
||||
name,
|
||||
@@ -2005,11 +1990,11 @@ function groupedRealizedSubfolderWithExtras(list, title) {
|
||||
{
|
||||
name: "Cumulative",
|
||||
tree: [
|
||||
{ name: "P&L", tree: groupedRealizedPnlCumulative(list, title) },
|
||||
{ name: "P&L", tree: groupedRealizedPnlCumulative(list, all, title) },
|
||||
{
|
||||
name: "Net",
|
||||
title: title("Cumulative Net Realized P&L"),
|
||||
bottom: mapCohorts(list, ({ color, name, tree }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
baseline({
|
||||
metric: tree.realized.netRealizedPnl.cumulative,
|
||||
name,
|
||||
@@ -2030,54 +2015,55 @@ function groupedRealizedSubfolderWithExtras(list, title) {
|
||||
|
||||
/**
|
||||
* Grouped profitability section (basic)
|
||||
* @template {readonly (UtxoCohortObject | CohortWithoutRelative)[]} T
|
||||
* @param {{ list: T, title: (metric: string) => string }} args
|
||||
* @param {{ list: readonly (UtxoCohortObject | CohortWithoutRelative)[], all: CohortAll, title: (metric: string) => string }} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createGroupedProfitabilitySection({ list, title }) {
|
||||
export function createGroupedProfitabilitySection({ list, all, title }) {
|
||||
return {
|
||||
name: "Profitability",
|
||||
tree: [
|
||||
{ name: "Unrealized", tree: groupedPnlCharts(list, title) },
|
||||
groupedRealizedSubfolder(list, title),
|
||||
{ name: "Volume", tree: groupedSentInPnl(list, title) },
|
||||
{ name: "Unrealized", tree: groupedPnlCharts(list, all, title) },
|
||||
groupedRealizedSubfolder(list, all, title),
|
||||
{ name: "Volume", tree: groupedSentInPnl(list, all, title) },
|
||||
{
|
||||
name: "Invested Capital",
|
||||
tree: groupedInvestedCapitalAbsolute(list, title),
|
||||
tree: groupedInvestedCapitalAbsolute(list, all, title),
|
||||
},
|
||||
groupedSentiment(list, title),
|
||||
groupedSentiment(list, all, title),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Grouped section with invested capital % (basic cohorts)
|
||||
* @param {{ list: readonly CohortBasicWithoutMarketCap[], title: (metric: string) => string }} args
|
||||
* @param {{ list: readonly CohortBasicWithoutMarketCap[], all: CohortAll, title: (metric: string) => string }} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createGroupedProfitabilitySectionBasicWithInvestedCapitalPct({
|
||||
list,
|
||||
all,
|
||||
title,
|
||||
}) {
|
||||
return {
|
||||
name: "Profitability",
|
||||
tree: [
|
||||
{ name: "Unrealized", tree: groupedPnlCharts(list, title) },
|
||||
groupedRealizedSubfolder(list, title),
|
||||
{ name: "Volume", tree: groupedSentInPnl(list, title) },
|
||||
{ name: "Invested Capital", tree: groupedInvestedCapital(list, title) },
|
||||
groupedSentiment(list, title),
|
||||
{ name: "Unrealized", tree: groupedPnlCharts(list, all, title) },
|
||||
groupedRealizedSubfolder(list, all, title),
|
||||
{ name: "Volume", tree: groupedSentInPnl(list, all, title) },
|
||||
{ name: "Invested Capital", tree: groupedInvestedCapital(list, all, title) },
|
||||
groupedSentiment(list, all, title),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Grouped section for ageRange cohorts
|
||||
* @param {{ list: readonly CohortAgeRange[], title: (metric: string) => string }} args
|
||||
* @param {{ list: readonly CohortAgeRange[], all: CohortAll, title: (metric: string) => string }} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createGroupedProfitabilitySectionWithInvestedCapitalPct({
|
||||
list,
|
||||
all,
|
||||
title,
|
||||
}) {
|
||||
return {
|
||||
@@ -2086,11 +2072,11 @@ export function createGroupedProfitabilitySectionWithInvestedCapitalPct({
|
||||
{
|
||||
name: "Unrealized",
|
||||
tree: [
|
||||
...groupedPnlChartsWithOwnMarketCap(list, title),
|
||||
...groupedPnlChartsWithOwnMarketCap(list, all, title),
|
||||
{
|
||||
name: "Peak Regret",
|
||||
title: title("Unrealized Peak Regret"),
|
||||
bottom: mapCohorts(list, ({ color, name, tree }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.unrealized.peakRegret,
|
||||
name,
|
||||
@@ -2101,31 +2087,31 @@ export function createGroupedProfitabilitySectionWithInvestedCapitalPct({
|
||||
},
|
||||
],
|
||||
},
|
||||
groupedRealizedSubfolderWithExtras(list, title),
|
||||
{ name: "Volume", tree: groupedSentInPnl(list, title) },
|
||||
{ name: "Invested Capital", tree: groupedInvestedCapital(list, title) },
|
||||
groupedSentiment(list, title),
|
||||
groupedRealizedSubfolderWithExtras(list, all, title),
|
||||
{ name: "Volume", tree: groupedSentInPnl(list, all, title) },
|
||||
{ name: "Invested Capital", tree: groupedInvestedCapital(list, all, title) },
|
||||
groupedSentiment(list, all, title),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Grouped section with NUPL
|
||||
* @param {{ list: readonly (CohortFull | CohortBasicWithMarketCap)[], title: (metric: string) => string }} args
|
||||
* @param {{ list: readonly (CohortFull | CohortBasicWithMarketCap)[], all: CohortAll, title: (metric: string) => string }} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createGroupedProfitabilitySectionWithNupl({ list, title }) {
|
||||
export function createGroupedProfitabilitySectionWithNupl({ list, all, title }) {
|
||||
return {
|
||||
name: "Profitability",
|
||||
tree: [
|
||||
{
|
||||
name: "Unrealized",
|
||||
tree: [
|
||||
...groupedPnlChartsWithMarketCap(list, title),
|
||||
...groupedPnlChartsWithMarketCap(list, all, title),
|
||||
{
|
||||
name: "NUPL",
|
||||
title: title("NUPL"),
|
||||
bottom: mapCohorts(list, ({ color, name, tree }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
baseline({
|
||||
metric: tree.relative.nupl,
|
||||
name,
|
||||
@@ -2136,31 +2122,31 @@ export function createGroupedProfitabilitySectionWithNupl({ list, title }) {
|
||||
},
|
||||
],
|
||||
},
|
||||
groupedRealizedSubfolder(list, title),
|
||||
{ name: "Volume", tree: groupedSentInPnl(list, title) },
|
||||
{ name: "Invested Capital", tree: groupedInvestedCapital(list, title) },
|
||||
groupedSentiment(list, title),
|
||||
groupedRealizedSubfolder(list, all, title),
|
||||
{ name: "Volume", tree: groupedSentInPnl(list, all, title) },
|
||||
{ name: "Invested Capital", tree: groupedInvestedCapital(list, all, title) },
|
||||
groupedSentiment(list, all, title),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Grouped section for LongTerm cohorts
|
||||
* @param {{ list: readonly CohortLongTerm[], title: (metric: string) => string }} args
|
||||
* @param {{ list: readonly CohortLongTerm[], all: CohortAll, title: (metric: string) => string }} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createGroupedProfitabilitySectionLongTerm({ list, title }) {
|
||||
export function createGroupedProfitabilitySectionLongTerm({ list, all, title }) {
|
||||
return {
|
||||
name: "Profitability",
|
||||
tree: [
|
||||
{
|
||||
name: "Unrealized",
|
||||
tree: [
|
||||
...groupedPnlChartsLongTerm(list, title),
|
||||
...groupedPnlChartsLongTerm(list, all, title),
|
||||
{
|
||||
name: "NUPL",
|
||||
title: title("NUPL"),
|
||||
bottom: mapCohorts(list, ({ color, name, tree }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
baseline({
|
||||
metric: tree.relative.nupl,
|
||||
name,
|
||||
@@ -2173,7 +2159,7 @@ export function createGroupedProfitabilitySectionLongTerm({ list, title }) {
|
||||
name: "Peak Regret",
|
||||
title: title("Unrealized Peak Regret"),
|
||||
bottom: [
|
||||
...mapCohorts(list, ({ color, name, tree }) =>
|
||||
...mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.unrealized.peakRegret,
|
||||
name,
|
||||
@@ -2181,7 +2167,7 @@ export function createGroupedProfitabilitySectionLongTerm({ list, title }) {
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
...mapCohorts(list, ({ color, name, tree }) =>
|
||||
...mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
baseline({
|
||||
metric: tree.relative.unrealizedPeakRegretRelToMarketCap,
|
||||
name,
|
||||
@@ -2193,21 +2179,22 @@ export function createGroupedProfitabilitySectionLongTerm({ list, title }) {
|
||||
},
|
||||
],
|
||||
},
|
||||
groupedRealizedSubfolderWithExtras(list, title),
|
||||
{ name: "Volume", tree: groupedSentInPnl(list, title) },
|
||||
{ name: "Invested Capital", tree: groupedInvestedCapital(list, title) },
|
||||
groupedSentiment(list, title),
|
||||
groupedRealizedSubfolderWithExtras(list, all, title),
|
||||
{ name: "Volume", tree: groupedSentInPnl(list, all, title) },
|
||||
{ name: "Invested Capital", tree: groupedInvestedCapital(list, all, title) },
|
||||
groupedSentiment(list, all, title),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Grouped section with Peak Regret + NUPL (minAge cohorts)
|
||||
* @param {{ list: readonly CohortMinAge[], title: (metric: string) => string }} args
|
||||
* @param {{ list: readonly CohortMinAge[], all: CohortAll, title: (metric: string) => string }} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createGroupedProfitabilitySectionWithPeakRegret({
|
||||
list,
|
||||
all,
|
||||
title,
|
||||
}) {
|
||||
return {
|
||||
@@ -2216,11 +2203,11 @@ export function createGroupedProfitabilitySectionWithPeakRegret({
|
||||
{
|
||||
name: "Unrealized",
|
||||
tree: [
|
||||
...groupedPnlChartsWithMarketCap(list, title),
|
||||
...groupedPnlChartsWithMarketCap(list, all, title),
|
||||
{
|
||||
name: "NUPL",
|
||||
title: title("NUPL"),
|
||||
bottom: mapCohorts(list, ({ color, name, tree }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
baseline({
|
||||
metric: tree.relative.nupl,
|
||||
name,
|
||||
@@ -2233,7 +2220,7 @@ export function createGroupedProfitabilitySectionWithPeakRegret({
|
||||
name: "Peak Regret",
|
||||
title: title("Unrealized Peak Regret"),
|
||||
bottom: [
|
||||
...mapCohorts(list, ({ color, name, tree }) =>
|
||||
...mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
line({
|
||||
metric: tree.unrealized.peakRegret,
|
||||
name,
|
||||
@@ -2241,7 +2228,7 @@ export function createGroupedProfitabilitySectionWithPeakRegret({
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
...mapCohorts(list, ({ color, name, tree }) =>
|
||||
...mapCohortsWithAll(list, all, ({ color, name, tree }) =>
|
||||
baseline({
|
||||
metric: tree.relative.unrealizedPeakRegretRelToMarketCap,
|
||||
name,
|
||||
@@ -2253,10 +2240,10 @@ export function createGroupedProfitabilitySectionWithPeakRegret({
|
||||
},
|
||||
],
|
||||
},
|
||||
groupedRealizedSubfolder(list, title),
|
||||
{ name: "Volume", tree: groupedSentInPnl(list, title) },
|
||||
{ name: "Invested Capital", tree: groupedInvestedCapital(list, title) },
|
||||
groupedSentiment(list, title),
|
||||
groupedRealizedSubfolder(list, all, title),
|
||||
{ name: "Volume", tree: groupedSentInPnl(list, all, title) },
|
||||
{ name: "Invested Capital", tree: groupedInvestedCapital(list, all, title) },
|
||||
groupedSentiment(list, all, title),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { line, baseline } from "../series.js";
|
||||
import { createRatioChart } from "../shared.js";
|
||||
import { createRatioChart, mapCohortsWithAll } from "../shared.js";
|
||||
|
||||
/**
|
||||
* @param {UtxoCohortObject | CohortWithoutRelative} cohort
|
||||
@@ -123,18 +123,17 @@ export function createValuationSection({ cohort, title }) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {readonly (UtxoCohortObject | CohortWithoutRelative)[]} T
|
||||
* @param {{ list: T, title: (metric: string) => string }} args
|
||||
* @param {{ list: readonly (UtxoCohortObject | CohortWithoutRelative)[], all: CohortAll, title: (metric: string) => string }} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createGroupedValuationSection({ list, title }) {
|
||||
export function createGroupedValuationSection({ list, all, title }) {
|
||||
return {
|
||||
name: "Valuation",
|
||||
tree: [
|
||||
{
|
||||
name: "Realized Cap",
|
||||
title: title("Realized Cap"),
|
||||
bottom: list.map(({ name, color, tree }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
line({
|
||||
metric: tree.realized.realizedCap,
|
||||
name,
|
||||
@@ -146,7 +145,7 @@ export function createGroupedValuationSection({ list, title }) {
|
||||
{
|
||||
name: "30d Change",
|
||||
title: title("Realized Cap 30d Change"),
|
||||
bottom: list.map(({ name, color, tree }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
baseline({
|
||||
metric: tree.realized.realizedCap30dDelta,
|
||||
name,
|
||||
@@ -158,7 +157,7 @@ export function createGroupedValuationSection({ list, title }) {
|
||||
{
|
||||
name: "MVRV",
|
||||
title: title("MVRV"),
|
||||
bottom: list.map(({ name, color, tree }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
baseline({
|
||||
metric: tree.realized.realizedPriceExtra.ratio,
|
||||
name,
|
||||
@@ -173,11 +172,10 @@ 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
|
||||
* @param {{ list: readonly (CohortAll | CohortFull | CohortWithPercentiles)[], all: CohortAll, title: (metric: string) => string }} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createGroupedValuationSectionWithOwnMarketCap({ list, title }) {
|
||||
export function createGroupedValuationSectionWithOwnMarketCap({ list, all, title }) {
|
||||
return {
|
||||
name: "Valuation",
|
||||
tree: [
|
||||
@@ -185,7 +183,7 @@ export function createGroupedValuationSectionWithOwnMarketCap({ list, title }) {
|
||||
name: "Realized Cap",
|
||||
title: title("Realized Cap"),
|
||||
bottom: [
|
||||
...list.map(({ name, color, tree }) =>
|
||||
...mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
line({
|
||||
metric: tree.realized.realizedCap,
|
||||
name,
|
||||
@@ -193,7 +191,7 @@ export function createGroupedValuationSectionWithOwnMarketCap({ list, title }) {
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
...list.map(({ name, color, tree }) =>
|
||||
...mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
baseline({
|
||||
metric: tree.realized.realizedCapRelToOwnMarketCap,
|
||||
name,
|
||||
@@ -206,7 +204,7 @@ export function createGroupedValuationSectionWithOwnMarketCap({ list, title }) {
|
||||
{
|
||||
name: "30d Change",
|
||||
title: title("Realized Cap 30d Change"),
|
||||
bottom: list.map(({ name, color, tree }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
baseline({
|
||||
metric: tree.realized.realizedCap30dDelta,
|
||||
name,
|
||||
@@ -218,7 +216,7 @@ export function createGroupedValuationSectionWithOwnMarketCap({ list, title }) {
|
||||
{
|
||||
name: "MVRV",
|
||||
title: title("MVRV"),
|
||||
bottom: list.map(({ name, color, tree }) =>
|
||||
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
baseline({
|
||||
metric: tree.realized.realizedPriceExtra.ratio,
|
||||
name,
|
||||
|
||||
Reference in New Issue
Block a user