mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-24 14:49:58 -07:00
website: snapshot
This commit is contained in:
@@ -16,6 +16,15 @@ import {
|
||||
createAddressCountSeries,
|
||||
createRealizedPriceSeries,
|
||||
createRealizedPriceRatioSeries,
|
||||
createSingleCoinsDestroyedSeries,
|
||||
createGroupedCoinblocksDestroyedSeries,
|
||||
createGroupedCoindaysDestroyedSeries,
|
||||
createGroupedSatblocksDestroyedSeries,
|
||||
createGroupedSatdaysDestroyedSeries,
|
||||
createSingleSentSeries,
|
||||
createGroupedSentSatsSeries,
|
||||
createGroupedSentBitcoinSeries,
|
||||
createGroupedSentDollarsSeries,
|
||||
} from "./shared.js";
|
||||
|
||||
/**
|
||||
@@ -123,7 +132,7 @@ export function createAddressCohortFolder(ctx, args) {
|
||||
...createCostBasisSection(list, useGroupName, title),
|
||||
|
||||
// Activity section
|
||||
...createActivitySection(list, useGroupName, title),
|
||||
...createActivitySection(args, title),
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -208,20 +217,25 @@ function createRealizedPnlSection(ctx, args, title) {
|
||||
color: colors.green,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: realized.realizedProfit.cumulative,
|
||||
name: "Profit Cumulative",
|
||||
color: colors.green,
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: realized.realizedLoss.sum,
|
||||
name: "Loss",
|
||||
color: colors.red,
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
// RealizedPattern (address cohorts) doesn't have realizedProfitToLossRatio
|
||||
line({
|
||||
metric: realized.totalRealizedPnl,
|
||||
name: "Total",
|
||||
color: colors.default,
|
||||
defaultActive: false,
|
||||
metric: realized.realizedLoss.cumulative,
|
||||
name: "Loss Cumulative",
|
||||
color: colors.red,
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: realized.negRealizedLoss.sum,
|
||||
@@ -231,7 +245,176 @@ function createRealizedPnlSection(ctx, args, title) {
|
||||
}),
|
||||
line({
|
||||
metric: realized.negRealizedLoss.cumulative,
|
||||
name: "Negative Loss",
|
||||
name: "Negative Loss Cumulative",
|
||||
color: colors.red,
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: realized.totalRealizedPnl,
|
||||
name: "Total",
|
||||
color: colors.default,
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
baseline({
|
||||
metric: realized.realizedProfitRelToRealizedCap.sum,
|
||||
name: "Profit",
|
||||
color: colors.green,
|
||||
unit: Unit.pctRcap,
|
||||
}),
|
||||
baseline({
|
||||
metric: realized.realizedProfitRelToRealizedCap.cumulative,
|
||||
name: "Profit Cumulative",
|
||||
color: colors.green,
|
||||
unit: Unit.pctRcap,
|
||||
defaultActive: false,
|
||||
}),
|
||||
baseline({
|
||||
metric: realized.realizedLossRelToRealizedCap.sum,
|
||||
name: "Loss",
|
||||
color: colors.red,
|
||||
unit: Unit.pctRcap,
|
||||
}),
|
||||
baseline({
|
||||
metric: realized.realizedLossRelToRealizedCap.cumulative,
|
||||
name: "Loss Cumulative",
|
||||
color: colors.red,
|
||||
unit: Unit.pctRcap,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Net pnl",
|
||||
title: `Net Realized P&L ${title}`,
|
||||
bottom: [
|
||||
baseline({
|
||||
metric: realized.netRealizedPnl.sum,
|
||||
name: "Net",
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
baseline({
|
||||
metric: realized.netRealizedPnl.cumulative,
|
||||
name: "Net Cumulative",
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
baseline({
|
||||
metric: realized.netRealizedPnlCumulative30dDelta,
|
||||
name: "Cumulative 30d change",
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
baseline({
|
||||
metric: realized.netRealizedPnlRelToRealizedCap.sum,
|
||||
name: "Net",
|
||||
unit: Unit.pctRcap,
|
||||
}),
|
||||
baseline({
|
||||
metric: realized.netRealizedPnlRelToRealizedCap.cumulative,
|
||||
name: "Net Cumulative",
|
||||
unit: Unit.pctRcap,
|
||||
defaultActive: false,
|
||||
}),
|
||||
baseline({
|
||||
metric: realized.netRealizedPnlCumulative30dDeltaRelToRealizedCap,
|
||||
name: "Cumulative 30d change",
|
||||
unit: Unit.pctRcap,
|
||||
defaultActive: false,
|
||||
}),
|
||||
baseline({
|
||||
metric: realized.netRealizedPnlCumulative30dDeltaRelToMarketCap,
|
||||
name: "Cumulative 30d change",
|
||||
unit: Unit.pctMcap,
|
||||
}),
|
||||
priceLine({
|
||||
ctx,
|
||||
unit: Unit.usd,
|
||||
number: 1,
|
||||
}),
|
||||
priceLine({
|
||||
ctx,
|
||||
unit: Unit.pctMcap,
|
||||
}),
|
||||
priceLine({
|
||||
ctx,
|
||||
unit: Unit.pctRcap,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "sopr",
|
||||
title: `SOPR ${title}`,
|
||||
bottom: [
|
||||
baseline({
|
||||
metric: realized.sopr,
|
||||
name: "SOPR",
|
||||
unit: Unit.ratio,
|
||||
base: 1,
|
||||
}),
|
||||
baseline({
|
||||
metric: realized.sopr7dEma,
|
||||
name: "7d EMA",
|
||||
color: [colors.lime, colors.rose],
|
||||
unit: Unit.ratio,
|
||||
defaultActive: false,
|
||||
base: 1,
|
||||
}),
|
||||
baseline({
|
||||
metric: realized.sopr30dEma,
|
||||
name: "30d EMA",
|
||||
color: [colors.avocado, colors.pink],
|
||||
unit: Unit.ratio,
|
||||
defaultActive: false,
|
||||
base: 1,
|
||||
}),
|
||||
priceLine({
|
||||
ctx,
|
||||
unit: Unit.ratio,
|
||||
number: 1,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Sell Side Risk",
|
||||
title: `Sell Side Risk Ratio ${title}`,
|
||||
bottom: [
|
||||
line({
|
||||
metric: realized.sellSideRiskRatio,
|
||||
name: "Raw",
|
||||
color: colors.orange,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
line({
|
||||
metric: realized.sellSideRiskRatio7dEma,
|
||||
name: "7d EMA",
|
||||
color: colors.red,
|
||||
unit: Unit.ratio,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: realized.sellSideRiskRatio30dEma,
|
||||
name: "30d EMA",
|
||||
color: colors.rose,
|
||||
unit: Unit.ratio,
|
||||
defaultActive: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "value",
|
||||
title: `Value Created & Destroyed ${title}`,
|
||||
bottom: [
|
||||
line({
|
||||
metric: realized.valueCreated,
|
||||
name: "Created",
|
||||
color: colors.emerald,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: realized.valueDestroyed,
|
||||
name: "Destroyed",
|
||||
color: colors.red,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
@@ -255,19 +438,6 @@ function createUnrealizedSection(ctx, list, useGroupName, title) {
|
||||
{
|
||||
name: "Unrealized",
|
||||
tree: [
|
||||
{
|
||||
name: "nupl",
|
||||
title: `Net Unrealized P&L ${title}`,
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
baseline({
|
||||
metric: tree.unrealized.netUnrealizedPnl,
|
||||
name: useGroupName ? name : "NUPL",
|
||||
color: useGroupName ? color : [colors.red, colors.green],
|
||||
unit: Unit.ratio,
|
||||
options: { baseValue: { price: 0 } },
|
||||
}),
|
||||
]),
|
||||
},
|
||||
{
|
||||
name: "profit",
|
||||
title: `Unrealized Profit ${title}`,
|
||||
@@ -275,7 +445,7 @@ function createUnrealizedSection(ctx, list, useGroupName, title) {
|
||||
line({
|
||||
metric: tree.unrealized.unrealizedProfit,
|
||||
name: useGroupName ? name : "Profit",
|
||||
color,
|
||||
color: useGroupName ? color : colors.green,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
]),
|
||||
@@ -287,11 +457,117 @@ function createUnrealizedSection(ctx, list, useGroupName, title) {
|
||||
line({
|
||||
metric: tree.unrealized.unrealizedLoss,
|
||||
name: useGroupName ? name : "Loss",
|
||||
color,
|
||||
color: useGroupName ? color : colors.red,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
{
|
||||
name: "total pnl",
|
||||
title: `Total Unrealized P&L ${title}`,
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
baseline({
|
||||
metric: tree.unrealized.totalUnrealizedPnl,
|
||||
name: useGroupName ? name : "Total",
|
||||
color: useGroupName ? color : undefined,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
{
|
||||
name: "negative loss",
|
||||
title: `Negative Unrealized Loss ${title}`,
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
line({
|
||||
metric: tree.unrealized.negUnrealizedLoss,
|
||||
name: useGroupName ? name : "Negative Loss",
|
||||
color: useGroupName ? color : colors.red,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
{
|
||||
name: "Relative",
|
||||
tree: [
|
||||
{
|
||||
name: "nupl",
|
||||
title: `NUPL (Rel to Market Cap) ${title}`,
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
baseline({
|
||||
metric: tree.relative.nupl,
|
||||
name: useGroupName ? name : "NUPL",
|
||||
color: useGroupName ? color : undefined,
|
||||
unit: Unit.ratio,
|
||||
options: { baseValue: { price: 0 } },
|
||||
}),
|
||||
]),
|
||||
},
|
||||
{
|
||||
name: "profit",
|
||||
title: `Unrealized Profit (% of Market Cap) ${title}`,
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
line({
|
||||
metric: tree.relative.unrealizedProfitRelToMarketCap,
|
||||
name: useGroupName ? name : "Profit",
|
||||
color: useGroupName ? color : colors.green,
|
||||
unit: Unit.pctMcap,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
{
|
||||
name: "loss",
|
||||
title: `Unrealized Loss (% of Market Cap) ${title}`,
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
line({
|
||||
metric: tree.relative.unrealizedLossRelToMarketCap,
|
||||
name: useGroupName ? name : "Loss",
|
||||
color: useGroupName ? color : colors.red,
|
||||
unit: Unit.pctMcap,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
{
|
||||
name: "net pnl",
|
||||
title: `Net Unrealized P&L (% of Market Cap) ${title}`,
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
baseline({
|
||||
metric: tree.relative.netUnrealizedPnlRelToMarketCap,
|
||||
name: useGroupName ? name : "Net",
|
||||
color: useGroupName ? color : undefined,
|
||||
unit: Unit.pctMcap,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
{
|
||||
name: "negative loss",
|
||||
title: `Negative Unrealized Loss (% of Market Cap) ${title}`,
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
line({
|
||||
metric: tree.relative.negUnrealizedLossRelToMarketCap,
|
||||
name: useGroupName ? name : "Negative Loss",
|
||||
color: useGroupName ? color : colors.red,
|
||||
unit: Unit.pctMcap,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "nupl",
|
||||
title: `Net Unrealized P&L ${title}`,
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
baseline({
|
||||
metric: tree.unrealized.netUnrealizedPnl,
|
||||
name: useGroupName ? name : "NUPL",
|
||||
color: useGroupName ? color : undefined,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
priceLine({
|
||||
ctx,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
]),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -340,12 +616,37 @@ function createCostBasisSection(list, useGroupName, title) {
|
||||
|
||||
/**
|
||||
* Create activity section
|
||||
* @param {readonly AddressCohortObject[]} list
|
||||
* @param {boolean} useGroupName
|
||||
* @param {AddressCohortObject | AddressCohortGroupObject} args
|
||||
* @param {string} title
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function createActivitySection(list, useGroupName, title) {
|
||||
function createActivitySection(args, title) {
|
||||
const list = "list" in args ? args.list : [args];
|
||||
const isSingle = !("list" in args);
|
||||
|
||||
// Single cohort: all metrics on one chart
|
||||
if (isSingle) {
|
||||
const cohort = /** @type {AddressCohortObject} */ (args);
|
||||
return [
|
||||
{
|
||||
name: "Activity",
|
||||
tree: [
|
||||
{
|
||||
name: "Coins Destroyed",
|
||||
title: `Coins Destroyed ${title}`,
|
||||
bottom: createSingleCoinsDestroyedSeries(cohort),
|
||||
},
|
||||
{
|
||||
name: "Sent",
|
||||
title: `Sent ${title}`,
|
||||
bottom: createSingleSentSeries(cohort),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// Grouped cohorts: split charts for comparison
|
||||
return [
|
||||
{
|
||||
name: "Activity",
|
||||
@@ -353,38 +654,42 @@ function createActivitySection(list, useGroupName, title) {
|
||||
{
|
||||
name: "coinblocks destroyed",
|
||||
title: `Coinblocks Destroyed ${title}`,
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
line({
|
||||
metric: tree.activity.coinblocksDestroyed.sum,
|
||||
name: useGroupName ? name : "Coinblocks",
|
||||
color,
|
||||
unit: Unit.coinblocks,
|
||||
}),
|
||||
line({
|
||||
metric: tree.activity.coinblocksDestroyed.cumulative,
|
||||
name: useGroupName ? name : "Coinblocks",
|
||||
color,
|
||||
unit: Unit.coinblocks,
|
||||
}),
|
||||
]),
|
||||
bottom: createGroupedCoinblocksDestroyedSeries(list),
|
||||
},
|
||||
{
|
||||
name: "coindays destroyed",
|
||||
title: `Coindays Destroyed ${title}`,
|
||||
bottom: list.flatMap(({ color, name, tree }) => [
|
||||
line({
|
||||
metric: tree.activity.coindaysDestroyed.sum,
|
||||
name: useGroupName ? name : "Coindays",
|
||||
color,
|
||||
unit: Unit.coindays,
|
||||
}),
|
||||
line({
|
||||
metric: tree.activity.coindaysDestroyed.cumulative,
|
||||
name: useGroupName ? name : "Coindays",
|
||||
color,
|
||||
unit: Unit.coindays,
|
||||
}),
|
||||
]),
|
||||
bottom: createGroupedCoindaysDestroyedSeries(list),
|
||||
},
|
||||
{
|
||||
name: "satblocks destroyed",
|
||||
title: `Satblocks Destroyed ${title}`,
|
||||
bottom: createGroupedSatblocksDestroyedSeries(list),
|
||||
},
|
||||
{
|
||||
name: "satdays destroyed",
|
||||
title: `Satdays Destroyed ${title}`,
|
||||
bottom: createGroupedSatdaysDestroyedSeries(list),
|
||||
},
|
||||
{
|
||||
name: "Sent",
|
||||
tree: [
|
||||
{
|
||||
name: "sats",
|
||||
title: `Sent (Sats) ${title}`,
|
||||
bottom: createGroupedSentSatsSeries(list),
|
||||
},
|
||||
{
|
||||
name: "bitcoin",
|
||||
title: `Sent (BTC) ${title}`,
|
||||
bottom: createGroupedSentBitcoinSeries(list),
|
||||
},
|
||||
{
|
||||
name: "dollars",
|
||||
title: `Sent ($) ${title}`,
|
||||
bottom: createGroupedSentDollarsSeries(list),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -92,8 +92,8 @@ export function buildCohortData(colors, brk) {
|
||||
};
|
||||
});
|
||||
|
||||
// Min age cohorts (from X time) - CohortBasic (neither adjustedSopr nor percentiles)
|
||||
/** @type {readonly CohortBasic[]} */
|
||||
// Min age cohorts (from X time) - CohortBasicWithMarketCap (has RelToMarketCap)
|
||||
/** @type {readonly CohortBasicWithMarketCap[]} */
|
||||
const fromDate = entries(utxoCohorts.minAge).map(([key, tree]) => {
|
||||
const names = MIN_AGE_NAMES[key];
|
||||
return {
|
||||
@@ -116,8 +116,8 @@ export function buildCohortData(colors, brk) {
|
||||
};
|
||||
});
|
||||
|
||||
// Epoch cohorts - CohortBasic (neither adjustedSopr nor percentiles)
|
||||
/** @type {readonly CohortBasic[]} */
|
||||
// Epoch cohorts - CohortBasicWithoutMarketCap (no RelToMarketCap)
|
||||
/** @type {readonly CohortBasicWithoutMarketCap[]} */
|
||||
const epoch = entries(utxoCohorts.epoch).map(([key, tree]) => {
|
||||
const names = EPOCH_NAMES[key];
|
||||
return {
|
||||
@@ -128,8 +128,8 @@ export function buildCohortData(colors, brk) {
|
||||
};
|
||||
});
|
||||
|
||||
// UTXOs above amount - CohortBasic (neither adjustedSopr nor percentiles)
|
||||
/** @type {readonly CohortBasic[]} */
|
||||
// UTXOs above amount - CohortBasicWithMarketCap (has RelToMarketCap)
|
||||
/** @type {readonly CohortBasicWithMarketCap[]} */
|
||||
const utxosAboveAmount = entries(utxoCohorts.geAmount).map(([key, tree]) => {
|
||||
const names = GE_AMOUNT_NAMES[key];
|
||||
return {
|
||||
@@ -154,8 +154,8 @@ export function buildCohortData(colors, brk) {
|
||||
},
|
||||
);
|
||||
|
||||
// UTXOs under amount - CohortBasic (neither adjustedSopr nor percentiles)
|
||||
/** @type {readonly CohortBasic[]} */
|
||||
// UTXOs under amount - CohortBasicWithMarketCap (has RelToMarketCap)
|
||||
/** @type {readonly CohortBasicWithMarketCap[]} */
|
||||
const utxosUnderAmount = entries(utxoCohorts.ltAmount).map(([key, tree]) => {
|
||||
const names = LT_AMOUNT_NAMES[key];
|
||||
return {
|
||||
@@ -180,8 +180,8 @@ export function buildCohortData(colors, brk) {
|
||||
},
|
||||
);
|
||||
|
||||
// UTXOs amount ranges - CohortBasic (neither adjustedSopr nor percentiles)
|
||||
/** @type {readonly CohortBasic[]} */
|
||||
// UTXOs amount ranges - CohortBasicWithoutMarketCap (no RelToMarketCap)
|
||||
/** @type {readonly CohortBasicWithoutMarketCap[]} */
|
||||
const utxosAmountRanges = entries(utxoCohorts.amountRange).map(
|
||||
([key, tree]) => {
|
||||
const names = AMOUNT_RANGE_NAMES[key];
|
||||
@@ -221,7 +221,7 @@ export function buildCohortData(colors, brk) {
|
||||
};
|
||||
});
|
||||
|
||||
/** @type {readonly CohortBasic[]} */
|
||||
/** @type {readonly CohortBasicWithoutMarketCap[]} */
|
||||
const typeOther = entries(utxoCohorts.type)
|
||||
.filter(([key]) => !isAddressable(key))
|
||||
.map(([key, tree]) => {
|
||||
@@ -234,8 +234,8 @@ export function buildCohortData(colors, brk) {
|
||||
};
|
||||
});
|
||||
|
||||
// Year cohorts - CohortBasic (neither adjustedSopr nor percentiles)
|
||||
/** @type {readonly CohortBasic[]} */
|
||||
// Year cohorts - CohortBasicWithoutMarketCap (no RelToMarketCap)
|
||||
/** @type {readonly CohortBasicWithoutMarketCap[]} */
|
||||
const year = entries(utxoCohorts.year).map(([key, tree]) => {
|
||||
const names = YEAR_NAMES[key];
|
||||
return {
|
||||
|
||||
@@ -11,7 +11,8 @@ export {
|
||||
createCohortFolderFull,
|
||||
createCohortFolderWithAdjusted,
|
||||
createCohortFolderWithPercentiles,
|
||||
createCohortFolderBasic,
|
||||
createCohortFolderBasicWithMarketCap,
|
||||
createCohortFolderBasicWithoutMarketCap,
|
||||
createCohortFolderAddress,
|
||||
} from "./utxo.js";
|
||||
export { createAddressCohortFolder } from "./address.js";
|
||||
@@ -27,6 +28,5 @@ export {
|
||||
createRealizedPriceSeries,
|
||||
createRealizedPriceRatioSeries,
|
||||
createRealizedCapSeries,
|
||||
createCostBasisMinMaxSeries,
|
||||
createCostBasisPercentilesSeries,
|
||||
} from "./shared.js";
|
||||
|
||||
@@ -221,72 +221,256 @@ export function createRealizedCapSeries(list, useGroupName) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create cost basis min/max series (available on all cohorts)
|
||||
* @param {readonly CohortObject[]} list
|
||||
* Create cost basis percentile series (only for cohorts with CostBasisPattern2)
|
||||
* Includes min (p0) and max (p100) with full rainbow coloring
|
||||
* @param {Colors} colors
|
||||
* @param {readonly CohortWithCostBasisPercentiles[]} list
|
||||
* @param {boolean} useGroupName
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function createCostBasisMinMaxSeries(list, useGroupName) {
|
||||
return list.flatMap(({ color, name, tree }) => [
|
||||
export function createCostBasisPercentilesSeries(colors, list, useGroupName) {
|
||||
return list.flatMap(({ name, tree }) => {
|
||||
const cb = tree.costBasis;
|
||||
const p = cb.percentiles;
|
||||
const n = (/** @type {number} */ pct) => (useGroupName ? `${name} p${pct}` : `p${pct}`);
|
||||
return [
|
||||
line({ metric: cb.max, name: n(100), color: colors.purple, unit: Unit.usd, defaultActive: false }),
|
||||
line({ metric: p.pct95, name: n(95), color: colors.fuchsia, unit: Unit.usd, defaultActive: false }),
|
||||
line({ metric: p.pct90, name: n(90), color: colors.pink, unit: Unit.usd, defaultActive: false }),
|
||||
line({ metric: p.pct85, name: n(85), color: colors.pink, unit: Unit.usd, defaultActive: false }),
|
||||
line({ metric: p.pct80, name: n(80), color: colors.rose, unit: Unit.usd, defaultActive: false }),
|
||||
line({ metric: p.pct75, name: n(75), color: colors.red, unit: Unit.usd, defaultActive: false }),
|
||||
line({ metric: p.pct70, name: n(70), color: colors.orange, unit: Unit.usd, defaultActive: false }),
|
||||
line({ metric: p.pct65, name: n(65), color: colors.amber, unit: Unit.usd, defaultActive: false }),
|
||||
line({ metric: p.pct60, name: n(60), color: colors.yellow, unit: Unit.usd, defaultActive: false }),
|
||||
line({ metric: p.pct55, name: n(55), color: colors.yellow, unit: Unit.usd, defaultActive: false }),
|
||||
line({ metric: p.pct50, name: n(50), color: colors.avocado, unit: Unit.usd }),
|
||||
line({ metric: p.pct45, name: n(45), color: colors.lime, unit: Unit.usd, defaultActive: false }),
|
||||
line({ metric: p.pct40, name: n(40), color: colors.green, unit: Unit.usd, defaultActive: false }),
|
||||
line({ metric: p.pct35, name: n(35), color: colors.emerald, unit: Unit.usd, defaultActive: false }),
|
||||
line({ metric: p.pct30, name: n(30), color: colors.teal, unit: Unit.usd, defaultActive: false }),
|
||||
line({ metric: p.pct25, name: n(25), color: colors.teal, unit: Unit.usd, defaultActive: false }),
|
||||
line({ metric: p.pct20, name: n(20), color: colors.cyan, unit: Unit.usd, defaultActive: false }),
|
||||
line({ metric: p.pct15, name: n(15), color: colors.sky, unit: Unit.usd, defaultActive: false }),
|
||||
line({ metric: p.pct10, name: n(10), color: colors.blue, unit: Unit.usd, defaultActive: false }),
|
||||
line({ metric: p.pct05, name: n(5), color: colors.indigo, unit: Unit.usd, defaultActive: false }),
|
||||
line({ metric: cb.min, name: n(0), color: colors.violet, unit: Unit.usd, defaultActive: false }),
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Activity Section Helpers
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Create coins destroyed series (coinblocks, coindays, satblocks, satdays) for single cohort
|
||||
* All metrics on one chart
|
||||
* @param {CohortObject} cohort
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function createSingleCoinsDestroyedSeries(cohort) {
|
||||
const { tree, color } = cohort;
|
||||
return [
|
||||
line({
|
||||
metric: tree.costBasis.min,
|
||||
name: useGroupName ? `${name} min` : "Min",
|
||||
metric: tree.activity.coinblocksDestroyed.sum,
|
||||
name: "Coinblocks",
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
unit: Unit.coinblocks,
|
||||
}),
|
||||
line({
|
||||
metric: tree.costBasis.max,
|
||||
name: useGroupName ? `${name} max` : "Max",
|
||||
metric: tree.activity.coinblocksDestroyed.cumulative,
|
||||
name: "Coinblocks Cumulative",
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
unit: Unit.coinblocks,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: tree.activity.coindaysDestroyed.sum,
|
||||
name: "Coindays",
|
||||
color,
|
||||
unit: Unit.coindays,
|
||||
}),
|
||||
line({
|
||||
metric: tree.activity.coindaysDestroyed.cumulative,
|
||||
name: "Coindays Cumulative",
|
||||
color,
|
||||
unit: Unit.coindays,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: tree.activity.satblocksDestroyed,
|
||||
name: "Satblocks",
|
||||
color,
|
||||
unit: Unit.satblocks,
|
||||
}),
|
||||
line({
|
||||
metric: tree.activity.satdaysDestroyed,
|
||||
name: "Satdays",
|
||||
color,
|
||||
unit: Unit.satdays,
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create coinblocks destroyed series for grouped cohorts (comparison)
|
||||
* @param {readonly CohortObject[]} list
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function createGroupedCoinblocksDestroyedSeries(list) {
|
||||
return list.flatMap(({ color, name, tree }) => [
|
||||
line({
|
||||
metric: tree.activity.coinblocksDestroyed.sum,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.coinblocks,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create cost basis percentile series (only for cohorts with CostBasisPattern2)
|
||||
* @param {readonly CohortWithCostBasisPercentiles[]} list
|
||||
* @param {boolean} useGroupName
|
||||
* Create coindays destroyed series for grouped cohorts (comparison)
|
||||
* @param {readonly CohortObject[]} list
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function createCostBasisPercentilesSeries(list, useGroupName) {
|
||||
return list.flatMap(({ color, name, tree }) => {
|
||||
const percentiles = tree.costBasis.percentiles;
|
||||
return [
|
||||
line({
|
||||
metric: percentiles.pct10,
|
||||
name: useGroupName ? `${name} p10` : "p10",
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: percentiles.pct25,
|
||||
name: useGroupName ? `${name} p25` : "p25",
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: percentiles.pct50,
|
||||
name: useGroupName ? `${name} p50` : "p50",
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: percentiles.pct75,
|
||||
name: useGroupName ? `${name} p75` : "p75",
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: percentiles.pct90,
|
||||
name: useGroupName ? `${name} p90` : "p90",
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
];
|
||||
});
|
||||
export function createGroupedCoindaysDestroyedSeries(list) {
|
||||
return list.flatMap(({ color, name, tree }) => [
|
||||
line({
|
||||
metric: tree.activity.coindaysDestroyed.sum,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.coindays,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create satblocks destroyed series for grouped cohorts (comparison)
|
||||
* @param {readonly CohortObject[]} list
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function createGroupedSatblocksDestroyedSeries(list) {
|
||||
return list.flatMap(({ color, name, tree }) => [
|
||||
line({
|
||||
metric: tree.activity.satblocksDestroyed,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.satblocks,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create satdays destroyed series for grouped cohorts (comparison)
|
||||
* @param {readonly CohortObject[]} list
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function createGroupedSatdaysDestroyedSeries(list) {
|
||||
return list.flatMap(({ color, name, tree }) => [
|
||||
line({
|
||||
metric: tree.activity.satdaysDestroyed,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.satdays,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create sent series (sats, btc, usd) for single cohort - all on one chart
|
||||
* @param {CohortObject} cohort
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
export function createSingleSentSeries(cohort) {
|
||||
const { tree, color } = cohort;
|
||||
return [
|
||||
line({
|
||||
metric: tree.activity.sent.sats.sum,
|
||||
name: "Sent",
|
||||
color,
|
||||
unit: Unit.sats,
|
||||
}),
|
||||
line({
|
||||
metric: tree.activity.sent.sats.cumulative,
|
||||
name: "Cumulative",
|
||||
color,
|
||||
unit: Unit.sats,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: tree.activity.sent.bitcoin.sum,
|
||||
name: "Sent",
|
||||
color,
|
||||
unit: Unit.btc,
|
||||
}),
|
||||
line({
|
||||
metric: tree.activity.sent.bitcoin.cumulative,
|
||||
name: "Cumulative",
|
||||
color,
|
||||
unit: Unit.btc,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: tree.activity.sent.dollars.sum,
|
||||
name: "Sent",
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: tree.activity.sent.dollars.cumulative,
|
||||
name: "Cumulative",
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ export function createCohortFolderAll(ctx, args) {
|
||||
createSingleAddrCountChart(ctx, args, title),
|
||||
createSingleRealizedSectionFull(ctx, args, title),
|
||||
createSingleUnrealizedSectionAll(ctx, args, title),
|
||||
createSingleCostBasisSectionWithPercentiles(args, title),
|
||||
createSingleCostBasisSectionWithPercentiles(ctx, args, title),
|
||||
createSingleActivitySectionWithAdjusted(ctx, args, title),
|
||||
],
|
||||
};
|
||||
@@ -81,7 +81,7 @@ export function createCohortFolderFull(ctx, args) {
|
||||
createGroupedUtxoCountChart(list, title),
|
||||
createGroupedRealizedSectionWithAdjusted(ctx, list, title),
|
||||
createGroupedUnrealizedSectionFull(ctx, list, title),
|
||||
createGroupedCostBasisSectionWithPercentiles(list, title),
|
||||
createGroupedCostBasisSectionWithPercentiles(ctx, list, title),
|
||||
createGroupedActivitySectionWithAdjusted(list, title),
|
||||
],
|
||||
};
|
||||
@@ -94,7 +94,7 @@ export function createCohortFolderFull(ctx, args) {
|
||||
createSingleUtxoCountChart(args, title),
|
||||
createSingleRealizedSectionFull(ctx, args, title),
|
||||
createSingleUnrealizedSectionFull(ctx, args, title),
|
||||
createSingleCostBasisSectionWithPercentiles(args, title),
|
||||
createSingleCostBasisSectionWithPercentiles(ctx, args, title),
|
||||
createSingleActivitySectionWithAdjusted(ctx, args, title),
|
||||
],
|
||||
};
|
||||
@@ -153,7 +153,7 @@ export function createCohortFolderWithPercentiles(ctx, args) {
|
||||
createGroupedUtxoCountChart(list, title),
|
||||
createGroupedRealizedSectionBasic(ctx, list, title),
|
||||
createGroupedUnrealizedSectionWithOwnCaps(ctx, list, title),
|
||||
createGroupedCostBasisSectionWithPercentiles(list, title),
|
||||
createGroupedCostBasisSectionWithPercentiles(ctx, list, title),
|
||||
createGroupedActivitySectionBasic(list, title),
|
||||
],
|
||||
};
|
||||
@@ -166,19 +166,55 @@ export function createCohortFolderWithPercentiles(ctx, args) {
|
||||
createSingleUtxoCountChart(args, title),
|
||||
createSingleRealizedSectionWithPercentiles(ctx, args, title),
|
||||
createSingleUnrealizedSectionWithOwnCaps(ctx, args, title),
|
||||
createSingleCostBasisSectionWithPercentiles(args, title),
|
||||
createSingleCostBasisSectionWithPercentiles(ctx, args, title),
|
||||
createSingleActivitySectionBasic(ctx, args, title),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic folder: no adjustedSopr, no percentiles (minAge.*, epoch.*, amount cohorts)
|
||||
* Basic folder WITH RelToMarketCap (minAge.*, geAmount.*, ltAmount.*, type.*)
|
||||
* @param {PartialContext} ctx
|
||||
* @param {CohortBasic | CohortGroupBasic} args
|
||||
* @param {CohortBasicWithMarketCap | CohortGroupBasicWithMarketCap} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createCohortFolderBasic(ctx, args) {
|
||||
export function createCohortFolderBasicWithMarketCap(ctx, args) {
|
||||
if ("list" in args) {
|
||||
const { list } = args;
|
||||
const title = args.title ? `by ${args.title}` : "";
|
||||
return {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createGroupedSupplySection(list, title),
|
||||
createGroupedUtxoCountChart(list, title),
|
||||
createGroupedRealizedSectionBasic(ctx, list, title),
|
||||
createGroupedUnrealizedSectionWithMarketCapOnly(ctx, list, title),
|
||||
createGroupedCostBasisSection(list, title),
|
||||
createGroupedActivitySectionBasic(list, title),
|
||||
],
|
||||
};
|
||||
}
|
||||
const title = args.title ? `of ${args.title}` : "";
|
||||
return {
|
||||
name: args.name || "all",
|
||||
tree: [
|
||||
createSingleSupplyChart(ctx, args, title),
|
||||
createSingleUtxoCountChart(args, title),
|
||||
createSingleRealizedSectionBasic(ctx, args, title),
|
||||
createSingleUnrealizedSectionWithMarketCapOnly(ctx, args, title),
|
||||
createSingleCostBasisSection(args, title),
|
||||
createSingleActivitySectionBasic(ctx, args, title),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic folder WITHOUT RelToMarketCap (epoch.*, amountRange.*, year.*)
|
||||
* @param {PartialContext} ctx
|
||||
* @param {CohortBasicWithoutMarketCap | CohortGroupBasicWithoutMarketCap} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createCohortFolderBasicWithoutMarketCap(ctx, args) {
|
||||
if ("list" in args) {
|
||||
const { list } = args;
|
||||
const title = args.title ? `by ${args.title}` : "";
|
||||
@@ -217,6 +253,7 @@ export function createCohortFolderBasic(ctx, args) {
|
||||
|
||||
/**
|
||||
* Address folder: like basic but with address count (addressable type cohorts)
|
||||
* Uses base unrealized section (no RelToMarketCap since it extends CohortBasicWithoutMarketCap)
|
||||
* @param {PartialContext} ctx
|
||||
* @param {CohortAddress | CohortGroupAddress} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
@@ -468,7 +505,7 @@ function createSingleRealizedSectionWithPercentiles(ctx, cohort, title) {
|
||||
/**
|
||||
* Create realized section for CohortBasic (no adjustedSopr, partial ratio)
|
||||
* @param {PartialContext} ctx
|
||||
* @param {CohortBasic} cohort
|
||||
* @param {CohortBasic | CohortAddress} cohort
|
||||
* @param {string} title
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
@@ -491,7 +528,7 @@ function createSingleRealizedSectionBasic(ctx, cohort, title) {
|
||||
/**
|
||||
* Create realized section without adjusted SOPR for grouped cohorts
|
||||
* @param {PartialContext} ctx
|
||||
* @param {readonly (CohortWithPercentiles | CohortBasic)[]} list
|
||||
* @param {readonly (CohortWithPercentiles | CohortBasic | CohortAddress)[]} list
|
||||
* @param {string} title
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
@@ -558,10 +595,11 @@ function createSingleRealizedPriceChartsWithRatio(ctx, cohort, title) {
|
||||
return [
|
||||
createSingleRealizedPriceChart(cohort, title),
|
||||
createRatioChart(ctx, {
|
||||
title: `Realized Price ${title}`,
|
||||
title,
|
||||
price: tree.realized.realizedPrice,
|
||||
ratio,
|
||||
color,
|
||||
name: "MVRV",
|
||||
}),
|
||||
createZScoresFolder(ctx, {
|
||||
title: `Realized Price ${title}`,
|
||||
@@ -577,7 +615,7 @@ function createSingleRealizedPriceChartsWithRatio(ctx, cohort, title) {
|
||||
* Create realized price and basic ratio charts for cohorts with RealizedPriceExtraPattern
|
||||
* (CohortWithAdjusted, CohortBasic have RealizedPattern/4 which has RealizedPriceExtraPattern)
|
||||
* @param {PartialContext} ctx
|
||||
* @param {CohortWithAdjusted | CohortBasic} cohort
|
||||
* @param {CohortWithAdjusted | CohortBasic | CohortAddress} cohort
|
||||
* @param {string} title
|
||||
* @returns {PartialChartOption[]}
|
||||
*/
|
||||
@@ -617,6 +655,13 @@ function createSingleRealizedCapSeries(ctx, cohort) {
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: tree.realized.realizedValue,
|
||||
name: "Value",
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
baseline({
|
||||
metric: tree.realized.realizedCap30dDelta,
|
||||
name: "30d change",
|
||||
@@ -689,8 +734,8 @@ function createSingleRealizedPnlSection(ctx, cohort, title) {
|
||||
),
|
||||
...fromBitcoinPatternWithUnit(
|
||||
tree.realized.negRealizedLoss,
|
||||
"Negative Loss",
|
||||
Unit.usd,
|
||||
"Negative Loss",
|
||||
colors.red,
|
||||
),
|
||||
...("realizedProfitToLossRatio" in tree.realized
|
||||
@@ -716,12 +761,26 @@ function createSingleRealizedPnlSection(ctx, cohort, title) {
|
||||
color: colors.green,
|
||||
unit: Unit.pctRcap,
|
||||
}),
|
||||
baseline({
|
||||
metric: tree.realized.realizedProfitRelToRealizedCap.cumulative,
|
||||
name: "Profit Cumulative",
|
||||
color: colors.green,
|
||||
unit: Unit.pctRcap,
|
||||
defaultActive: false,
|
||||
}),
|
||||
baseline({
|
||||
metric: tree.realized.realizedLossRelToRealizedCap.sum,
|
||||
name: "Loss",
|
||||
color: colors.red,
|
||||
unit: Unit.pctRcap,
|
||||
}),
|
||||
baseline({
|
||||
metric: tree.realized.realizedLossRelToRealizedCap.cumulative,
|
||||
name: "Loss Cumulative",
|
||||
color: colors.red,
|
||||
unit: Unit.pctRcap,
|
||||
defaultActive: false,
|
||||
}),
|
||||
priceLine({ ctx, unit: Unit.pctRcap }),
|
||||
priceLine({ ctx, unit: Unit.usd, defaultActive: false }),
|
||||
],
|
||||
@@ -746,6 +805,12 @@ function createSingleRealizedPnlSection(ctx, cohort, title) {
|
||||
name: "Net",
|
||||
unit: Unit.pctRcap,
|
||||
}),
|
||||
baseline({
|
||||
metric: tree.realized.netRealizedPnlRelToRealizedCap.cumulative,
|
||||
name: "Net Cumulative",
|
||||
unit: Unit.pctRcap,
|
||||
defaultActive: false,
|
||||
}),
|
||||
baseline({
|
||||
metric:
|
||||
tree.realized.netRealizedPnlCumulative30dDeltaRelToRealizedCap,
|
||||
@@ -900,7 +965,6 @@ function createGroupedRealizedPnlSections(ctx, list, title) {
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
defaultActive: false,
|
||||
}),
|
||||
]),
|
||||
priceLine({ ctx, unit: Unit.usd }),
|
||||
@@ -980,7 +1044,7 @@ function createSingleBaseSoprChart(ctx, cohort, title) {
|
||||
metric: tree.realized.sopr,
|
||||
name: "SOPR",
|
||||
unit: Unit.ratio,
|
||||
options: { baseValue: { price: 1 } },
|
||||
base: 1,
|
||||
}),
|
||||
baseline({
|
||||
metric: tree.realized.sopr7dEma,
|
||||
@@ -988,7 +1052,7 @@ function createSingleBaseSoprChart(ctx, cohort, title) {
|
||||
color: [colors.lime, colors.rose],
|
||||
unit: Unit.ratio,
|
||||
defaultActive: false,
|
||||
options: { baseValue: { price: 1 } },
|
||||
base: 1,
|
||||
}),
|
||||
baseline({
|
||||
metric: tree.realized.sopr30dEma,
|
||||
@@ -996,7 +1060,7 @@ function createSingleBaseSoprChart(ctx, cohort, title) {
|
||||
color: [colors.avocado, colors.pink],
|
||||
unit: Unit.ratio,
|
||||
defaultActive: false,
|
||||
options: { baseValue: { price: 1 } },
|
||||
base: 1,
|
||||
}),
|
||||
priceLine({ ctx, number: 1, unit: Unit.ratio }),
|
||||
],
|
||||
@@ -1064,7 +1128,7 @@ function createGroupedBaseSoprChart(ctx, list, title) {
|
||||
name,
|
||||
color,
|
||||
unit: Unit.ratio,
|
||||
options: { baseValue: { price: 1 } },
|
||||
base: 1,
|
||||
}),
|
||||
baseline({
|
||||
metric: tree.realized.sopr7dEma,
|
||||
@@ -1072,7 +1136,7 @@ function createGroupedBaseSoprChart(ctx, list, title) {
|
||||
color,
|
||||
unit: Unit.ratio,
|
||||
defaultActive: false,
|
||||
options: { baseValue: { price: 1 } },
|
||||
base: 1,
|
||||
}),
|
||||
baseline({
|
||||
metric: tree.realized.sopr30dEma,
|
||||
@@ -1080,7 +1144,7 @@ function createGroupedBaseSoprChart(ctx, list, title) {
|
||||
color,
|
||||
unit: Unit.ratio,
|
||||
defaultActive: false,
|
||||
options: { baseValue: { price: 1 } },
|
||||
base: 1,
|
||||
}),
|
||||
]),
|
||||
priceLine({ ctx, number: 1, unit: Unit.ratio }),
|
||||
@@ -1171,7 +1235,7 @@ function createGroupedSoprSectionWithAdjusted(ctx, list, title) {
|
||||
/**
|
||||
* Create SOPR section without adjusted SOPR (for cohorts with RealizedPattern/2)
|
||||
* @param {PartialContext} ctx
|
||||
* @param {CohortWithPercentiles | CohortBasic} cohort
|
||||
* @param {CohortWithPercentiles | CohortBasic | CohortAddress} cohort
|
||||
* @param {string} title
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
@@ -1185,7 +1249,7 @@ function createSingleSoprSectionBasic(ctx, cohort, title) {
|
||||
/**
|
||||
* Create grouped SOPR section without adjusted SOPR
|
||||
* @param {PartialContext} ctx
|
||||
* @param {readonly (CohortWithPercentiles | CohortBasic)[]} list
|
||||
* @param {readonly (CohortWithPercentiles | CohortBasic | CohortAddress)[]} list
|
||||
* @param {string} title
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
@@ -1378,8 +1442,7 @@ function createNetUnrealizedPnlBaseMetric(tree) {
|
||||
return baseline({
|
||||
metric: tree.unrealized.netUnrealizedPnl,
|
||||
name: "Net",
|
||||
unit: Unit.ratio,
|
||||
options: { baseValue: { price: 0 } },
|
||||
unit: Unit.usd,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1457,6 +1520,18 @@ function createSingleUnrealizedSectionFull(ctx, cohort, title) {
|
||||
priceLine({ ctx, unit: Unit.pctMcap }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "nupl",
|
||||
title: `NUPL ${title}`,
|
||||
bottom: [
|
||||
baseline({
|
||||
metric: tree.relative.nupl,
|
||||
name: "NUPL",
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
priceLine({ ctx, unit: Unit.ratio }),
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -1493,6 +1568,22 @@ function createSingleUnrealizedSectionWithMarketCap(ctx, cohort, title) {
|
||||
priceLine({ ctx, unit: Unit.pctMcap }),
|
||||
],
|
||||
},
|
||||
...("nupl" in tree.relative
|
||||
? [
|
||||
{
|
||||
name: "nupl",
|
||||
title: `NUPL ${title}`,
|
||||
bottom: [
|
||||
baseline({
|
||||
metric: tree.relative.nupl,
|
||||
name: "NUPL",
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
priceLine({ ctx, unit: Unit.ratio }),
|
||||
],
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -1530,20 +1621,91 @@ function createSingleUnrealizedSectionWithOwnCaps(ctx, cohort, title) {
|
||||
priceLine({ ctx, unit: Unit.usd }),
|
||||
],
|
||||
},
|
||||
...("nupl" in tree.relative
|
||||
? [
|
||||
{
|
||||
name: "nupl",
|
||||
title: `NUPL ${title}`,
|
||||
bottom: [
|
||||
baseline({
|
||||
metric: tree.relative.nupl,
|
||||
name: "NUPL",
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
priceLine({ ctx, unit: Unit.ratio }),
|
||||
],
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Unrealized section with only base metrics (no relative unrealized)
|
||||
* Used by: Epoch cohorts (RelativePattern4)
|
||||
* Unrealized section WITH RelToMarketCap metrics (for CohortBasicWithMarketCap)
|
||||
* Used by: minAge.*, geAmount.*, ltAmount.*
|
||||
* @param {PartialContext} ctx
|
||||
* @param {{ tree: { unrealized: PatternAll["unrealized"] } }} cohort
|
||||
* @param {CohortBasicWithMarketCap} cohort
|
||||
* @param {string} title
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
function createSingleUnrealizedSectionWithMarketCapOnly(ctx, cohort, title) {
|
||||
const { tree } = cohort;
|
||||
|
||||
return {
|
||||
name: "Unrealized",
|
||||
tree: [
|
||||
{
|
||||
name: "pnl",
|
||||
title: `Unrealized P&L ${title}`,
|
||||
bottom: [
|
||||
...createUnrealizedPnlBaseMetrics(ctx, tree),
|
||||
...createUnrealizedPnlRelToMarketCapMetrics(ctx, tree.relative),
|
||||
priceLine({ ctx, unit: Unit.usd, defaultActive: false }),
|
||||
priceLine({ ctx, unit: Unit.pctMcap, defaultActive: false }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Net pnl",
|
||||
title: `Net Unrealized P&L ${title}`,
|
||||
bottom: [
|
||||
createNetUnrealizedPnlBaseMetric(tree),
|
||||
...createNetUnrealizedPnlRelToMarketCapMetrics(tree.relative),
|
||||
priceLine({ ctx, unit: Unit.usd }),
|
||||
priceLine({ ctx, unit: Unit.pctMcap }),
|
||||
],
|
||||
},
|
||||
...("nupl" in tree.relative
|
||||
? [
|
||||
{
|
||||
name: "nupl",
|
||||
title: `NUPL ${title}`,
|
||||
bottom: [
|
||||
baseline({
|
||||
metric: tree.relative.nupl,
|
||||
name: "NUPL",
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
priceLine({ ctx, unit: Unit.ratio }),
|
||||
],
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Unrealized section with only base metrics (no RelToMarketCap)
|
||||
* Used by: epoch.*, amountRange.*, year.*
|
||||
* @param {PartialContext} ctx
|
||||
* @param {CohortBasicWithoutMarketCap} cohort
|
||||
* @param {string} title
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
function createSingleUnrealizedSectionBase(ctx, cohort, title) {
|
||||
const { tree } = cohort;
|
||||
|
||||
return {
|
||||
name: "Unrealized",
|
||||
tree: [
|
||||
@@ -1634,7 +1796,7 @@ function createGroupedUnrealizedSectionFull(ctx, list, title) {
|
||||
metric: tree.unrealized.netUnrealizedPnl,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.ratio,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
baseline({
|
||||
metric: tree.relative.netUnrealizedPnlRelToMarketCap,
|
||||
@@ -1661,6 +1823,21 @@ function createGroupedUnrealizedSectionFull(ctx, list, title) {
|
||||
priceLine({ ctx, unit: Unit.pctOwnPnl }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "nupl",
|
||||
title: `NUPL ${title}`,
|
||||
bottom: [
|
||||
...list.flatMap(({ color, name, tree }) => [
|
||||
baseline({
|
||||
metric: tree.relative.nupl,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
]),
|
||||
priceLine({ ctx, unit: Unit.ratio }),
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -1686,7 +1863,7 @@ function createGroupedUnrealizedSectionWithMarketCap(ctx, list, title) {
|
||||
metric: tree.unrealized.netUnrealizedPnl,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.ratio,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
baseline({
|
||||
metric: tree.relative.netUnrealizedPnlRelToMarketCap,
|
||||
@@ -1699,6 +1876,21 @@ function createGroupedUnrealizedSectionWithMarketCap(ctx, list, title) {
|
||||
priceLine({ ctx, unit: Unit.pctMcap }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "nupl",
|
||||
title: `NUPL ${title}`,
|
||||
bottom: [
|
||||
...list.flatMap(({ color, name, tree }) => [
|
||||
baseline({
|
||||
metric: tree.relative.nupl,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
]),
|
||||
priceLine({ ctx, unit: Unit.ratio }),
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -1711,6 +1903,7 @@ function createGroupedUnrealizedSectionWithMarketCap(ctx, list, title) {
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
function createGroupedUnrealizedSectionWithOwnCaps(ctx, list, title) {
|
||||
const cohortsWithNupl = list.filter(({ tree }) => "nupl" in tree.relative);
|
||||
return {
|
||||
name: "Unrealized",
|
||||
tree: [
|
||||
@@ -1724,7 +1917,7 @@ function createGroupedUnrealizedSectionWithOwnCaps(ctx, list, title) {
|
||||
metric: tree.unrealized.netUnrealizedPnl,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.ratio,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
baseline({
|
||||
metric: tree.relative.netUnrealizedPnlRelToOwnMarketCap,
|
||||
@@ -1744,14 +1937,89 @@ function createGroupedUnrealizedSectionWithOwnCaps(ctx, list, title) {
|
||||
priceLine({ ctx, unit: Unit.pctOwnPnl }),
|
||||
],
|
||||
},
|
||||
...(cohortsWithNupl.length > 0
|
||||
? [
|
||||
{
|
||||
name: "nupl",
|
||||
title: `NUPL ${title}`,
|
||||
bottom: [
|
||||
...cohortsWithNupl.flatMap(({ color, name, tree }) => [
|
||||
baseline({
|
||||
metric: /** @type {{ nupl: AnyMetricPattern }} */ (
|
||||
tree.relative
|
||||
).nupl,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
]),
|
||||
priceLine({ ctx, unit: Unit.ratio }),
|
||||
],
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Grouped unrealized section for Basic cohorts (base only)
|
||||
* Grouped unrealized section WITH RelToMarketCap (for CohortBasicWithMarketCap)
|
||||
* Used by: minAge.*, geAmount.*, ltAmount.*
|
||||
* @param {PartialContext} ctx
|
||||
* @param {readonly CohortBasic[]} list
|
||||
* @param {readonly CohortBasicWithMarketCap[]} list
|
||||
* @param {string} title
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
function createGroupedUnrealizedSectionWithMarketCapOnly(ctx, list, title) {
|
||||
return {
|
||||
name: "Unrealized",
|
||||
tree: [
|
||||
...createGroupedUnrealizedBaseCharts(list, title),
|
||||
{
|
||||
name: "Net pnl",
|
||||
title: `Net Unrealized P&L ${title}`,
|
||||
bottom: [
|
||||
...list.flatMap(({ color, name, tree }) => [
|
||||
baseline({
|
||||
metric: tree.unrealized.netUnrealizedPnl,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
baseline({
|
||||
metric: tree.relative.netUnrealizedPnlRelToMarketCap,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.pctMcap,
|
||||
}),
|
||||
]),
|
||||
priceLine({ ctx, unit: Unit.usd }),
|
||||
priceLine({ ctx, unit: Unit.pctMcap }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "nupl",
|
||||
title: `NUPL ${title}`,
|
||||
bottom: [
|
||||
...list.flatMap(({ color, name, tree }) => [
|
||||
baseline({
|
||||
metric: tree.relative.nupl,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
]),
|
||||
priceLine({ ctx, unit: Unit.ratio }),
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Grouped unrealized section without RelToMarketCap (for CohortBasicWithoutMarketCap)
|
||||
* @param {PartialContext} ctx
|
||||
* @param {readonly CohortBasicWithoutMarketCap[]} list
|
||||
* @param {string} title
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
@@ -1769,7 +2037,7 @@ function createGroupedUnrealizedSectionBase(ctx, list, title) {
|
||||
metric: tree.unrealized.netUnrealizedPnl,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.ratio,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
]),
|
||||
priceLine({ ctx, unit: Unit.usd }),
|
||||
@@ -1781,11 +2049,13 @@ function createGroupedUnrealizedSectionBase(ctx, list, title) {
|
||||
|
||||
/**
|
||||
* Create cost basis section for single cohort WITH percentiles
|
||||
* @param {PartialContext} ctx
|
||||
* @param {CohortAll | CohortFull | CohortWithPercentiles} cohort
|
||||
* @param {string} title
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
function createSingleCostBasisSectionWithPercentiles(cohort, title) {
|
||||
function createSingleCostBasisSectionWithPercentiles(ctx, cohort, title) {
|
||||
const { colors } = ctx;
|
||||
const { color, tree } = cohort;
|
||||
|
||||
return {
|
||||
@@ -1819,7 +2089,7 @@ function createSingleCostBasisSectionWithPercentiles(cohort, title) {
|
||||
{
|
||||
name: "percentiles",
|
||||
title: `Cost Basis Percentiles ${title}`,
|
||||
top: createCostBasisPercentilesSeries([cohort], false),
|
||||
top: createCostBasisPercentilesSeries(colors, [cohort], false),
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1827,11 +2097,13 @@ function createSingleCostBasisSectionWithPercentiles(cohort, title) {
|
||||
|
||||
/**
|
||||
* Create cost basis section for grouped cohorts WITH percentiles
|
||||
* @param {PartialContext} ctx
|
||||
* @param {readonly (CohortFull | CohortWithPercentiles)[]} list
|
||||
* @param {string} title
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
function createGroupedCostBasisSectionWithPercentiles(list, title) {
|
||||
function createGroupedCostBasisSectionWithPercentiles(ctx, list, title) {
|
||||
const { colors } = ctx;
|
||||
return {
|
||||
name: "Cost Basis",
|
||||
tree: [
|
||||
@@ -1861,13 +2133,18 @@ function createGroupedCostBasisSectionWithPercentiles(list, title) {
|
||||
line({ metric: tree.costBasis.max, name, color, unit: Unit.usd }),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "percentiles",
|
||||
title: `Cost Basis Percentiles ${title}`,
|
||||
top: createCostBasisPercentilesSeries(colors, list, true),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create cost basis section for single cohort (no percentiles)
|
||||
* @param {CohortWithAdjusted | CohortBasic} cohort
|
||||
* @param {CohortWithAdjusted | CohortBasic | CohortAddress} cohort
|
||||
* @param {string} title
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
@@ -1908,7 +2185,7 @@ function createSingleCostBasisSection(cohort, title) {
|
||||
|
||||
/**
|
||||
* Create cost basis section for grouped cohorts (no percentiles)
|
||||
* @param {readonly (CohortWithAdjusted | CohortBasic)[]} list
|
||||
* @param {readonly (CohortWithAdjusted | CohortBasic | CohortAddress)[]} list
|
||||
* @param {string} title
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
@@ -1954,12 +2231,36 @@ function createGroupedCostBasisSection(list, title) {
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
function createSingleActivitySectionWithAdjusted(ctx, cohort, title) {
|
||||
const { colors } = ctx;
|
||||
const { colors, fromBlockCountWithUnit, fromBitcoinPatternWithUnit } = ctx;
|
||||
const { tree, color } = cohort;
|
||||
|
||||
return {
|
||||
name: "Activity",
|
||||
tree: [
|
||||
{
|
||||
name: "Sent",
|
||||
title: `Sent ${title}`,
|
||||
bottom: [
|
||||
...fromBlockCountWithUnit(
|
||||
tree.activity.sent.sats,
|
||||
Unit.sats,
|
||||
undefined,
|
||||
color,
|
||||
),
|
||||
...fromBitcoinPatternWithUnit(
|
||||
tree.activity.sent.bitcoin,
|
||||
Unit.btc,
|
||||
undefined,
|
||||
color,
|
||||
),
|
||||
...fromBlockCountWithUnit(
|
||||
tree.activity.sent.dollars,
|
||||
Unit.usd,
|
||||
undefined,
|
||||
color,
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Sell Side Risk",
|
||||
title: `Sell Side Risk Ratio ${title}`,
|
||||
@@ -2046,6 +2347,18 @@ function createSingleActivitySectionWithAdjusted(ctx, cohort, title) {
|
||||
unit: Unit.coindays,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: tree.activity.satblocksDestroyed,
|
||||
name: "Satblocks",
|
||||
color,
|
||||
unit: Unit.satblocks,
|
||||
}),
|
||||
line({
|
||||
metric: tree.activity.satdaysDestroyed,
|
||||
name: "Satdays",
|
||||
color,
|
||||
unit: Unit.satdays,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -2055,17 +2368,41 @@ function createSingleActivitySectionWithAdjusted(ctx, cohort, title) {
|
||||
/**
|
||||
* Create activity section without adjusted values (for cohorts with RealizedPattern/2)
|
||||
* @param {PartialContext} ctx
|
||||
* @param {CohortWithPercentiles | CohortBasic} cohort
|
||||
* @param {CohortWithPercentiles | CohortBasic | CohortAddress} cohort
|
||||
* @param {string} title
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
function createSingleActivitySectionBasic(ctx, cohort, title) {
|
||||
const { colors } = ctx;
|
||||
const { colors, fromBlockCountWithUnit, fromBitcoinPatternWithUnit } = ctx;
|
||||
const { tree, color } = cohort;
|
||||
|
||||
return {
|
||||
name: "Activity",
|
||||
tree: [
|
||||
{
|
||||
name: "Sent",
|
||||
title: `Sent ${title}`,
|
||||
bottom: [
|
||||
...fromBlockCountWithUnit(
|
||||
tree.activity.sent.sats,
|
||||
Unit.sats,
|
||||
undefined,
|
||||
color,
|
||||
),
|
||||
...fromBitcoinPatternWithUnit(
|
||||
tree.activity.sent.bitcoin,
|
||||
Unit.btc,
|
||||
undefined,
|
||||
color,
|
||||
),
|
||||
...fromBlockCountWithUnit(
|
||||
tree.activity.sent.dollars,
|
||||
Unit.usd,
|
||||
undefined,
|
||||
color,
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Sell Side Risk",
|
||||
title: `Sell Side Risk Ratio ${title}`,
|
||||
@@ -2140,6 +2477,18 @@ function createSingleActivitySectionBasic(ctx, cohort, title) {
|
||||
unit: Unit.coindays,
|
||||
defaultActive: false,
|
||||
}),
|
||||
line({
|
||||
metric: tree.activity.satblocksDestroyed,
|
||||
name: "Satblocks",
|
||||
color,
|
||||
unit: Unit.satblocks,
|
||||
}),
|
||||
line({
|
||||
metric: tree.activity.satdaysDestroyed,
|
||||
name: "Satdays",
|
||||
color,
|
||||
unit: Unit.satdays,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -2278,7 +2627,7 @@ function createGroupedActivitySectionWithAdjusted(list, title) {
|
||||
|
||||
/**
|
||||
* Create activity section for grouped cohorts without adjusted values (for cohorts with RealizedPattern/2)
|
||||
* @param {readonly (CohortWithPercentiles | CohortBasic)[]} list
|
||||
* @param {readonly (CohortWithPercentiles | CohortBasic | CohortAddress)[]} list
|
||||
* @param {string} title
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user