global: snapshot

This commit is contained in:
nym21
2026-02-01 22:38:01 +01:00
parent f03bbd9a92
commit f7d7c5704a
47 changed files with 2924 additions and 837 deletions

View File

@@ -7,7 +7,7 @@
import { Unit } from "../../utils/units.js";
import { priceLine } from "../constants.js";
import { line, baseline, price } from "../series.js";
import { formatCohortTitle } from "../shared.js";
import { formatCohortTitle, satsBtcUsd } from "../shared.js";
import {
createSingleSupplySeries,
createGroupedSupplySection,
@@ -19,9 +19,6 @@ import {
createGroupedCoinblocksDestroyedSeries,
createGroupedCoindaysDestroyedSeries,
createSingleSentSeries,
createGroupedSentSatsSeries,
createGroupedSentBitcoinSeries,
createGroupedSentDollarsSeries,
groupedSupplyRelativeGenerators,
createSingleSupplyRelativeOptions,
createSingleSellSideRiskSeries,
@@ -139,13 +136,13 @@ export function createAddressCohortFolder(ctx, args) {
}),
),
},
...(!useGroupName
? createRealizedPnlSection(
...(useGroupName
? createGroupedRealizedPnlSection(ctx, list, title)
: createRealizedPnlSection(
ctx,
/** @type {AddressCohortObject} */ (args),
title,
)
: []),
)),
],
},
@@ -255,6 +252,12 @@ function createRealizedPnlSection(ctx, args, title) {
color: colors.green,
unit: Unit.usd,
}),
line({
metric: realized.realizedProfit7dEma,
name: "Profit 7d EMA",
color: colors.green,
unit: Unit.usd,
}),
line({
metric: realized.realizedProfit.cumulative,
name: "Profit Cumulative",
@@ -268,6 +271,12 @@ function createRealizedPnlSection(ctx, args, title) {
color: colors.red,
unit: Unit.usd,
}),
line({
metric: realized.realizedLoss7dEma,
name: "Loss 7d EMA",
color: colors.red,
unit: Unit.usd,
}),
line({
metric: realized.realizedLoss.cumulative,
name: "Loss Cumulative",
@@ -333,6 +342,11 @@ function createRealizedPnlSection(ctx, args, title) {
name: "Net",
unit: Unit.usd,
}),
baseline({
metric: realized.netRealizedPnl7dEma,
name: "Net 7d EMA",
unit: Unit.usd,
}),
baseline({
metric: realized.netRealizedPnl.cumulative,
name: "Net Cumulative",
@@ -419,6 +433,179 @@ function createRealizedPnlSection(ctx, args, title) {
},
],
},
{
name: "Peak Regret",
title: title("Peak Regret"),
bottom: [
line({ metric: realized.peakRegret.sum, name: "Sum", color: colors.red, unit: Unit.usd }),
line({ metric: realized.peakRegret.cumulative, name: "Cumulative", color: colors.red, unit: Unit.usd, defaultActive: false }),
baseline({ metric: realized.peakRegretRelToRealizedCap, name: "Rel. to Realized Cap", color: colors.orange, unit: Unit.pctRcap }),
],
},
{
name: "Sent In P/L",
tree: [
{
name: "In Profit",
title: title("Sent In Profit"),
bottom: [
line({ metric: realized.sentInProfit.bitcoin.sum, name: "Sum", color: colors.green, unit: Unit.btc }),
line({ metric: realized.sentInProfit.bitcoin.cumulative, name: "Cumulative", color: colors.green, unit: Unit.btc, defaultActive: false }),
line({ metric: realized.sentInProfit.sats.sum, name: "Sum", color: colors.green, unit: Unit.sats }),
line({ metric: realized.sentInProfit.sats.cumulative, name: "Cumulative", color: colors.green, unit: Unit.sats, defaultActive: false }),
line({ metric: realized.sentInProfit.dollars.sum, name: "Sum", color: colors.green, unit: Unit.usd }),
line({ metric: realized.sentInProfit.dollars.cumulative, name: "Cumulative", color: colors.green, unit: Unit.usd, defaultActive: false }),
],
},
{
name: "In Loss",
title: title("Sent In Loss"),
bottom: [
line({ metric: realized.sentInLoss.bitcoin.sum, name: "Sum", color: colors.red, unit: Unit.btc }),
line({ metric: realized.sentInLoss.bitcoin.cumulative, name: "Cumulative", color: colors.red, unit: Unit.btc, defaultActive: false }),
line({ metric: realized.sentInLoss.sats.sum, name: "Sum", color: colors.red, unit: Unit.sats }),
line({ metric: realized.sentInLoss.sats.cumulative, name: "Cumulative", color: colors.red, unit: Unit.sats, defaultActive: false }),
line({ metric: realized.sentInLoss.dollars.sum, name: "Sum", color: colors.red, unit: Unit.usd }),
line({ metric: realized.sentInLoss.dollars.cumulative, name: "Cumulative", color: colors.red, unit: Unit.usd, defaultActive: false }),
],
},
{
name: "In Profit 14d EMA",
title: title("Sent In Profit 14d EMA"),
bottom: satsBtcUsd({ pattern: realized.sentInProfit14dEma, name: "14d EMA", color: colors.green }),
},
{
name: "In Loss 14d EMA",
title: title("Sent In Loss 14d EMA"),
bottom: satsBtcUsd({ pattern: realized.sentInLoss14dEma, name: "14d EMA", color: colors.red }),
},
],
},
];
}
/**
* Create grouped realized P&L section for address cohorts (for compare view)
* @param {PartialContext} ctx
* @param {readonly AddressCohortObject[]} list
* @param {(metric: string) => string} title
* @returns {PartialOptionsTree}
*/
function createGroupedRealizedPnlSection(ctx, list, title) {
const pnlConfigs = /** @type {const} */ ([
{ name: "Profit", sum: "realizedProfit", ema: "realizedProfit7dEma", rel: "realizedProfitRelToRealizedCap", isNet: false },
{ name: "Loss", sum: "realizedLoss", ema: "realizedLoss7dEma", rel: "realizedLossRelToRealizedCap", isNet: false },
{ name: "Net P&L", sum: "netRealizedPnl", ema: "netRealizedPnl7dEma", rel: "netRealizedPnlRelToRealizedCap", isNet: true },
]);
return [
...pnlConfigs.map(({ name, sum, ema, rel, isNet }) => ({
name,
tree: [
{
name: "Sum",
title: title(`Realized ${name}`),
bottom: [
...list.flatMap(({ color, name, tree }) => [
(isNet ? baseline : line)({ metric: tree.realized[sum].sum, name, color, unit: Unit.usd }),
baseline({ metric: tree.realized[rel].sum, name, color, unit: Unit.pctRcap }),
]),
priceLine({ ctx, unit: Unit.usd }),
],
},
{
name: "7d EMA",
title: title(`Realized ${name} 7d EMA`),
bottom: [
...list.map(({ color, name, tree }) =>
(isNet ? baseline : line)({ metric: tree.realized[ema], name, color, unit: Unit.usd }),
),
priceLine({ ctx, unit: Unit.usd }),
],
},
],
})),
{
name: "Peak Regret",
tree: [
{
name: "Sum",
title: title("Peak Regret"),
bottom: list.flatMap(({ color, name, tree }) => [
line({ metric: tree.realized.peakRegret.sum, name, color, unit: Unit.usd }),
]),
},
{
name: "Cumulative",
title: title("Peak Regret Cumulative"),
bottom: list.flatMap(({ color, name, tree }) => [
line({ metric: tree.realized.peakRegret.cumulative, name, color, unit: Unit.usd }),
]),
},
{
name: "Rel. to Realized Cap",
title: title("Peak Regret Rel. to Realized Cap"),
bottom: list.flatMap(({ color, name, tree }) => [
baseline({ metric: tree.realized.peakRegretRelToRealizedCap, name, color, unit: Unit.pctRcap }),
]),
},
],
},
{
name: "Sent In P/L",
tree: [
{
name: "In Profit",
title: title("Sent In Profit"),
bottom: list.flatMap(({ color, name, tree }) => [
line({ metric: tree.realized.sentInProfit.bitcoin.sum, name, color, unit: Unit.btc }),
line({ metric: tree.realized.sentInProfit.sats.sum, name, color, unit: Unit.sats }),
line({ metric: tree.realized.sentInProfit.dollars.sum, name, color, unit: Unit.usd }),
]),
},
{
name: "In Profit Cumulative",
title: title("Sent In Profit Cumulative"),
bottom: list.flatMap(({ color, name, tree }) => [
line({ metric: tree.realized.sentInProfit.bitcoin.cumulative, name, color, unit: Unit.btc }),
line({ metric: tree.realized.sentInProfit.sats.cumulative, name, color, unit: Unit.sats }),
line({ metric: tree.realized.sentInProfit.dollars.cumulative, name, color, unit: Unit.usd }),
]),
},
{
name: "In Loss",
title: title("Sent In Loss"),
bottom: list.flatMap(({ color, name, tree }) => [
line({ metric: tree.realized.sentInLoss.bitcoin.sum, name, color, unit: Unit.btc }),
line({ metric: tree.realized.sentInLoss.sats.sum, name, color, unit: Unit.sats }),
line({ metric: tree.realized.sentInLoss.dollars.sum, name, color, unit: Unit.usd }),
]),
},
{
name: "In Loss Cumulative",
title: title("Sent In Loss Cumulative"),
bottom: list.flatMap(({ color, name, tree }) => [
line({ metric: tree.realized.sentInLoss.bitcoin.cumulative, name, color, unit: Unit.btc }),
line({ metric: tree.realized.sentInLoss.sats.cumulative, name, color, unit: Unit.sats }),
line({ metric: tree.realized.sentInLoss.dollars.cumulative, name, color, unit: Unit.usd }),
]),
},
{
name: "In Profit 14d EMA",
title: title("Sent In Profit 14d EMA"),
bottom: list.flatMap(({ color, name, tree }) =>
satsBtcUsd({ pattern: tree.realized.sentInProfit14dEma, name, color }),
),
},
{
name: "In Loss 14d EMA",
title: title("Sent In Loss 14d EMA"),
bottom: list.flatMap(({ color, name, tree }) =>
satsBtcUsd({ pattern: tree.realized.sentInLoss14dEma, name, color }),
),
},
],
},
];
}
@@ -620,6 +807,22 @@ function createUnrealizedSection(ctx, list, useGroupName, title) {
}),
]),
},
{
name: "Net Sentiment",
title: title("Net Sentiment"),
bottom: list.flatMap(({ color, name, tree }) => [
baseline({
metric: tree.unrealized.netSentiment,
name: useGroupName ? name : "Net Sentiment",
color: useGroupName ? color : undefined,
unit: Unit.usd,
}),
priceLine({
ctx,
unit: Unit.usd,
}),
]),
},
],
},
];
@@ -715,19 +918,26 @@ function createActivitySection(args, title) {
name: "Sent",
tree: [
{
name: "Sats",
title: title("Sent (Sats)"),
bottom: createGroupedSentSatsSeries(list),
name: "Sum",
title: title("Sent"),
bottom: list.flatMap(({ color, name, tree }) =>
satsBtcUsd({
pattern: {
sats: tree.activity.sent.sats.sum,
bitcoin: tree.activity.sent.bitcoin.sum,
dollars: tree.activity.sent.dollars.sum,
},
name,
color,
}),
),
},
{
name: "Bitcoin",
title: title("Sent (BTC)"),
bottom: createGroupedSentBitcoinSeries(list),
},
{
name: "Dollars",
title: title("Sent ($)"),
bottom: createGroupedSentDollarsSeries(list),
name: "14d EMA",
title: title("Sent 14d EMA"),
bottom: list.flatMap(({ color, name, tree }) =>
satsBtcUsd({ pattern: tree.activity.sent14dEma, name, color }),
),
},
],
},

