mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-24 14:49:58 -07:00
global: snapshot
This commit is contained in:
@@ -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 }),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -12,6 +12,7 @@ export {
|
||||
createCohortFolderWithAdjusted,
|
||||
createCohortFolderWithNupl,
|
||||
createCohortFolderAgeRange,
|
||||
createCohortFolderMinAge,
|
||||
createCohortFolderBasicWithMarketCap,
|
||||
createCohortFolderBasicWithoutMarketCap,
|
||||
createCohortFolderWithoutRelative,
|
||||
|
||||
@@ -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,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -3,7 +3,8 @@ import { createButtonElement, createAnchorElement } from "../utils/dom.js";
|
||||
import { pushHistory, resetParams } from "../utils/url.js";
|
||||
import { readStored, writeToStorage } from "../utils/storage.js";
|
||||
import { stringToId } from "../utils/format.js";
|
||||
import { collect, markUsed, logUnused } from "./unused.js";
|
||||
import { collect, markUsed, logUnused, extractTreeStructure } from "./unused.js";
|
||||
import { localhost } from "../utils/env.js";
|
||||
import { setQr } from "../panes/share.js";
|
||||
import { getConstant } from "./constants.js";
|
||||
import { colors } from "../chart/colors.js";
|
||||
@@ -29,6 +30,11 @@ export function initOptions(brk) {
|
||||
brk,
|
||||
});
|
||||
|
||||
// Log tree structure for analysis (localhost only)
|
||||
if (localhost) {
|
||||
console.log(extractTreeStructure(partialOptions));
|
||||
}
|
||||
|
||||
/** @type {Option[]} */
|
||||
const list = [];
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
createCohortFolderWithAdjusted,
|
||||
createCohortFolderWithNupl,
|
||||
createCohortFolderAgeRange,
|
||||
createCohortFolderMinAge,
|
||||
createCohortFolderBasicWithMarketCap,
|
||||
createCohortFolderBasicWithoutMarketCap,
|
||||
createCohortFolderWithoutRelative,
|
||||
@@ -62,6 +63,8 @@ export function createPartialOptions({ brk }) {
|
||||
/** @param {CohortBasicWithMarketCap} cohort */
|
||||
const mapBasicWithMarketCap = (cohort) =>
|
||||
createCohortFolderBasicWithMarketCap(ctx, cohort);
|
||||
/** @param {CohortMinAge} cohort */
|
||||
const mapMinAge = (cohort) => createCohortFolderMinAge(ctx, cohort);
|
||||
/** @param {CohortBasicWithoutMarketCap} cohort */
|
||||
const mapBasicWithoutMarketCap = (cohort) =>
|
||||
createCohortFolderBasicWithoutMarketCap(ctx, cohort);
|
||||
@@ -135,12 +138,12 @@ export function createPartialOptions({ brk }) {
|
||||
{
|
||||
name: "Older Than",
|
||||
tree: [
|
||||
createCohortFolderBasicWithMarketCap(ctx, {
|
||||
createCohortFolderMinAge(ctx, {
|
||||
name: "Compare",
|
||||
title: "Min Age",
|
||||
list: fromDate,
|
||||
}),
|
||||
...fromDate.map(mapBasicWithMarketCap),
|
||||
...fromDate.map(mapMinAge),
|
||||
],
|
||||
},
|
||||
// Range
|
||||
|
||||
@@ -214,13 +214,20 @@
|
||||
* @property {Color} color
|
||||
* @property {AgeRangePattern} tree
|
||||
*
|
||||
* Basic cohort WITH RelToMarketCap (minAge.*, geAmount.*, ltAmount.*)
|
||||
* Basic cohort WITH RelToMarketCap (geAmount.*, ltAmount.*)
|
||||
* @typedef {Object} CohortBasicWithMarketCap
|
||||
* @property {string} name
|
||||
* @property {string} title
|
||||
* @property {Color} color
|
||||
* @property {PatternBasicWithMarketCap} tree
|
||||
*
|
||||
* MinAge cohort - has peakRegret in unrealized (minAge.*)
|
||||
* @typedef {Object} CohortMinAge
|
||||
* @property {string} name
|
||||
* @property {string} title
|
||||
* @property {Color} color
|
||||
* @property {MinAgePattern} tree
|
||||
*
|
||||
* Basic cohort WITHOUT RelToMarketCap (epoch.*, amountRange.*, year.*, type.*)
|
||||
* @typedef {Object} CohortBasicWithoutMarketCap
|
||||
* @property {string} name
|
||||
@@ -279,6 +286,11 @@
|
||||
* @property {string} title
|
||||
* @property {readonly CohortBasicWithMarketCap[]} list
|
||||
*
|
||||
* @typedef {Object} CohortGroupMinAge
|
||||
* @property {string} name
|
||||
* @property {string} title
|
||||
* @property {readonly CohortMinAge[]} list
|
||||
*
|
||||
* @typedef {Object} CohortGroupBasicWithoutMarketCap
|
||||
* @property {string} name
|
||||
* @property {string} title
|
||||
|
||||
@@ -52,16 +52,6 @@ function walk(node, map, path) {
|
||||
(kn.startsWith("_") && kn.endsWith("start"))
|
||||
)
|
||||
continue;
|
||||
// if (
|
||||
// kn === "mvrv" ||
|
||||
// kn.endsWith("index") ||
|
||||
// kn.endsWith("indexes") ||
|
||||
// kn.endsWith("start") ||
|
||||
// kn.endsWith("hash") ||
|
||||
// kn.endsWith("data") ||
|
||||
// kn.endsWith("constants")
|
||||
// )
|
||||
// return;
|
||||
walk(/** @type {TreeNode | null | undefined} */ (value), map, [
|
||||
...path,
|
||||
key,
|
||||
@@ -108,3 +98,82 @@ export function logUnused() {
|
||||
|
||||
console.log("Unused metrics:", { count: unused.size, tree });
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract tree structure from partial options (names + hierarchy, series grouped by unit)
|
||||
* @param {PartialOptionsTree} options
|
||||
* @returns {object[]}
|
||||
*/
|
||||
export function extractTreeStructure(options) {
|
||||
/**
|
||||
* Group series by unit
|
||||
* @param {(AnyFetchedSeriesBlueprint | FetchedPriceSeriesBlueprint)[]} series
|
||||
* @param {boolean} isTop
|
||||
* @returns {Record<string, string[]>}
|
||||
*/
|
||||
function groupByUnit(series, isTop) {
|
||||
/** @type {Record<string, string[]>} */
|
||||
const grouped = {};
|
||||
for (const s of series) {
|
||||
// Price patterns in top pane have dollars/sats sub-metrics
|
||||
const metric = /** @type {any} */ (s.metric);
|
||||
if (isTop && metric?.dollars && metric?.sats) {
|
||||
const title = s.title || s.key || "unnamed";
|
||||
(grouped["USD"] ??= []).push(title);
|
||||
(grouped["sats"] ??= []).push(title);
|
||||
} else {
|
||||
const unit = /** @type {AnyFetchedSeriesBlueprint} */ (s).unit;
|
||||
const unitName = unit?.name || "unknown";
|
||||
const title = s.title || s.key || "unnamed";
|
||||
(grouped[unitName] ??= []).push(title);
|
||||
}
|
||||
}
|
||||
return grouped;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AnyPartialOption | PartialOptionsGroup} node
|
||||
* @returns {object}
|
||||
*/
|
||||
function processNode(node) {
|
||||
// Group with children
|
||||
if ("tree" in node && node.tree) {
|
||||
return {
|
||||
name: node.name,
|
||||
children: node.tree.map(processNode),
|
||||
};
|
||||
}
|
||||
// Chart option
|
||||
if ("top" in node || "bottom" in node) {
|
||||
const chartNode = /** @type {PartialChartOption} */ (node);
|
||||
const top = chartNode.top ? groupByUnit(chartNode.top, true) : undefined;
|
||||
const bottom = chartNode.bottom
|
||||
? groupByUnit(chartNode.bottom, false)
|
||||
: undefined;
|
||||
return {
|
||||
name: node.name,
|
||||
title: chartNode.title,
|
||||
...(top && Object.keys(top).length > 0 ? { top } : {}),
|
||||
...(bottom && Object.keys(bottom).length > 0 ? { bottom } : {}),
|
||||
};
|
||||
}
|
||||
// URL option
|
||||
if ("url" in node) {
|
||||
return { name: node.name, url: true };
|
||||
}
|
||||
// Other options (explorer, table, simulation)
|
||||
return { name: node.name };
|
||||
}
|
||||
|
||||
return options.map(processNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log the options tree structure to console (localhost only)
|
||||
* @param {PartialOptionsTree} options
|
||||
*/
|
||||
export function logTreeStructure(options) {
|
||||
if (!localhost) return;
|
||||
const structure = extractTreeStructure(options);
|
||||
console.log("Options tree structure:", JSON.stringify(structure, null, 2));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user