View File

@@ -12,6 +12,7 @@ export {
createCohortFolderWithAdjusted,
createCohortFolderWithNupl,
createCohortFolderAgeRange,
createCohortFolderMinAge,
createCohortFolderBasicWithMarketCap,
createCohortFolderBasicWithoutMarketCap,
createCohortFolderWithoutRelative,

View File

@@ -125,6 +125,7 @@ function createSingleSupplySeriesBase(ctx, cohort) {
return [
...satsBtcUsd({ pattern: tree.supply.total, name: "Supply", color: colors.default }),
...satsBtcUsd({ pattern: tree.supply._30dChange, name: "30d Change", color: colors.orange }),
...satsBtcUsd({ pattern: tree.unrealized.supplyInProfit, name: "In Profit", color: colors.green }),
...satsBtcUsd({ pattern: tree.unrealized.supplyInLoss, name: "In Loss", color: colors.red }),
...satsBtcUsd({ pattern: tree.supply.halved, name: "half", color: colors.gray }).map((s) => ({
@@ -261,6 +262,13 @@ export function createGroupedSupplySection(list, title, { supplyRelativeMetrics,
title: title("Supply"),
bottom: createGroupedSupplyTotalSeries(list, { relativeMetrics: supplyRelativeMetrics }),
},
{
name: "30d Change",
title: title("Supply 30d Change"),
bottom: list.flatMap(({ color, name, tree }) =>
satsBtcUsd({ pattern: tree.supply._30dChange, name, color }),
),
},
{
name: "In Profit",
title: title("Supply In Profit"),
@@ -692,57 +700,10 @@ export function createSingleSentSeries(cohort) {
unit: Unit.usd,
defaultActive: false,
}),
...satsBtcUsd({ pattern: tree.activity.sent14dEma, name: "14d EMA" }),
];
}
/**
* Create sent (sats) series for grouped cohorts (comparison)
* @param {readonly CohortObject[]} list
* @returns {AnyFetchedSeriesBlueprint[]}
*/
export function createGroupedSentSatsSeries(list) {
return list.flatMap(({ color, name, tree }) => [
line({
metric: tree.activity.sent.sats.sum,
name,
color,
unit: Unit.sats,
}),
]);
}
/**
* Create sent (bitcoin) series for grouped cohorts (comparison)
* @param {readonly CohortObject[]} list
* @returns {AnyFetchedSeriesBlueprint[]}
*/
export function createGroupedSentBitcoinSeries(list) {
return list.flatMap(({ color, name, tree }) => [
line({
metric: tree.activity.sent.bitcoin.sum,
name,
color,
unit: Unit.btc,
}),
]);
}
/**
* Create sent (dollars) series for grouped cohorts (comparison)
* @param {readonly CohortObject[]} list
* @returns {AnyFetchedSeriesBlueprint[]}
*/
export function createGroupedSentDollarsSeries(list) {
return list.flatMap(({ color, name, tree }) => [
line({
metric: tree.activity.sent.dollars.sum,
name,
color,
unit: Unit.usd,
}),
]);
}
// ============================================================================
// Sell Side Risk Ratio Helpers
// ============================================================================
@@ -1036,11 +997,11 @@ export function createGroupedInvestorPriceFolder(list, title) {
}
// ============================================================================
// ATH Regret Helpers
// Peak Regret Helpers
// ============================================================================
/**
* Create realized ATH regret series for single cohort
* Create realized peak regret series for single cohort
* @param {{ realized: AnyRealizedPattern }} tree
* @param {Color} color
* @returns {AnyFetchedSeriesBlueprint[]}
@@ -1048,34 +1009,23 @@ export function createGroupedInvestorPriceFolder(list, title) {
export function createSingleRealizedAthRegretSeries(tree, color) {
return [
line({
metric: tree.realized.athRegret.sum,
name: "ATH Regret",
metric: tree.realized.peakRegret.sum,
name: "Peak Regret",
color,
unit: Unit.usd,
}),
line({
metric: tree.realized.athRegret.cumulative,
metric: tree.realized.peakRegret.cumulative,
name: "Cumulative",
color,
unit: Unit.usd,
defaultActive: false,
}),
];
}
/**
* Create unrealized ATH regret series for single cohort
* @param {{ unrealized: UnrealizedPattern }} tree
* @param {Color} color
* @returns {AnyFetchedSeriesBlueprint[]}
*/
export function createSingleUnrealizedAthRegretSeries(tree, color) {
return [
line({
metric: tree.unrealized.athRegret,
name: "ATH Regret",
baseline({
metric: tree.realized.peakRegretRelToRealizedCap,
name: "Rel. to Realized Cap",
color,
unit: Unit.usd,
unit: Unit.pctRcap,
}),
];
}
@@ -1088,26 +1038,16 @@ export function createSingleUnrealizedAthRegretSeries(tree, color) {
export function createGroupedRealizedAthRegretSeries(list) {
return list.flatMap(({ color, name, tree }) => [
line({
metric: tree.realized.athRegret.sum,
metric: tree.realized.peakRegret.sum,
name,
color,
unit: Unit.usd,
}),
]);
}
/**
* Create unrealized ATH regret series for grouped cohorts
* @param {readonly { color: Color, name: string, tree: { unrealized: UnrealizedPattern } }[]} list
* @returns {AnyFetchedSeriesBlueprint[]}
*/
export function createGroupedUnrealizedAthRegretSeries(list) {
return list.flatMap(({ color, name, tree }) => [
line({
metric: tree.unrealized.athRegret,
baseline({
metric: tree.realized.peakRegretRelToRealizedCap,
name,
color,
unit: Unit.usd,
unit: Unit.pctRcap,
}),
]);
}

View File

@@ -46,9 +46,7 @@ import {
createInvestorPriceFolderBasic,
createGroupedInvestorPriceFolder,
createSingleRealizedAthRegretSeries,
createSingleUnrealizedAthRegretSeries,
createGroupedRealizedAthRegretSeries,
createGroupedUnrealizedAthRegretSeries,
createSingleSentimentSeries,
createGroupedNetSentimentSeries,
createGroupedGreedIndexSeries,
@@ -57,6 +55,7 @@ import {
import {
createPriceRatioCharts,
formatCohortTitle,
satsBtcUsd,
} from "../shared.js";
import { Unit } from "../../utils/units.js";
import { line, baseline, price } from "../series.js";
@@ -67,7 +66,7 @@ import { priceLine } from "../constants.js";
// ============================================================================
/**
* All folder: for the special "All" cohort (adjustedSopr + percentiles but no RelToMarketCap)
* All folder: for the special "All" cohort (adjustedSopr + percentiles + RelToMarketCap)
* @param {PartialContext} ctx
* @param {CohortAll} args
* @returns {PartialOptionsGroup}
@@ -266,7 +265,52 @@ export function createCohortFolderAgeRange(ctx, args) {
}
/**
* Basic folder WITH RelToMarketCap (minAge.*, geAmount.*, ltAmount.*, type.*)
* MinAge folder - has peakRegret in unrealized (minAge.*)
* @param {PartialContext} ctx
* @param {CohortMinAge | CohortGroupMinAge} args
* @returns {PartialOptionsGroup}
*/
export function createCohortFolderMinAge(ctx, args) {
if ("list" in args) {
const { list } = args;
const title = formatCohortTitle(args.title);
return {
name: args.name || "all",
tree: [
createGroupedSupplySection(
list,
title,
groupedSupplyRelativeGenerators,
),
createGroupedUtxoCountChart(list, title),
createGroupedRealizedSectionBasic(ctx, list, title),
createGroupedUnrealizedSectionMinAge(ctx, list, title),
createGroupedCostBasisSection({ list, title }),
createGroupedActivitySection({ list, title }),
],
};
}
const title = formatCohortTitle(args.name);
return {
name: args.name || "all",
tree: [
createSingleSupplyChart(
ctx,
args,
title,
createSingleSupplyRelativeOptions(ctx, args),
),
createSingleUtxoCountChart(args, title),
createSingleRealizedSectionBasic(ctx, args, title),
createSingleUnrealizedSectionMinAge(ctx, args, title),
createCostBasisSection(ctx, { cohort: args, title }),
createActivitySection({ ctx, cohort: args, title }),
],
};
}
/**
* Basic folder WITH RelToMarketCap (geAmount.*, ltAmount.*)
* @param {PartialContext} ctx
* @param {CohortBasicWithMarketCap | CohortGroupBasicWithMarketCap} args
* @returns {PartialOptionsGroup}
@@ -546,8 +590,8 @@ function createSingleRealizedSectionFull(ctx, cohort, title) {
extra: createRealizedPnlRatioSeries(colors, tree),
}),
{
name: "ATH Regret",
title: title("Realized ATH Regret"),
name: "Peak Regret",
title: title("Realized Peak Regret"),
bottom: createSingleRealizedAthRegretSeries(tree, color),
},
createSingleSoprSectionWithAdjusted(ctx, cohort, title),
@@ -576,8 +620,8 @@ function createSingleRealizedSectionWithAdjusted(ctx, cohort, title) {
},
...createSingleRealizedPnlSection(ctx, cohort, title),
{
name: "ATH Regret",
title: title("Realized ATH Regret"),
name: "Peak Regret",
title: title("Realized Peak Regret"),
bottom: createSingleRealizedAthRegretSeries(tree, color),
},
createSingleSoprSectionWithAdjusted(ctx, cohort, title),
@@ -622,8 +666,8 @@ function createGroupedRealizedSectionWithAdjusted(
},
...createGroupedRealizedPnlSections(ctx, list, title, { ratioMetrics }),
{
name: "ATH Regret",
title: title("Realized ATH Regret"),
name: "Peak Regret",
title: title("Realized Peak Regret"),
bottom: createGroupedRealizedAthRegretSeries(list),
},
createGroupedSoprSectionWithAdjusted(list, title),
@@ -657,8 +701,8 @@ function createSingleRealizedSectionWithPercentiles(ctx, cohort, title) {
extra: createRealizedPnlRatioSeries(colors, tree),
}),
{
name: "ATH Regret",
title: title("Realized ATH Regret"),
name: "Peak Regret",
title: title("Realized Peak Regret"),
bottom: createSingleRealizedAthRegretSeries(tree, color),
},
createSingleSoprSectionBasic(ctx, cohort, title),
@@ -687,8 +731,8 @@ function createSingleRealizedSectionBasic(ctx, cohort, title) {
},
...createSingleRealizedPnlSection(ctx, cohort, title),
{
name: "ATH Regret",
title: title("Realized ATH Regret"),
name: "Peak Regret",
title: title("Realized Peak Regret"),
bottom: createSingleRealizedAthRegretSeries(tree, color),
},
createSingleSoprSectionBasic(ctx, cohort, title),
@@ -733,8 +777,8 @@ function createGroupedRealizedSectionBasic(
},
...createGroupedRealizedPnlSections(ctx, list, title, { ratioMetrics }),
{
name: "ATH Regret",
title: title("Realized ATH Regret"),
name: "Peak Regret",
title: title("Realized Peak Regret"),
bottom: createGroupedRealizedAthRegretSeries(list),
},
createGroupedSoprSectionBasic(list, title),
@@ -953,12 +997,24 @@ function createSingleRealizedPnlSection(
title: "Profit",
sumColor: colors.green,
}),
line({
metric: tree.realized.realizedProfit7dEma,
name: "Profit 7d EMA",
color: colors.green,
unit: Unit.usd,
}),
...fromCountPattern({
pattern: tree.realized.realizedLoss,
unit: Unit.usd,
title: "Loss",
sumColor: colors.red,
}),
line({
metric: tree.realized.realizedLoss7dEma,
name: "Loss 7d EMA",
color: colors.red,
unit: Unit.usd,
}),
...fromBitcoinPatternWithUnit({
pattern: tree.realized.negRealizedLoss,
unit: Unit.usd,
@@ -1013,6 +1069,11 @@ function createSingleRealizedPnlSection(
unit: Unit.usd,
title: "Net",
}),
baseline({
metric: tree.realized.netRealizedPnl7dEma,
name: "Net 7d EMA",
unit: Unit.usd,
}),
baseline({
metric: tree.realized.netRealizedPnlCumulative30dDelta,
name: "Cumulative 30d Change",
@@ -1047,6 +1108,45 @@ function createSingleRealizedPnlSection(
priceLine({ ctx, unit: Unit.usd }),
],
},
{
name: "Sent In P/L",
tree: [
{
name: "In Profit",
title: title("Sent In Profit"),
bottom: [
line({ metric: tree.realized.sentInProfit.bitcoin.sum, name: "Sum", color: colors.green, unit: Unit.btc }),
line({ metric: tree.realized.sentInProfit.bitcoin.cumulative, name: "Cumulative", color: colors.green, unit: Unit.btc, defaultActive: false }),
line({ metric: tree.realized.sentInProfit.sats.sum, name: "Sum", color: colors.green, unit: Unit.sats }),
line({ metric: tree.realized.sentInProfit.sats.cumulative, name: "Cumulative", color: colors.green, unit: Unit.sats, defaultActive: false }),
line({ metric: tree.realized.sentInProfit.dollars.sum, name: "Sum", color: colors.green, unit: Unit.usd }),
line({ metric: tree.realized.sentInProfit.dollars.cumulative, name: "Cumulative", color: colors.green, unit: Unit.usd, defaultActive: false }),
],
},
{
name: "In Loss",
title: title("Sent In Loss"),
bottom: [
line({ metric: tree.realized.sentInLoss.bitcoin.sum, name: "Sum", color: colors.red, unit: Unit.btc }),
line({ metric: tree.realized.sentInLoss.bitcoin.cumulative, name: "Cumulative", color: colors.red, unit: Unit.btc, defaultActive: false }),
line({ metric: tree.realized.sentInLoss.sats.sum, name: "Sum", color: colors.red, unit: Unit.sats }),
line({ metric: tree.realized.sentInLoss.sats.cumulative, name: "Cumulative", color: colors.red, unit: Unit.sats, defaultActive: false }),
line({ metric: tree.realized.sentInLoss.dollars.sum, name: "Sum", color: colors.red, unit: Unit.usd }),
line({ metric: tree.realized.sentInLoss.dollars.cumulative, name: "Cumulative", color: colors.red, unit: Unit.usd, defaultActive: false }),
],
},
{
name: "In Profit 14d EMA",
title: title("Sent In Profit 14d EMA"),
bottom: satsBtcUsd({ pattern: tree.realized.sentInProfit14dEma, name: "14d EMA", color: colors.green }),
},
{
name: "In Loss 14d EMA",
title: title("Sent In Loss 14d EMA"),
bottom: satsBtcUsd({ pattern: tree.realized.sentInLoss14dEma, name: "14d EMA", color: colors.red }),
},
],
},
];
}
@@ -1066,85 +1166,46 @@ function createGroupedRealizedPnlSections(
title,
{ ratioMetrics } = {},
) {
const pnlConfigs = /** @type {const} */ ([
{ name: "Profit", sum: "realizedProfit", ema: "realizedProfit7dEma", rel: "realizedProfitRelToRealizedCap", isNet: false },
{ name: "Loss", sum: "realizedLoss", ema: "realizedLoss7dEma", rel: "realizedLossRelToRealizedCap", isNet: false },
{ name: "Net P&L", sum: "netRealizedPnl", ema: "netRealizedPnl7dEma", rel: "netRealizedPnlRelToRealizedCap", isNet: true },
]);
return [
{
name: "Profit",
title: title("Realized Profit"),
bottom: [
...list.flatMap(({ color, name, tree }) => [
line({
metric: tree.realized.realizedProfit.sum,
name,
color,
unit: Unit.usd,
}),
baseline({
metric: tree.realized.realizedProfitRelToRealizedCap.sum,
name,
color,
unit: Unit.pctRcap,
}),
]),
priceLine({ ctx, unit: Unit.usd }),
...pnlConfigs.map(({ name, sum, ema, rel, isNet }) => ({
name,
tree: [
{
name: "Sum",
title: title(`Realized ${name}`),
bottom: [
...list.flatMap(({ color, name, tree }) => [
(isNet ? baseline : line)({ metric: tree.realized[sum].sum, name, color, unit: Unit.usd }),
baseline({ metric: tree.realized[rel].sum, name, color, unit: Unit.pctRcap }),
]),
priceLine({ ctx, unit: Unit.usd }),
],
},
{
name: "7d EMA",
title: title(`Realized ${name} 7d EMA`),
bottom: [
...list.map(({ color, name, tree }) =>
(isNet ? baseline : line)({ metric: tree.realized[ema], name, color, unit: Unit.usd }),
),
priceLine({ ctx, unit: Unit.usd }),
],
},
],
},
{
name: "Loss",
title: title("Realized Loss"),
bottom: [
...list.flatMap(({ color, name, tree }) => [
line({
metric: tree.realized.realizedLoss.sum,
name,
color,
unit: Unit.usd,
}),
baseline({
metric: tree.realized.realizedLossRelToRealizedCap.sum,
name,
color,
unit: Unit.pctRcap,
}),
]),
priceLine({ ctx, unit: Unit.usd }),
],
},
})),
{
name: "Total P&L",
title: title("Total Realized P&L"),
bottom: [
...list.flatMap((cohort) => [
line({
metric: cohort.tree.realized.totalRealizedPnl,
name: cohort.name,
color: cohort.color,
unit: Unit.usd,
}),
...(ratioMetrics ? ratioMetrics(cohort) : []),
]),
],
},
{
name: "Net P&L",
title: title("Net Realized P&L"),
bottom: [
...list.flatMap(({ color, name, tree }) => [
baseline({
metric: tree.realized.netRealizedPnl.sum,
name,
color,
unit: Unit.usd,
}),
baseline({
metric: tree.realized.netRealizedPnlRelToRealizedCap.sum,
name,
color,
unit: Unit.pctRcap,
}),
]),
priceLine({ ctx, unit: Unit.usd }),
priceLine({ ctx, unit: Unit.pctRcap }),
],
bottom: list.flatMap((cohort) => [
line({ metric: cohort.tree.realized.totalRealizedPnl, name: cohort.name, color: cohort.color, unit: Unit.usd }),
...(ratioMetrics ? ratioMetrics(cohort) : []),
]),
},
{
name: "Cumulative",
@@ -1222,6 +1283,61 @@ function createGroupedRealizedPnlSections(
},
],
},
{
name: "Sent In P/L",
tree: [
{
name: "In Profit",
title: title("Sent In Profit"),
bottom: list.flatMap(({ color, name, tree }) => [
line({ metric: tree.realized.sentInProfit.bitcoin.sum, name, color, unit: Unit.btc }),
line({ metric: tree.realized.sentInProfit.sats.sum, name, color, unit: Unit.sats }),
line({ metric: tree.realized.sentInProfit.dollars.sum, name, color, unit: Unit.usd }),
]),
},
{
name: "In Profit Cumulative",
title: title("Sent In Profit Cumulative"),
bottom: list.flatMap(({ color, name, tree }) => [
line({ metric: tree.realized.sentInProfit.bitcoin.cumulative, name, color, unit: Unit.btc }),
line({ metric: tree.realized.sentInProfit.sats.cumulative, name, color, unit: Unit.sats }),
line({ metric: tree.realized.sentInProfit.dollars.cumulative, name, color, unit: Unit.usd }),
]),
},
{
name: "In Loss",
title: title("Sent In Loss"),
bottom: list.flatMap(({ color, name, tree }) => [
line({ metric: tree.realized.sentInLoss.bitcoin.sum, name, color, unit: Unit.btc }),
line({ metric: tree.realized.sentInLoss.sats.sum, name, color, unit: Unit.sats }),
line({ metric: tree.realized.sentInLoss.dollars.sum, name, color, unit: Unit.usd }),
]),
},
{
name: "In Loss Cumulative",
title: title("Sent In Loss Cumulative"),
bottom: list.flatMap(({ color, name, tree }) => [
line({ metric: tree.realized.sentInLoss.bitcoin.cumulative, name, color, unit: Unit.btc }),
line({ metric: tree.realized.sentInLoss.sats.cumulative, name, color, unit: Unit.sats }),
line({ metric: tree.realized.sentInLoss.dollars.cumulative, name, color, unit: Unit.usd }),
]),
},
{
name: "In Profit 14d EMA",
title: title("Sent In Profit 14d EMA"),
bottom: list.flatMap(({ color, name, tree }) =>
satsBtcUsd({ pattern: tree.realized.sentInProfit14dEma, name, color }),
),
},
{
name: "In Loss 14d EMA",
title: title("Sent In Loss 14d EMA"),
bottom: list.flatMap(({ color, name, tree }) =>
satsBtcUsd({ pattern: tree.realized.sentInLoss14dEma, name, color }),
),
},
],
},
];
}
@@ -1670,6 +1786,56 @@ function createNuplChart(ctx, rel, title) {
};
}
/**
* Create peak regret chart (basic - just absolute value)
* @param {PartialContext} ctx
* @param {{ unrealized: UnrealizedFullPattern }} tree
* @param {(metric: string) => string} title
* @returns {PartialChartOption}
*/
function createPeakRegretChart(ctx, tree, title) {
return {
name: "Peak Regret",
title: title("Unrealized Peak Regret"),
bottom: [
line({
metric: tree.unrealized.peakRegret,
name: "Peak Regret",
color: ctx.colors.orange,
unit: Unit.usd,
}),
],
};
}
/**
* Create peak regret chart with RelToMarketCap metric
* @param {PartialContext} ctx
* @param {{ unrealized: UnrealizedFullPattern, relative: RelativeWithMarketCap }} tree
* @param {(metric: string) => string} title
* @returns {PartialChartOption}
*/
function createPeakRegretChartWithMarketCap(ctx, tree, title) {
return {
name: "Peak Regret",
title: title("Unrealized Peak Regret"),
bottom: [
line({
metric: tree.unrealized.peakRegret,
name: "Peak Regret",
color: ctx.colors.orange,
unit: Unit.usd,
}),
baseline({
metric: tree.relative.unrealizedPeakRegretRelToMarketCap,
name: "Peak Regret",
color: ctx.colors.orange,
unit: Unit.pctMcap,
}),
],
};
}
/**
* Create invested capital absolute chart
* @param {PartialContext} ctx
@@ -1773,6 +1939,54 @@ function createGroupedNuplChart(ctx, list, title) {
};
}
/**
* Create grouped peak regret chart (basic - no RelToMarketCap)
* @param {readonly { color: Color, name: string, tree: { unrealized: UnrealizedFullPattern } }[]} list
* @param {(metric: string) => string} title
* @returns {PartialChartOption}
*/
function createGroupedPeakRegretChartBasic(list, title) {
return {
name: "Peak Regret",
title: title("Unrealized Peak Regret"),
bottom: list.flatMap(({ color, name, tree }) => [
line({
metric: tree.unrealized.peakRegret,
name,
color,
unit: Unit.usd,
}),
]),
};
}
/**
* Create grouped peak regret chart with RelToMarketCap metric
* @param {readonly { color: Color, name: string, tree: { unrealized: UnrealizedFullPattern, relative: RelativeWithMarketCap } }[]} list
* @param {(metric: string) => string} title
* @returns {PartialChartOption}
*/
function createGroupedPeakRegretChart(list, title) {
return {
name: "Peak Regret",
title: title("Unrealized Peak Regret"),
bottom: list.flatMap(({ color, name, tree }) => [
line({
metric: tree.unrealized.peakRegret,
name,
color,
unit: Unit.usd,
}),
baseline({
metric: tree.relative.unrealizedPeakRegretRelToMarketCap,
name,
color,
unit: Unit.pctMcap,
}),
]),
};
}
// ============================================================================
// Unrealized Section Builder (generic, type-safe composition)
// ============================================================================
@@ -1826,11 +2040,6 @@ function createUnrealizedSection({
title: title("Market Sentiment"),
bottom: createSingleSentimentSeries(colors, tree),
},
{
name: "ATH Regret",
title: title("Unrealized ATH Regret"),
bottom: createSingleUnrealizedAthRegretSeries(tree, colors.orange),
},
...charts,
],
};
@@ -1968,11 +2177,6 @@ function createGroupedUnrealizedSection({
},
],
},
{
name: "ATH Regret",
title: title("Unrealized ATH Regret"),
bottom: createGroupedUnrealizedAthRegretSeries(list),
},
...charts,
],
};
@@ -2025,11 +2229,6 @@ function createGroupedUnrealizedSectionWithoutRelative(list, title) {
},
],
},
{
name: "ATH Regret",
title: title("Unrealized ATH Regret"),
bottom: createGroupedUnrealizedAthRegretSeries(list),
},
],
};
}
@@ -2050,9 +2249,21 @@ function createSingleUnrealizedSectionAll(ctx, cohort, title) {
ctx,
tree,
title,
pnl: createUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative),
netPnl: createNetUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative),
pnl: [
...createUnrealizedPnlRelToMarketCapMetrics(ctx, tree.relative),
...createUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative),
priceLine({ ctx, unit: Unit.pctMcap, defaultActive: false }),
],
netPnl: [
...createNetUnrealizedPnlRelToMarketCapMetrics(tree.relative),
...createNetUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative),
priceLine({ ctx, unit: Unit.pctMcap }),
],
investedCapitalFolder: createSingleInvestedCapitalFolderFull(ctx, tree, title),
charts: [
createNuplChart(ctx, tree.relative, title),
createPeakRegretChartWithMarketCap(ctx, tree, title),
],
});
}
@@ -2081,7 +2292,10 @@ function createSingleUnrealizedSectionFull(ctx, cohort, title) {
priceLine({ ctx, unit: Unit.pctMcap }),
],
investedCapitalFolder: createSingleInvestedCapitalFolderFull(ctx, tree, title),
charts: [createNuplChart(ctx, tree.relative, title)],
charts: [
createNuplChart(ctx, tree.relative, title),
createPeakRegretChartWithMarketCap(ctx, tree, title),
],
});
}
@@ -2106,7 +2320,10 @@ function createSingleUnrealizedSectionWithMarketCap(ctx, cohort, title) {
priceLine({ ctx, unit: Unit.pctMcap }),
],
investedCapitalFolder: createSingleInvestedCapitalFolderFull(ctx, tree, title),
charts: [createNuplChart(ctx, tree.relative, title)],
charts: [
createNuplChart(ctx, tree.relative, title),
createPeakRegretChartWithMarketCap(ctx, tree, title),
],
});
}
@@ -2135,6 +2352,34 @@ function createSingleUnrealizedSectionWithMarketCapOnly(ctx, cohort, title) {
});
}
/**
* Unrealized section for minAge cohorts (has peakRegret)
* @param {PartialContext} ctx
* @param {CohortMinAge} cohort
* @param {(metric: string) => string} title
*/
function createSingleUnrealizedSectionMinAge(ctx, cohort, title) {
const { tree } = cohort;
return createUnrealizedSection({
ctx,
tree,
title,
pnl: [
...createUnrealizedPnlRelToMarketCapMetrics(ctx, tree.relative),
priceLine({ ctx, unit: Unit.pctMcap, defaultActive: false }),
],
netPnl: [
...createNetUnrealizedPnlRelToMarketCapMetrics(tree.relative),
priceLine({ ctx, unit: Unit.pctMcap }),
],
investedCapitalFolder: createSingleInvestedCapitalFolderFull(ctx, tree, title),
charts: [
createNuplChart(ctx, tree.relative, title),
createPeakRegretChartWithMarketCap(ctx, tree, title),
],
});
}
/**
* Unrealized section with only base metrics (no RelToMarketCap)
* @param {PartialContext} ctx
@@ -2169,7 +2414,7 @@ function createSingleUnrealizedSectionWithoutRelative(ctx, cohort, title) {
/**
* Grouped unrealized base charts (profit, loss, total pnl)
* @param {readonly { color: Color, name: string, tree: { unrealized: PatternAll["unrealized"] } }[]} list
* @param {readonly { color: Color, name: string, tree: { unrealized: UnrealizedPattern } }[]} list
* @param {(metric: string) => string} title
*/
function createGroupedUnrealizedBaseCharts(list, title) {
@@ -2243,7 +2488,10 @@ function createGroupedUnrealizedSectionFull(ctx, list, title) {
unit: Unit.pctOwnPnl,
}),
],
charts: [createGroupedNuplChart(ctx, list, title)],
charts: [
createGroupedNuplChart(ctx, list, title),
createGroupedPeakRegretChart(list, title),
],
});
}
@@ -2265,7 +2513,10 @@ function createGroupedUnrealizedSectionWithMarketCap(ctx, list, title) {
unit: Unit.pctMcap,
}),
],
charts: [createGroupedNuplChart(ctx, list, title)],
charts: [
createGroupedNuplChart(ctx, list, title),
createGroupedPeakRegretChart(list, title),
],
});
}
@@ -2291,6 +2542,31 @@ function createGroupedUnrealizedSectionWithMarketCapOnly(ctx, list, title) {
});
}
/**
* Grouped unrealized section for minAge cohorts (has peakRegret)
* @param {PartialContext} ctx
* @param {readonly CohortMinAge[]} list
* @param {(metric: string) => string} title
*/
function createGroupedUnrealizedSectionMinAge(ctx, list, title) {
return createGroupedUnrealizedSection({
list,
title,
netPnlMetrics: ({ color, name, tree }) => [
baseline({
metric: tree.relative.netUnrealizedPnlRelToMarketCap,
name,
color,
unit: Unit.pctMcap,
}),
],
charts: [
createGroupedNuplChart(ctx, list, title),
createGroupedPeakRegretChart(list, title),
],
});
}
/**
* Grouped unrealized section without RelToMarketCap (for CohortBasicWithoutMarketCap)
* @param {readonly CohortBasicWithoutMarketCap[]} list
@@ -2324,7 +2600,10 @@ function createSingleUnrealizedSectionWithNupl({ ctx, cohort, title }) {
...createNetUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative),
],
investedCapitalFolder: createSingleInvestedCapitalFolderFull(ctx, tree, title),
charts: [createNuplChart(ctx, tree.relative, title)],
charts: [
createNuplChart(ctx, tree.relative, title),
createPeakRegretChartWithMarketCap(ctx, tree, title),
],
});
}
@@ -2359,7 +2638,10 @@ function createGroupedUnrealizedSectionWithNupl({ ctx, list, title }) {
unit: Unit.pctOwnPnl,
}),
],
charts: [createGroupedNuplChart(ctx, list, title)],
charts: [
createGroupedNuplChart(ctx, list, title),
createGroupedPeakRegretChart(list, title),
],
});
}
@@ -2384,6 +2666,7 @@ function createSingleUnrealizedSectionAgeRange(ctx, cohort, title) {
...createNetUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative),
],
investedCapitalFolder: createSingleInvestedCapitalFolderFull(ctx, tree, title),
charts: [createPeakRegretChart(ctx, tree, title)],
});
}
@@ -2410,6 +2693,7 @@ function createGroupedUnrealizedSectionAgeRange(list, title) {
unit: Unit.pctOwnPnl,
}),
],
charts: [createGroupedPeakRegretChartBasic(list, title)],
});
}
@@ -2632,6 +2916,21 @@ function createActivitySection({ ctx, cohort, title, valueMetrics = [] }) {
unit: Unit.usd,
sumColor: color,
}),
line({
metric: tree.activity.sent14dEma.sats,
name: "14d EMA",
unit: Unit.sats,
}),
line({
metric: tree.activity.sent14dEma.bitcoin,
name: "14d EMA",
unit: Unit.btc,
}),
line({
metric: tree.activity.sent14dEma.dollars,
name: "14d EMA",
unit: Unit.usd,
}),
],
},
{
@@ -2768,6 +3067,33 @@ function createGroupedActivitySection({ list, title, valueTree }) {
return {
name: "Activity",
tree: [
{
name: "Sent",
tree: [
{
name: "Sum",
title: title("Sent"),
bottom: list.flatMap(({ color, name, tree }) =>
satsBtcUsd({
pattern: {
sats: tree.activity.sent.sats.sum,
bitcoin: tree.activity.sent.bitcoin.sum,
dollars: tree.activity.sent.dollars.sum,
},
name,
color,
}),
),
},
{
name: "14d EMA",
title: title("Sent 14d EMA"),
bottom: list.flatMap(({ color, name, tree }) =>
satsBtcUsd({ pattern: tree.activity.sent14dEma, name, color }),
),
},
],
},
{
name: "Sell Side Risk",
title: title("Sell Side Risk Ratio"),