mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-24 06:39:58 -07:00
global: snapshot
This commit is contained in:
@@ -77,6 +77,127 @@ function volumeAndCoinsTree(activity, color, title) {
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sent in profit/loss breakdown tree (shared by full and mid-level activity)
|
||||
* @param {Brk.BaseCumulativeInSumPattern} sent
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function sentProfitLossTree(sent, title) {
|
||||
return [
|
||||
{
|
||||
name: "Sent In Profit",
|
||||
tree: [
|
||||
{
|
||||
name: "USD",
|
||||
title: title("Sent Volume In Profit"),
|
||||
bottom: [
|
||||
line({ metric: sent.inProfit.base.usd, name: "Base", color: colors.profit, unit: Unit.usd }),
|
||||
line({ metric: sent.inProfit.sum._24h.usd, name: "24h", color: colors.time._24h, unit: Unit.usd, defaultActive: false }),
|
||||
line({ metric: sent.inProfit.sum._1w.usd, name: "1w", color: colors.time._1w, unit: Unit.usd, defaultActive: false }),
|
||||
line({ metric: sent.inProfit.sum._1m.usd, name: "1m", color: colors.time._1m, unit: Unit.usd, defaultActive: false }),
|
||||
line({ metric: sent.inProfit.sum._1y.usd, name: "1y", color: colors.time._1y, unit: Unit.usd, defaultActive: false }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "BTC",
|
||||
title: title("Sent Volume In Profit (BTC)"),
|
||||
bottom: [
|
||||
line({ metric: sent.inProfit.base.btc, name: "Base", color: colors.profit, unit: Unit.btc }),
|
||||
line({ metric: sent.inProfit.sum._24h.btc, name: "24h", color: colors.time._24h, unit: Unit.btc, defaultActive: false }),
|
||||
line({ metric: sent.inProfit.sum._1w.btc, name: "1w", color: colors.time._1w, unit: Unit.btc, defaultActive: false }),
|
||||
line({ metric: sent.inProfit.sum._1m.btc, name: "1m", color: colors.time._1m, unit: Unit.btc, defaultActive: false }),
|
||||
line({ metric: sent.inProfit.sum._1y.btc, name: "1y", color: colors.time._1y, unit: Unit.btc, defaultActive: false }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Sats",
|
||||
title: title("Sent Volume In Profit (Sats)"),
|
||||
bottom: [
|
||||
line({ metric: sent.inProfit.base.sats, name: "Base", color: colors.profit, unit: Unit.sats }),
|
||||
line({ metric: sent.inProfit.sum._24h.sats, name: "24h", color: colors.time._24h, unit: Unit.sats, defaultActive: false }),
|
||||
line({ metric: sent.inProfit.sum._1w.sats, name: "1w", color: colors.time._1w, unit: Unit.sats, defaultActive: false }),
|
||||
line({ metric: sent.inProfit.sum._1m.sats, name: "1m", color: colors.time._1m, unit: Unit.sats, defaultActive: false }),
|
||||
line({ metric: sent.inProfit.sum._1y.sats, name: "1y", color: colors.time._1y, unit: Unit.sats, defaultActive: false }),
|
||||
],
|
||||
},
|
||||
{ name: "Cumulative", title: title("Cumulative Sent In Profit"), bottom: [
|
||||
line({ metric: sent.inProfit.cumulative.usd, name: "USD", color: colors.profit, unit: Unit.usd }),
|
||||
line({ metric: sent.inProfit.cumulative.btc, name: "BTC", color: colors.profit, unit: Unit.btc, defaultActive: false }),
|
||||
line({ metric: sent.inProfit.cumulative.sats, name: "Sats", color: colors.profit, unit: Unit.sats, defaultActive: false }),
|
||||
]},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Sent In Loss",
|
||||
tree: [
|
||||
{
|
||||
name: "USD",
|
||||
title: title("Sent Volume In Loss"),
|
||||
bottom: [
|
||||
line({ metric: sent.inLoss.base.usd, name: "Base", color: colors.loss, unit: Unit.usd }),
|
||||
line({ metric: sent.inLoss.sum._24h.usd, name: "24h", color: colors.time._24h, unit: Unit.usd, defaultActive: false }),
|
||||
line({ metric: sent.inLoss.sum._1w.usd, name: "1w", color: colors.time._1w, unit: Unit.usd, defaultActive: false }),
|
||||
line({ metric: sent.inLoss.sum._1m.usd, name: "1m", color: colors.time._1m, unit: Unit.usd, defaultActive: false }),
|
||||
line({ metric: sent.inLoss.sum._1y.usd, name: "1y", color: colors.time._1y, unit: Unit.usd, defaultActive: false }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "BTC",
|
||||
title: title("Sent Volume In Loss (BTC)"),
|
||||
bottom: [
|
||||
line({ metric: sent.inLoss.base.btc, name: "Base", color: colors.loss, unit: Unit.btc }),
|
||||
line({ metric: sent.inLoss.sum._24h.btc, name: "24h", color: colors.time._24h, unit: Unit.btc, defaultActive: false }),
|
||||
line({ metric: sent.inLoss.sum._1w.btc, name: "1w", color: colors.time._1w, unit: Unit.btc, defaultActive: false }),
|
||||
line({ metric: sent.inLoss.sum._1m.btc, name: "1m", color: colors.time._1m, unit: Unit.btc, defaultActive: false }),
|
||||
line({ metric: sent.inLoss.sum._1y.btc, name: "1y", color: colors.time._1y, unit: Unit.btc, defaultActive: false }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Sats",
|
||||
title: title("Sent Volume In Loss (Sats)"),
|
||||
bottom: [
|
||||
line({ metric: sent.inLoss.base.sats, name: "Base", color: colors.loss, unit: Unit.sats }),
|
||||
line({ metric: sent.inLoss.sum._24h.sats, name: "24h", color: colors.time._24h, unit: Unit.sats, defaultActive: false }),
|
||||
line({ metric: sent.inLoss.sum._1w.sats, name: "1w", color: colors.time._1w, unit: Unit.sats, defaultActive: false }),
|
||||
line({ metric: sent.inLoss.sum._1m.sats, name: "1m", color: colors.time._1m, unit: Unit.sats, defaultActive: false }),
|
||||
line({ metric: sent.inLoss.sum._1y.sats, name: "1y", color: colors.time._1y, unit: Unit.sats, defaultActive: false }),
|
||||
],
|
||||
},
|
||||
{ name: "Cumulative", title: title("Cumulative Sent In Loss"), bottom: [
|
||||
line({ metric: sent.inLoss.cumulative.usd, name: "USD", color: colors.loss, unit: Unit.usd }),
|
||||
line({ metric: sent.inLoss.cumulative.btc, name: "BTC", color: colors.loss, unit: Unit.btc, defaultActive: false }),
|
||||
line({ metric: sent.inLoss.cumulative.sats, name: "Sats", color: colors.loss, unit: Unit.sats, defaultActive: false }),
|
||||
]},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Volume and coins tree with coinyears, dormancy, and sent in profit/loss (All/STH/LTH)
|
||||
* @param {Brk.CoindaysCoinyearsDormancySentPattern} activity
|
||||
* @param {Color} color
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function fullVolumeTree(activity, color, title) {
|
||||
return [
|
||||
...volumeAndCoinsTree(activity, color, title),
|
||||
...sentProfitLossTree(activity.sent, title),
|
||||
{
|
||||
name: "Coinyears Destroyed",
|
||||
title: title("Coinyears Destroyed"),
|
||||
bottom: [line({ metric: activity.coinyearsDestroyed, name: "CYD", color, unit: Unit.years })],
|
||||
},
|
||||
{
|
||||
name: "Dormancy",
|
||||
title: title("Dormancy"),
|
||||
bottom: [line({ metric: activity.dormancy, name: "Dormancy", color, unit: Unit.days })],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Shared SOPR Helpers
|
||||
// ============================================================================
|
||||
@@ -349,7 +470,7 @@ export function createActivitySectionWithAdjusted({ cohort, title }) {
|
||||
return {
|
||||
name: "Activity",
|
||||
tree: [
|
||||
...volumeAndCoinsTree(tree.activity, color, title),
|
||||
...fullVolumeTree(tree.activity, color, title),
|
||||
{
|
||||
name: "SOPR",
|
||||
tree: [
|
||||
@@ -400,7 +521,7 @@ export function createActivitySection({ cohort, title }) {
|
||||
return {
|
||||
name: "Activity",
|
||||
tree: [
|
||||
...volumeAndCoinsTree(tree.activity, color, title),
|
||||
...fullVolumeTree(tree.activity, color, title),
|
||||
{
|
||||
name: "SOPR",
|
||||
tree: singleRollingSoprTree(sopr.ratio, title),
|
||||
@@ -430,6 +551,7 @@ export function createActivitySectionWithActivity({ cohort, title }) {
|
||||
name: "Activity",
|
||||
tree: [
|
||||
...volumeAndCoinsTree(tree.activity, color, title),
|
||||
...sentProfitLossTree(tree.activity.sent, title),
|
||||
{
|
||||
name: "SOPR",
|
||||
title: title("SOPR (24h)"),
|
||||
|
||||
@@ -83,6 +83,7 @@ export function buildCohortData() {
|
||||
title: `UTXOs ${names.long}`,
|
||||
color: colors.at(i, arr.length),
|
||||
tree: utxoCohorts.ageRange[key],
|
||||
matured: utxoCohorts.matured[key],
|
||||
}));
|
||||
|
||||
const epoch = entries(EPOCH_NAMES).map(([key, names], i, arr) => ({
|
||||
|
||||
@@ -115,6 +115,34 @@ function circulatingSupplyPctSeries(supply) {
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Ratio of Circulating Supply series (total, profit, loss)
|
||||
* @param {{ relToCirculating: { ratio: AnyMetricPattern }, inProfit: { relToCirculating: { ratio: AnyMetricPattern } }, inLoss: { relToCirculating: { ratio: AnyMetricPattern } } }} supply
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
function circulatingSupplyRatioSeries(supply) {
|
||||
return [
|
||||
line({
|
||||
metric: supply.relToCirculating.ratio,
|
||||
name: "Total",
|
||||
color: colors.default,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
line({
|
||||
metric: supply.inProfit.relToCirculating.ratio,
|
||||
name: "In Profit",
|
||||
color: colors.profit,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
line({
|
||||
metric: supply.inLoss.relToCirculating.ratio,
|
||||
name: "In Loss",
|
||||
color: colors.loss,
|
||||
unit: Unit.ratio,
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {readonly (UtxoCohortObject | CohortWithoutRelative)[]} list
|
||||
* @param {CohortAll} all
|
||||
@@ -157,7 +185,7 @@ function singleDeltaTree(delta, unit, title, name) {
|
||||
* @template {{ name: string, color: Color }} A
|
||||
* @param {readonly T[]} list
|
||||
* @param {A} all
|
||||
* @param {(c: T | A) => ChangeRatePattern | ChangeRatePattern2} getDelta
|
||||
* @param {(c: T | A) => DeltaPattern} getDelta
|
||||
* @param {Unit} unit
|
||||
* @param {(metric: string) => string} title
|
||||
* @param {string} name
|
||||
@@ -309,11 +337,21 @@ export function createHoldingsSectionWithRelative({ cohort, title }) {
|
||||
tree: [
|
||||
{
|
||||
name: "Supply",
|
||||
title: title("Supply"),
|
||||
bottom: [
|
||||
...fullSupplySeries(supply),
|
||||
...circulatingSupplyPctSeries(supply),
|
||||
...ownSupplyPctSeries(supply),
|
||||
tree: [
|
||||
{
|
||||
name: "Overview",
|
||||
title: title("Supply"),
|
||||
bottom: [
|
||||
...fullSupplySeries(supply),
|
||||
...circulatingSupplyPctSeries(supply),
|
||||
...ownSupplyPctSeries(supply),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Ratio",
|
||||
title: title("Supply (% of Circulating)"),
|
||||
bottom: circulatingSupplyRatioSeries(supply),
|
||||
},
|
||||
],
|
||||
},
|
||||
singleUtxoCountChart(cohort, title),
|
||||
@@ -341,10 +379,20 @@ export function createHoldingsSectionWithOwnSupply({ cohort, title }) {
|
||||
tree: [
|
||||
{
|
||||
name: "Supply",
|
||||
title: title("Supply"),
|
||||
bottom: [
|
||||
...fullSupplySeries(supply),
|
||||
...circulatingSupplyPctSeries(supply),
|
||||
tree: [
|
||||
{
|
||||
name: "Overview",
|
||||
title: title("Supply"),
|
||||
bottom: [
|
||||
...fullSupplySeries(supply),
|
||||
...circulatingSupplyPctSeries(supply),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Ratio",
|
||||
title: title("Supply (% of Circulating)"),
|
||||
bottom: circulatingSupplyRatioSeries(supply),
|
||||
},
|
||||
],
|
||||
},
|
||||
singleUtxoCountChart(cohort, title),
|
||||
@@ -359,6 +407,33 @@ export function createHoldingsSectionWithOwnSupply({ cohort, title }) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Holdings with inProfit/inLoss (no rel, no address count)
|
||||
* For: CohortWithoutRelative (p2ms, unknown, empty)
|
||||
* @param {{ cohort: CohortWithoutRelative, title: (metric: string) => string }} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createHoldingsSectionWithProfitLoss({ cohort, title }) {
|
||||
return {
|
||||
name: "Holdings",
|
||||
tree: [
|
||||
{
|
||||
name: "Supply",
|
||||
title: title("Supply"),
|
||||
bottom: fullSupplySeries(cohort.tree.supply),
|
||||
},
|
||||
singleUtxoCountChart(cohort, title),
|
||||
{
|
||||
name: "Change",
|
||||
tree: [
|
||||
singleDeltaTree(cohort.tree.supply.delta, Unit.sats, title, "Supply"),
|
||||
singleDeltaTree(cohort.tree.outputs.unspentCount.delta, Unit.count, title, "UTXO Count"),
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Holdings for CohortAddress (has inProfit/inLoss but no rel, plus address count)
|
||||
* @param {{ cohort: CohortAddress, title: (metric: string) => string }} args
|
||||
@@ -559,6 +634,66 @@ export function createGroupedHoldingsSection({ list, all, title }) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Grouped holdings with inProfit/inLoss (no rel, no address count)
|
||||
* For: CohortWithoutRelative (p2ms, unknown, empty)
|
||||
* @param {{ list: readonly CohortWithoutRelative[], all: CohortAll, title: (metric: string) => string }} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createGroupedHoldingsSectionWithProfitLoss({
|
||||
list,
|
||||
all,
|
||||
title,
|
||||
}) {
|
||||
return {
|
||||
name: "Holdings",
|
||||
tree: [
|
||||
{
|
||||
name: "Supply",
|
||||
tree: [
|
||||
{
|
||||
name: "Total",
|
||||
title: title("Supply"),
|
||||
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
satsBtcUsd({ pattern: tree.supply.total, name, color }),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "In Profit",
|
||||
title: title("Supply In Profit"),
|
||||
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
satsBtcUsd({
|
||||
pattern: tree.supply.inProfit,
|
||||
name,
|
||||
color,
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "In Loss",
|
||||
title: title("Supply In Loss"),
|
||||
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
satsBtcUsd({
|
||||
pattern: tree.supply.inLoss,
|
||||
name,
|
||||
color,
|
||||
}),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
groupedUtxoCountChart(list, all, title),
|
||||
{
|
||||
name: "Change",
|
||||
tree: [
|
||||
groupedDeltaTree(list, all, (c) => c.tree.supply.delta, Unit.sats, title, "Supply"),
|
||||
groupedDeltaTree(list, all, (c) => c.tree.outputs.unspentCount.delta, Unit.count, title, "UTXO Count"),
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Grouped holdings with inProfit/inLoss + relToCirculating (no relToOwn)
|
||||
* For: CohortWithAdjusted, CohortAgeRange
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* - activity.js: SOPR, Volume, Lifespan
|
||||
*/
|
||||
|
||||
import { formatCohortTitle } from "../shared.js";
|
||||
import { formatCohortTitle, satsBtcUsd } from "../shared.js";
|
||||
|
||||
// Section builders
|
||||
import {
|
||||
@@ -18,9 +18,11 @@ import {
|
||||
createHoldingsSectionAll,
|
||||
createHoldingsSectionAddress,
|
||||
createHoldingsSectionAddressAmount,
|
||||
createHoldingsSectionWithProfitLoss,
|
||||
createHoldingsSectionWithRelative,
|
||||
createHoldingsSectionWithOwnSupply,
|
||||
createGroupedHoldingsSection,
|
||||
createGroupedHoldingsSectionWithProfitLoss,
|
||||
createGroupedHoldingsSectionAddress,
|
||||
createGroupedHoldingsSectionAddressAmount,
|
||||
createGroupedHoldingsSectionWithRelative,
|
||||
@@ -42,14 +44,16 @@ import {
|
||||
createGroupedCostBasisSectionWithPercentiles,
|
||||
} from "./cost-basis.js";
|
||||
import {
|
||||
createProfitabilitySection,
|
||||
createProfitabilitySectionAll,
|
||||
createProfitabilitySectionFull,
|
||||
createProfitabilitySectionWithNupl,
|
||||
createProfitabilitySectionWithInvestedCapitalPct,
|
||||
createProfitabilitySectionBasicWithInvestedCapitalPct,
|
||||
createProfitabilitySectionAddress,
|
||||
createProfitabilitySectionWithProfitLoss,
|
||||
createProfitabilitySectionLongTerm,
|
||||
createGroupedProfitabilitySection,
|
||||
createGroupedProfitabilitySectionWithProfitLoss,
|
||||
createGroupedProfitabilitySectionWithNupl,
|
||||
createGroupedProfitabilitySectionWithInvestedCapitalPct,
|
||||
createGroupedProfitabilitySectionBasicWithInvestedCapitalPct,
|
||||
@@ -126,7 +130,7 @@ export function createCohortFolderWithAdjusted(cohort) {
|
||||
createHoldingsSectionWithOwnSupply({ cohort, title }),
|
||||
createValuationSection({ cohort, title }),
|
||||
createPricesSectionBasic({ cohort, title }),
|
||||
createProfitabilitySectionWithNupl({ cohort, title }),
|
||||
createProfitabilitySectionWithInvestedCapitalPct({ cohort, title }),
|
||||
createActivitySectionWithActivity({ cohort, title }),
|
||||
],
|
||||
};
|
||||
@@ -191,6 +195,22 @@ export function createCohortFolderAgeRange(cohort) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Age range folder with matured supply
|
||||
* @param {CohortAgeRangeWithMatured} cohort
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createCohortFolderAgeRangeWithMatured(cohort) {
|
||||
const folder = createCohortFolderAgeRange(cohort);
|
||||
const title = formatCohortTitle(cohort.name);
|
||||
folder.tree.push({
|
||||
name: "Matured",
|
||||
title: title("Matured Supply"),
|
||||
bottom: satsBtcUsd({ pattern: cohort.matured, name: cohort.name }),
|
||||
});
|
||||
return folder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic folder WITH RelToMarketCap
|
||||
* @param {CohortBasicWithMarketCap} cohort
|
||||
@@ -242,7 +262,7 @@ export function createCohortFolderAddress(cohort) {
|
||||
createHoldingsSectionAddress({ cohort, title }),
|
||||
createValuationSection({ cohort, title }),
|
||||
createPricesSectionBasic({ cohort, title }),
|
||||
createProfitabilitySectionBasicWithInvestedCapitalPct({ cohort, title }),
|
||||
createProfitabilitySectionAddress({ cohort, title }),
|
||||
createActivitySectionMinimal({ cohort, title }),
|
||||
],
|
||||
};
|
||||
@@ -258,10 +278,10 @@ export function createCohortFolderWithoutRelative(cohort) {
|
||||
return {
|
||||
name: cohort.name || "all",
|
||||
tree: [
|
||||
createHoldingsSection({ cohort, title }),
|
||||
createHoldingsSectionWithProfitLoss({ cohort, title }),
|
||||
createValuationSection({ cohort, title }),
|
||||
createPricesSectionBasic({ cohort, title }),
|
||||
createProfitabilitySection({ cohort, title }),
|
||||
createProfitabilitySectionWithProfitLoss({ cohort, title }),
|
||||
createActivitySectionMinimal({ cohort, title }),
|
||||
],
|
||||
};
|
||||
@@ -331,7 +351,11 @@ export function createGroupedCohortFolderWithAdjusted({
|
||||
createGroupedHoldingsSectionWithOwnSupply({ list, all, title }),
|
||||
createGroupedValuationSection({ list, all, title }),
|
||||
createGroupedPricesSection({ list, all, title }),
|
||||
createGroupedProfitabilitySectionWithInvestedCapitalPct({ list, all, title }),
|
||||
createGroupedProfitabilitySectionWithInvestedCapitalPct({
|
||||
list,
|
||||
all,
|
||||
title,
|
||||
}),
|
||||
createGroupedActivitySectionWithActivity({ list, all, title }),
|
||||
],
|
||||
};
|
||||
@@ -412,6 +436,28 @@ export function createGroupedCohortFolderAgeRange({
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{ name: string, title: string, list: readonly CohortAgeRangeWithMatured[], all: CohortAll }} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createGroupedCohortFolderAgeRangeWithMatured({
|
||||
name,
|
||||
title: groupTitle,
|
||||
list,
|
||||
all,
|
||||
}) {
|
||||
const folder = createGroupedCohortFolderAgeRange({ name, title: groupTitle, list, all });
|
||||
const title = formatCohortTitle(groupTitle);
|
||||
folder.tree.push({
|
||||
name: "Matured",
|
||||
title: title("Matured Supply"),
|
||||
bottom: list.flatMap((cohort) =>
|
||||
satsBtcUsd({ pattern: cohort.matured, name: cohort.name, color: cohort.color }),
|
||||
),
|
||||
});
|
||||
return folder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {CohortGroupBasicWithMarketCap} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
@@ -503,10 +549,10 @@ export function createGroupedCohortFolderWithoutRelative({
|
||||
return {
|
||||
name: name || "all",
|
||||
tree: [
|
||||
createGroupedHoldingsSection({ list, all, title }),
|
||||
createGroupedHoldingsSectionWithProfitLoss({ list, all, title }),
|
||||
createGroupedValuationSection({ list, all, title }),
|
||||
createGroupedPricesSection({ list, all, title }),
|
||||
createGroupedProfitabilitySection({ list, all, title }),
|
||||
createGroupedProfitabilitySectionWithProfitLoss({ list, all, title }),
|
||||
createGroupedActivitySectionMinimal({ list, all, title }),
|
||||
],
|
||||
};
|
||||
|
||||
@@ -98,8 +98,8 @@ export function createPricesSectionBasic({ cohort, title }) {
|
||||
top: [price({ metric: tree.realized.price, name: "Realized", color })],
|
||||
},
|
||||
{
|
||||
name: "Ratio",
|
||||
title: title("Realized Price Ratio"),
|
||||
name: "MVRV",
|
||||
title: title("MVRV"),
|
||||
bottom: [
|
||||
baseline({
|
||||
metric: tree.realized.mvrv,
|
||||
@@ -109,6 +109,18 @@ export function createPricesSectionBasic({ cohort, title }) {
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Price Ratio",
|
||||
title: title("Realized Price Ratio"),
|
||||
bottom: [
|
||||
baseline({
|
||||
metric: tree.realized.price.ratio,
|
||||
name: "Price Ratio",
|
||||
unit: Unit.ratio,
|
||||
base: 1,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -105,6 +105,7 @@ function unrealizedPnlTreeAll(u, title) {
|
||||
{ name: "USD", title: title("Unrealized P&L"), bottom: unrealizedUsdSeries(u) },
|
||||
relPnlChart(u.profit.relToMcap, u.loss.relToMcap, "% of Mcap", title),
|
||||
relPnlChart(u.profit.relToOwnGross, u.loss.relToOwnGross, "% of Own P&L", title),
|
||||
...unrealizedCumulativeRollingTree(u.profit, u.loss, title),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -120,6 +121,7 @@ function unrealizedPnlTreeFull(u, title) {
|
||||
relPnlChart(u.profit.relToMcap, u.loss.relToMcap, "% of Mcap", title),
|
||||
relPnlChart(u.profit.relToOwnMcap, u.loss.relToOwnMcap, "% of Own Mcap", title),
|
||||
relPnlChart(u.profit.relToOwnGross, u.loss.relToOwnGross, "% of Own P&L", title),
|
||||
...unrealizedCumulativeRollingTree(u.profit, u.loss, title),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -139,21 +141,89 @@ function unrealizedPnlTreeLongTerm(u, title) {
|
||||
},
|
||||
relPnlChart(u.profit.relToOwnMcap, u.loss.relToOwnMcap, "% of Own Mcap", title),
|
||||
relPnlChart(u.profit.relToOwnGross, u.loss.relToOwnGross, "% of Own P&L", title),
|
||||
...unrealizedCumulativeRollingTree(u.profit, u.loss, title),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Unrealized P&L (USD only) for mid-tier cohorts (AgeRange/MaxAge)
|
||||
* Unrealized P&L tree for mid-tier cohorts (AgeRange/MaxAge)
|
||||
* @param {Brk.LossNetNuplProfitPattern} u
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function unrealizedMid(u) {
|
||||
function unrealizedPnlTreeMid(u, title) {
|
||||
return [
|
||||
...pnlLines(
|
||||
{ profit: u.profit.base.usd, loss: u.loss.base.usd, negLoss: u.loss.negative },
|
||||
Unit.usd,
|
||||
),
|
||||
priceLine({ unit: Unit.usd, defaultActive: false }),
|
||||
{
|
||||
name: "USD",
|
||||
title: title("Unrealized P&L"),
|
||||
bottom: [
|
||||
...pnlLines(
|
||||
{ profit: u.profit.base.usd, loss: u.loss.base.usd, negLoss: u.loss.negative },
|
||||
Unit.usd,
|
||||
),
|
||||
priceLine({ unit: Unit.usd, defaultActive: false }),
|
||||
],
|
||||
},
|
||||
...unrealizedCumulativeRollingTree(u.profit, u.loss, title),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Unrealized cumulative + rolling P&L tree (profit and loss have cumulative.usd + sum[w].usd)
|
||||
* @param {{ cumulative: { usd: AnyMetricPattern }, sum: { _24h: { usd: AnyMetricPattern }, _1w: { usd: AnyMetricPattern }, _1m: { usd: AnyMetricPattern }, _1y: { usd: AnyMetricPattern } } }} profit
|
||||
* @param {{ cumulative: { usd: AnyMetricPattern }, sum: { _24h: { usd: AnyMetricPattern }, _1w: { usd: AnyMetricPattern }, _1m: { usd: AnyMetricPattern }, _1y: { usd: AnyMetricPattern } } }} loss
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function unrealizedCumulativeRollingTree(profit, loss, title) {
|
||||
return [
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: title("Cumulative Unrealized P&L"),
|
||||
bottom: [
|
||||
line({ metric: profit.cumulative.usd, name: "Profit", color: colors.profit, unit: Unit.usd }),
|
||||
line({ metric: loss.cumulative.usd, name: "Loss", color: colors.loss, unit: Unit.usd }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Rolling",
|
||||
tree: [
|
||||
{
|
||||
name: "Profit",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: title("Rolling Unrealized Profit"),
|
||||
bottom: ROLLING_WINDOWS.map((w) =>
|
||||
line({ metric: profit.sum[w.key].usd, name: w.name, color: w.color, unit: Unit.usd }),
|
||||
),
|
||||
},
|
||||
...ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: title(`Unrealized Profit (${w.name})`),
|
||||
bottom: [line({ metric: profit.sum[w.key].usd, name: "Profit", color: colors.profit, unit: Unit.usd })],
|
||||
})),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Loss",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: title("Rolling Unrealized Loss"),
|
||||
bottom: ROLLING_WINDOWS.map((w) =>
|
||||
line({ metric: loss.sum[w.key].usd, name: w.name, color: w.color, unit: Unit.usd }),
|
||||
),
|
||||
},
|
||||
...ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: title(`Unrealized Loss (${w.name})`),
|
||||
bottom: [line({ metric: loss.sum[w.key].usd, name: "Loss", color: colors.loss, unit: Unit.usd })],
|
||||
})),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -320,24 +390,103 @@ function realizedPnlCumulativeTreeFull(r, title) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Net realized P&L delta tree (absolute + rate across all rolling windows)
|
||||
* @param {Brk.BaseChangeCumulativeDeltaRelSumPattern | Brk.BaseCumulativeDeltaSumPattern} netPnl
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
function realizedNetPnlDeltaTree(netPnl, title) {
|
||||
return {
|
||||
name: "Change",
|
||||
tree: [
|
||||
{
|
||||
name: "Absolute",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: title("Net Realized P&L Change"),
|
||||
bottom: ROLLING_WINDOWS.map((w) =>
|
||||
baseline({ metric: netPnl.delta.absolute[w.key].usd, name: w.name, color: w.color, unit: Unit.usd }),
|
||||
),
|
||||
},
|
||||
...ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: title(`Net Realized P&L Change (${w.name})`),
|
||||
bottom: [baseline({ metric: netPnl.delta.absolute[w.key].usd, name: "Change", unit: Unit.usd })],
|
||||
})),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Rate",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: title("Net Realized P&L Rate"),
|
||||
bottom: ROLLING_WINDOWS.flatMap((w) =>
|
||||
percentRatio({ pattern: netPnl.delta.rate[w.key], name: w.name, color: w.color }),
|
||||
),
|
||||
},
|
||||
...ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: title(`Net Realized P&L Rate (${w.name})`),
|
||||
bottom: percentRatioBaseline({ pattern: netPnl.delta.rate[w.key], name: "Rate" }),
|
||||
})),
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Full realized delta tree (absolute + rate + rel to mcap/rcap)
|
||||
* @param {Brk.CapGrossInvestorLossMvrvNetPeakPriceProfitSellSoprPattern | Brk.MetricsTree_Cohorts_Utxo_Lth_Realized} r
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialOptionsTree}
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
function realized30dChangeTreeFull(r, title) {
|
||||
return [
|
||||
{ name: "USD", title: title("Realized P&L 30d Change"), bottom: [baseline({ metric: r.netPnl.delta.absolute._1m.usd, name: "30d Change", unit: Unit.usd })] },
|
||||
{
|
||||
name: "% of Mcap",
|
||||
title: title("Realized 30d Change (% of Mcap)"),
|
||||
bottom: percentRatioBaseline({ pattern: r.netPnl.change1m.relToMcap, name: "30d Change" }),
|
||||
},
|
||||
{
|
||||
name: "% of Rcap",
|
||||
title: title("Realized 30d Change (% of Realized Cap)"),
|
||||
bottom: percentRatioBaseline({ pattern: r.netPnl.change1m.relToRcap, name: "30d Change" }),
|
||||
},
|
||||
];
|
||||
function realizedNetPnlDeltaTreeFull(r, title) {
|
||||
const base = realizedNetPnlDeltaTree(r.netPnl, title);
|
||||
return {
|
||||
...base,
|
||||
tree: [
|
||||
...base.tree,
|
||||
{
|
||||
name: "% of Mcap",
|
||||
title: title("Net Realized P&L Change (% of Mcap)"),
|
||||
bottom: percentRatioBaseline({ pattern: r.netPnl.change1m.relToMcap, name: "30d Change" }),
|
||||
},
|
||||
{
|
||||
name: "% of Rcap",
|
||||
title: title("Net Realized P&L Change (% of Rcap)"),
|
||||
bottom: percentRatioBaseline({ pattern: r.netPnl.change1m.relToRcap, name: "30d Change" }),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Rolling net realized P&L tree (reusable by full and mid realized)
|
||||
* @param {{ sum: { _24h: { usd: AnyMetricPattern }, _1w: { usd: AnyMetricPattern }, _1m: { usd: AnyMetricPattern }, _1y: { usd: AnyMetricPattern } } }} netPnl
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
function rollingNetRealizedTree(netPnl, title) {
|
||||
return {
|
||||
name: "Net",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: title("Rolling Net Realized P&L"),
|
||||
bottom: ROLLING_WINDOWS.map((w) =>
|
||||
baseline({ metric: netPnl.sum[w.key].usd, name: w.name, color: w.color, unit: Unit.usd }),
|
||||
),
|
||||
},
|
||||
...ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: title(`Net Realized P&L (${w.name})`),
|
||||
bottom: [baseline({ metric: netPnl.sum[w.key].usd, name: "Net", unit: Unit.usd })],
|
||||
})),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -382,23 +531,7 @@ function singleRollingRealizedTreeFull(r, title) {
|
||||
})),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Net",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: title("Rolling Net Realized P&L"),
|
||||
bottom: ROLLING_WINDOWS.map((w) =>
|
||||
baseline({ metric: r.netPnl.sum[w.key].usd, name: w.name, color: w.color, unit: Unit.usd }),
|
||||
),
|
||||
},
|
||||
...ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: title(`Net Realized P&L (${w.name})`),
|
||||
bottom: [baseline({ metric: r.netPnl.sum[w.key].usd, name: "Net", unit: Unit.usd })],
|
||||
})),
|
||||
],
|
||||
},
|
||||
rollingNetRealizedTree(r.netPnl, title),
|
||||
{
|
||||
name: "P/L Ratio",
|
||||
tree: [
|
||||
@@ -451,6 +584,96 @@ function singleRollingRealizedTreeBasic(profit, loss, title) {
|
||||
// Realized Subfolder Builders
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Value Created/Destroyed tree for a single P&L side (profit or loss)
|
||||
* @param {CountPattern<number>} valueCreated
|
||||
* @param {CountPattern<number>} valueDestroyed
|
||||
* @param {string} label - "Profit" or "Loss"
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
function realizedValueTree(valueCreated, valueDestroyed, label, title) {
|
||||
return {
|
||||
name: label,
|
||||
tree: [
|
||||
{
|
||||
name: "Rolling",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: title(`${label} Value Created vs Destroyed`),
|
||||
bottom: ROLLING_WINDOWS.flatMap((w) => [
|
||||
line({ metric: valueCreated.sum[w.key], name: `Created (${w.name})`, color: w.color, unit: Unit.usd }),
|
||||
line({ metric: valueDestroyed.sum[w.key], name: `Destroyed (${w.name})`, color: w.color, unit: Unit.usd, style: 2 }),
|
||||
]),
|
||||
},
|
||||
...ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: title(`${label} Value (${w.name})`),
|
||||
bottom: [
|
||||
line({ metric: valueCreated.sum[w.key], name: "Created", color: colors.profit, unit: Unit.usd }),
|
||||
line({ metric: valueDestroyed.sum[w.key], name: "Destroyed", color: colors.loss, unit: Unit.usd }),
|
||||
],
|
||||
})),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Cumulative",
|
||||
title: title(`Cumulative ${label} Value`),
|
||||
bottom: [
|
||||
line({ metric: valueCreated.cumulative, name: "Created", color: colors.profit, unit: Unit.usd }),
|
||||
line({ metric: valueDestroyed.cumulative, name: "Destroyed", color: colors.loss, unit: Unit.usd }),
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Investor price percentiles tree (pct1/2/5/95/98/99)
|
||||
* @param {InvestorPercentilesPattern} percentiles
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
function investorPricePercentilesTree(percentiles, title) {
|
||||
/** @type {readonly [InvestorPercentileEntry, string, Color][]} */
|
||||
const pcts = [
|
||||
[percentiles.pct99, "p99", colors.stat.max],
|
||||
[percentiles.pct98, "p98", colors.stat.pct90],
|
||||
[percentiles.pct95, "p95", colors.stat.pct75],
|
||||
[percentiles.pct5, "p5", colors.stat.pct25],
|
||||
[percentiles.pct2, "p2", colors.stat.pct10],
|
||||
[percentiles.pct1, "p1", colors.stat.min],
|
||||
];
|
||||
|
||||
return {
|
||||
name: "Percentiles",
|
||||
tree: [
|
||||
{
|
||||
name: "USD",
|
||||
title: title("Investor Price Percentiles"),
|
||||
bottom: pcts.map(([p, name, color]) =>
|
||||
line({ metric: p.price.usd, name, color, unit: Unit.usd }),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Sats",
|
||||
title: title("Investor Price Percentiles (Sats)"),
|
||||
bottom: pcts.map(([p, name, color]) =>
|
||||
line({ metric: p.price.sats, name, color, unit: Unit.sats }),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Ratio",
|
||||
title: title("Investor Price Percentile Ratios"),
|
||||
bottom: pcts.map(([p, name, color]) =>
|
||||
baseline({ metric: p.ratio, name, color, unit: Unit.ratio }),
|
||||
),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Full realized subfolder (All/STH/LTH)
|
||||
* @param {Brk.CapGrossInvestorLossMvrvNetPeakPriceProfitSellSoprPattern | Brk.MetricsTree_Cohorts_Utxo_Lth_Realized} r
|
||||
@@ -463,7 +686,7 @@ function realizedSubfolderFull(r, title) {
|
||||
tree: [
|
||||
{ name: "P&L", tree: realizedPnlSumTreeFull(r, title) },
|
||||
{ name: "Net", tree: realizedNetPnlSumTreeFull(r, title) },
|
||||
{ name: "30d Change", tree: realized30dChangeTreeFull(r, title) },
|
||||
realizedNetPnlDeltaTreeFull(r, title),
|
||||
{
|
||||
name: "Gross P&L",
|
||||
tree: [
|
||||
@@ -488,6 +711,13 @@ function realizedSubfolderFull(r, title) {
|
||||
{ name: "Cumulative", title: title("Total Realized P&L"), bottom: [line({ metric: r.grossPnl.cumulative.usd, name: "Total", unit: Unit.usd, color: colors.bitcoin })] },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Value",
|
||||
tree: [
|
||||
realizedValueTree(r.profit.valueCreated, r.profit.valueDestroyed, "Profit", title),
|
||||
realizedValueTree(r.loss.valueCreated, r.loss.valueDestroyed, "Loss", title),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "P/L Ratio",
|
||||
title: title("Realized Profit/Loss Ratio"),
|
||||
@@ -498,6 +728,12 @@ function realizedSubfolderFull(r, title) {
|
||||
title: title("Realized Peak Regret"),
|
||||
bottom: [line({ metric: r.peakRegret.base, name: "Peak Regret", unit: Unit.usd })],
|
||||
},
|
||||
{
|
||||
name: "Investor Price",
|
||||
tree: [
|
||||
investorPricePercentilesTree(r.investor.price.percentiles, title),
|
||||
],
|
||||
},
|
||||
{ name: "Rolling", tree: singleRollingRealizedTreeFull(r, title) },
|
||||
{
|
||||
name: "Cumulative",
|
||||
@@ -555,12 +791,14 @@ function realizedSubfolderMid(r, title) {
|
||||
title: title("Net Realized P&L"),
|
||||
bottom: [dotsBaseline({ metric: r.netPnl.base.usd, name: "Net", unit: Unit.usd })],
|
||||
},
|
||||
realizedNetPnlDeltaTree(r.netPnl, title),
|
||||
{
|
||||
name: "30d Change",
|
||||
title: title("Realized P&L 30d Change"),
|
||||
bottom: [baseline({ metric: r.netPnl.delta.absolute._1m.usd, name: "30d Change", unit: Unit.usd })],
|
||||
name: "Rolling",
|
||||
tree: [
|
||||
...singleRollingRealizedTreeBasic(r.profit, r.loss, title),
|
||||
rollingNetRealizedTree(r.netPnl, title),
|
||||
],
|
||||
},
|
||||
{ name: "Rolling", tree: singleRollingRealizedTreeBasic(r.profit, r.loss, title) },
|
||||
{
|
||||
name: "Cumulative",
|
||||
tree: [
|
||||
@@ -620,7 +858,7 @@ function realizedSubfolderBasic(r, title) {
|
||||
|
||||
/**
|
||||
* Basic profitability section (NUPL only unrealized, basic realized)
|
||||
* @param {{ cohort: UtxoCohortObject | CohortWithoutRelative, title: (metric: string) => string }} args
|
||||
* @param {{ cohort: UtxoCohortObject, title: (metric: string) => string }} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createProfitabilitySection({ cohort, title }) {
|
||||
@@ -639,6 +877,42 @@ export function createProfitabilitySection({ cohort, title }) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Profitability section with unrealized P&L + NUPL (no netPnl, no rel)
|
||||
* For: CohortWithoutRelative (p2ms, unknown, empty)
|
||||
* @param {{ cohort: CohortWithoutRelative, title: (metric: string) => string }} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createProfitabilitySectionWithProfitLoss({ cohort, title }) {
|
||||
const u = cohort.tree.unrealized;
|
||||
return {
|
||||
name: "Profitability",
|
||||
tree: [
|
||||
{
|
||||
name: "Unrealized",
|
||||
tree: [
|
||||
{
|
||||
name: "P&L",
|
||||
tree: [
|
||||
{
|
||||
name: "USD",
|
||||
title: title("Unrealized P&L"),
|
||||
bottom: [
|
||||
...pnlLines({ profit: u.profit.base.usd, loss: u.loss.base.usd, negLoss: u.loss.negative }, Unit.usd),
|
||||
priceLine({ unit: Unit.usd, defaultActive: false }),
|
||||
],
|
||||
},
|
||||
...unrealizedCumulativeRollingTree(u.profit, u.loss, title),
|
||||
],
|
||||
},
|
||||
{ name: "NUPL", title: title("NUPL"), bottom: nuplSeries(u.nupl) },
|
||||
],
|
||||
},
|
||||
realizedSubfolderBasic(cohort.tree.realized, title),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Section for All cohort
|
||||
* @param {{ cohort: CohortAll, title: (metric: string) => string }} args
|
||||
@@ -764,7 +1038,7 @@ export function createProfitabilitySectionWithInvestedCapitalPct({ cohort, title
|
||||
{
|
||||
name: "Unrealized",
|
||||
tree: [
|
||||
{ name: "P&L", title: title("Unrealized P&L"), bottom: unrealizedMid(u) },
|
||||
{ name: "P&L", tree: unrealizedPnlTreeMid(u, title) },
|
||||
{ name: "Net P&L", title: title("Net Unrealized P&L"), bottom: netUnrealizedMid(u) },
|
||||
{ name: "NUPL", title: title("NUPL"), bottom: nuplSeries(u.nupl) },
|
||||
],
|
||||
@@ -795,6 +1069,41 @@ export function createProfitabilitySectionBasicWithInvestedCapitalPct({ cohort,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Section for CohortAddress (has unrealized profit/loss + NUPL, basic realized)
|
||||
* @param {{ cohort: CohortAddress, title: (metric: string) => string }} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createProfitabilitySectionAddress({ cohort, title }) {
|
||||
const u = cohort.tree.unrealized;
|
||||
return {
|
||||
name: "Profitability",
|
||||
tree: [
|
||||
{
|
||||
name: "Unrealized",
|
||||
tree: [
|
||||
{
|
||||
name: "P&L",
|
||||
tree: [
|
||||
{
|
||||
name: "USD",
|
||||
title: title("Unrealized P&L"),
|
||||
bottom: [
|
||||
...pnlLines({ profit: u.profit.base.usd, loss: u.loss.base.usd, negLoss: u.loss.negative }, Unit.usd),
|
||||
priceLine({ unit: Unit.usd, defaultActive: false }),
|
||||
],
|
||||
},
|
||||
...unrealizedCumulativeRollingTree(u.profit, u.loss, title),
|
||||
],
|
||||
},
|
||||
{ name: "NUPL", title: title("NUPL"), bottom: nuplSeries(u.nupl) },
|
||||
],
|
||||
},
|
||||
realizedSubfolderBasic(cohort.tree.realized, title),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Grouped Cohort Helpers
|
||||
// ============================================================================
|
||||
@@ -945,6 +1254,63 @@ function groupedRealizedSubfolder(list, all, title) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Grouped net realized P&L delta (Absolute + Rate with all rolling windows)
|
||||
* @param {readonly (CohortAll | CohortFull | CohortLongTerm)[]} list
|
||||
* @param {CohortAll} all
|
||||
* @param {(metric: string) => string} title
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
function groupedRealizedNetPnlDeltaTree(list, all, title) {
|
||||
return {
|
||||
name: "Change",
|
||||
tree: [
|
||||
{
|
||||
name: "Absolute",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: title("Net Realized P&L Change"),
|
||||
bottom: ROLLING_WINDOWS.flatMap((w) =>
|
||||
mapCohortsWithAll(list, all, ({ name, tree }) =>
|
||||
baseline({ metric: tree.realized.netPnl.delta.absolute[w.key].usd, name: `${name} (${w.name})`, color: w.color, unit: Unit.usd }),
|
||||
),
|
||||
),
|
||||
},
|
||||
...ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: title(`Net Realized P&L Change (${w.name})`),
|
||||
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
baseline({ metric: tree.realized.netPnl.delta.absolute[w.key].usd, name, color, unit: Unit.usd }),
|
||||
),
|
||||
})),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Rate",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title: title("Net Realized P&L Rate"),
|
||||
bottom: ROLLING_WINDOWS.flatMap((w) =>
|
||||
flatMapCohortsWithAll(list, all, ({ name, tree }) =>
|
||||
percentRatio({ pattern: tree.realized.netPnl.delta.rate[w.key], name: `${name} (${w.name})`, color: w.color }),
|
||||
),
|
||||
),
|
||||
},
|
||||
...ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: title(`Net Realized P&L Rate (${w.name})`),
|
||||
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
percentRatio({ pattern: tree.realized.netPnl.delta.rate[w.key], name, color }),
|
||||
),
|
||||
})),
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Grouped realized subfolder for full cohorts
|
||||
* @param {readonly (CohortAll | CohortFull | CohortLongTerm)[]} list
|
||||
@@ -964,13 +1330,7 @@ function groupedRealizedSubfolderFull(list, all, title) {
|
||||
baseline({ metric: tree.realized.netPnl.base.usd, name, color, unit: Unit.usd }),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "30d Change",
|
||||
title: title("Realized P&L 30d Change"),
|
||||
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
baseline({ metric: tree.realized.netPnl.delta.absolute._1m.usd, name, color, unit: Unit.usd }),
|
||||
),
|
||||
},
|
||||
groupedRealizedNetPnlDeltaTree(list, all, title),
|
||||
{ name: "Rolling", tree: groupedRollingRealizedChartsFull(list, all, title) },
|
||||
{
|
||||
name: "Cumulative",
|
||||
@@ -1265,6 +1625,41 @@ export function createGroupedProfitabilitySection({ list, all, title }) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Grouped profitability with unrealized profit/loss + NUPL
|
||||
* For: CohortWithoutRelative (p2ms, unknown, empty)
|
||||
* @param {{ list: readonly CohortWithoutRelative[], all: CohortAll, title: (metric: string) => string }} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createGroupedProfitabilitySectionWithProfitLoss({ list, all, title }) {
|
||||
return {
|
||||
name: "Profitability",
|
||||
tree: [
|
||||
{
|
||||
name: "Unrealized",
|
||||
tree: [
|
||||
{
|
||||
name: "Profit",
|
||||
title: title("Unrealized Profit"),
|
||||
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
line({ metric: tree.unrealized.profit.base.usd, name, color, unit: Unit.usd }),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Loss",
|
||||
title: title("Unrealized Loss"),
|
||||
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
line({ metric: tree.unrealized.loss.base.usd, name, color, unit: Unit.usd }),
|
||||
),
|
||||
},
|
||||
...groupedNuplCharts(list, all, title),
|
||||
],
|
||||
},
|
||||
groupedRealizedSubfolder(list, all, title),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Grouped section with invested capital % (basic cohorts — uses NUPL only)
|
||||
* @param {{ list: readonly CohortBasicWithoutMarketCap[], all: CohortAll, title: (metric: string) => string }} args
|
||||
|
||||
89
website/scripts/options/distribution/utxo-profitability.js
Normal file
89
website/scripts/options/distribution/utxo-profitability.js
Normal file
@@ -0,0 +1,89 @@
|
||||
/** UTXO Profitability section — range bands, cumulative profit/loss thresholds */
|
||||
|
||||
import { colors } from "../../utils/colors.js";
|
||||
import { entries } from "../../utils/array.js";
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { line, price } from "../series.js";
|
||||
import { brk } from "../../client.js";
|
||||
import { satsBtcUsd } from "../shared.js";
|
||||
|
||||
/**
|
||||
* @param {{ name: string, color: Color, pattern: RealizedSupplyPattern }[]} list
|
||||
* @param {string} titlePrefix
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function bucketCharts(list, titlePrefix) {
|
||||
return [
|
||||
{
|
||||
name: "Supply",
|
||||
title: `${titlePrefix}: Supply`,
|
||||
bottom: list.flatMap(({ name, color, pattern }) =>
|
||||
satsBtcUsd({ pattern: pattern.supply, name, color }),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Realized Cap",
|
||||
title: `${titlePrefix}: Realized Cap`,
|
||||
bottom: list.map(({ name, color, pattern }) =>
|
||||
line({ metric: pattern.realizedCap, name, color, unit: Unit.usd }),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Realized Price",
|
||||
title: `${titlePrefix}: Realized Price`,
|
||||
top: list.map(({ name, color, pattern }) =>
|
||||
price({ metric: pattern.realizedPrice, name, color }),
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createUtxoProfitabilitySection() {
|
||||
const { range, profit, loss } = brk.metrics.cohorts.utxo.profitability;
|
||||
const {
|
||||
PROFITABILITY_RANGE_NAMES,
|
||||
PROFIT_NAMES,
|
||||
LOSS_NAMES,
|
||||
} = brk;
|
||||
|
||||
const rangeList = entries(PROFITABILITY_RANGE_NAMES).map(
|
||||
([key, names], i, arr) => ({
|
||||
name: names.short,
|
||||
color: colors.at(i, arr.length),
|
||||
pattern: range[key],
|
||||
}),
|
||||
);
|
||||
|
||||
const profitList = entries(PROFIT_NAMES).map(([key, names], i, arr) => ({
|
||||
name: names.short,
|
||||
color: colors.at(i, arr.length),
|
||||
pattern: profit[key],
|
||||
}));
|
||||
|
||||
const lossList = entries(LOSS_NAMES).map(([key, names], i, arr) => ({
|
||||
name: names.short,
|
||||
color: colors.at(i, arr.length),
|
||||
pattern: loss[key],
|
||||
}));
|
||||
|
||||
return {
|
||||
name: "UTXO Profitability",
|
||||
tree: [
|
||||
{
|
||||
name: "Range",
|
||||
tree: bucketCharts(rangeList, "Profitability Range"),
|
||||
},
|
||||
{
|
||||
name: "In Profit",
|
||||
tree: bucketCharts(profitList, "In Profit"),
|
||||
},
|
||||
{
|
||||
name: "In Loss",
|
||||
tree: bucketCharts(lossList, "In Loss"),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -36,6 +36,18 @@ import { periodIdToName } from "./utils.js";
|
||||
* @property {Brk.BpsCentsRatioSatsUsdPattern} ratio
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create index (percent) + ratio line pair from a BpsPercentRatioPattern
|
||||
* @param {{ pattern: { percent: AnyMetricPattern, ratio: AnyMetricPattern }, name: string, color?: Color, defaultActive?: boolean }} args
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
function indexRatio({ pattern, name, color, defaultActive }) {
|
||||
return [
|
||||
line({ metric: pattern.percent, name, color, defaultActive, unit: Unit.index }),
|
||||
line({ metric: pattern.ratio, name, color, defaultActive, unit: Unit.ratio }),
|
||||
];
|
||||
}
|
||||
|
||||
const commonMaIds = /** @type {const} */ ([
|
||||
"1w",
|
||||
"1m",
|
||||
@@ -671,166 +683,43 @@ export function createMarketSection() {
|
||||
name: "Compare",
|
||||
title: "RSI Comparison",
|
||||
bottom: [
|
||||
line({
|
||||
metric: technical.rsi._24h.rsi.percent,
|
||||
name: "1d",
|
||||
color: colors.time._24h,
|
||||
unit: Unit.index,
|
||||
}),
|
||||
line({
|
||||
metric: technical.rsi._1w.rsi.percent,
|
||||
name: "1w",
|
||||
color: colors.time._1w,
|
||||
unit: Unit.index,
|
||||
}),
|
||||
line({
|
||||
metric: technical.rsi._1m.rsi.percent,
|
||||
name: "1m",
|
||||
color: colors.time._1m,
|
||||
unit: Unit.index,
|
||||
}),
|
||||
line({
|
||||
metric: technical.rsi._1y.rsi.percent,
|
||||
name: "1y",
|
||||
color: colors.time._1y,
|
||||
unit: Unit.index,
|
||||
}),
|
||||
...ROLLING_WINDOWS.flatMap((w) =>
|
||||
indexRatio({ pattern: technical.rsi[w.key].rsi, name: w.name, color: w.color }),
|
||||
),
|
||||
priceLine({ unit: Unit.index, number: 70 }),
|
||||
priceLine({ unit: Unit.index, number: 30 }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "1 Day",
|
||||
title: "RSI (1d)",
|
||||
bottom: [
|
||||
line({
|
||||
metric: technical.rsi._24h.rsi.percent,
|
||||
name: "RSI",
|
||||
color: colors.indicator.main,
|
||||
unit: Unit.index,
|
||||
}),
|
||||
line({
|
||||
metric: technical.rsi._24h.rsiMax.percent,
|
||||
name: "Max",
|
||||
color: colors.stat.max,
|
||||
defaultActive: false,
|
||||
unit: Unit.index,
|
||||
}),
|
||||
line({
|
||||
metric: technical.rsi._24h.rsiMin.percent,
|
||||
name: "Min",
|
||||
color: colors.stat.min,
|
||||
defaultActive: false,
|
||||
unit: Unit.index,
|
||||
}),
|
||||
priceLine({ unit: Unit.index, number: 70 }),
|
||||
priceLine({
|
||||
unit: Unit.index,
|
||||
number: 50,
|
||||
defaultActive: false,
|
||||
}),
|
||||
priceLine({ unit: Unit.index, number: 30 }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "1 Week",
|
||||
title: "RSI (1w)",
|
||||
bottom: [
|
||||
line({
|
||||
metric: technical.rsi._1w.rsi.percent,
|
||||
name: "RSI",
|
||||
color: colors.indicator.main,
|
||||
unit: Unit.index,
|
||||
}),
|
||||
line({
|
||||
metric: technical.rsi._1w.rsiMax.percent,
|
||||
name: "Max",
|
||||
color: colors.stat.max,
|
||||
defaultActive: false,
|
||||
unit: Unit.index,
|
||||
}),
|
||||
line({
|
||||
metric: technical.rsi._1w.rsiMin.percent,
|
||||
name: "Min",
|
||||
color: colors.stat.min,
|
||||
defaultActive: false,
|
||||
unit: Unit.index,
|
||||
}),
|
||||
priceLine({ unit: Unit.index, number: 70 }),
|
||||
priceLine({
|
||||
unit: Unit.index,
|
||||
number: 50,
|
||||
defaultActive: false,
|
||||
}),
|
||||
priceLine({ unit: Unit.index, number: 30 }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "1 Month",
|
||||
title: "RSI (1m)",
|
||||
bottom: [
|
||||
line({
|
||||
metric: technical.rsi._1m.rsi.percent,
|
||||
name: "RSI",
|
||||
color: colors.indicator.main,
|
||||
unit: Unit.index,
|
||||
}),
|
||||
line({
|
||||
metric: technical.rsi._1m.rsiMax.percent,
|
||||
name: "Max",
|
||||
color: colors.stat.max,
|
||||
defaultActive: false,
|
||||
unit: Unit.index,
|
||||
}),
|
||||
line({
|
||||
metric: technical.rsi._1m.rsiMin.percent,
|
||||
name: "Min",
|
||||
color: colors.stat.min,
|
||||
defaultActive: false,
|
||||
unit: Unit.index,
|
||||
}),
|
||||
priceLine({ unit: Unit.index, number: 70 }),
|
||||
priceLine({
|
||||
unit: Unit.index,
|
||||
number: 50,
|
||||
defaultActive: false,
|
||||
}),
|
||||
priceLine({ unit: Unit.index, number: 30 }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "1 Year",
|
||||
title: "RSI (1y)",
|
||||
bottom: [
|
||||
line({
|
||||
metric: technical.rsi._1y.rsi.percent,
|
||||
name: "RSI",
|
||||
color: colors.indicator.main,
|
||||
unit: Unit.index,
|
||||
}),
|
||||
line({
|
||||
metric: technical.rsi._1y.rsiMax.percent,
|
||||
name: "Max",
|
||||
color: colors.stat.max,
|
||||
defaultActive: false,
|
||||
unit: Unit.index,
|
||||
}),
|
||||
line({
|
||||
metric: technical.rsi._1y.rsiMin.percent,
|
||||
name: "Min",
|
||||
color: colors.stat.min,
|
||||
defaultActive: false,
|
||||
unit: Unit.index,
|
||||
}),
|
||||
priceLine({ unit: Unit.index, number: 70 }),
|
||||
priceLine({
|
||||
unit: Unit.index,
|
||||
number: 50,
|
||||
defaultActive: false,
|
||||
}),
|
||||
priceLine({ unit: Unit.index, number: 30 }),
|
||||
],
|
||||
},
|
||||
...ROLLING_WINDOWS.map((w) => {
|
||||
const rsi = technical.rsi[w.key];
|
||||
return {
|
||||
name: w.name,
|
||||
tree: [
|
||||
{
|
||||
name: "Value",
|
||||
title: `RSI (${w.name})`,
|
||||
bottom: [
|
||||
...indexRatio({ pattern: rsi.rsi, name: "RSI", color: colors.indicator.main }),
|
||||
...indexRatio({ pattern: rsi.rsiMax, name: "Max", color: colors.stat.max, defaultActive: false }),
|
||||
...indexRatio({ pattern: rsi.rsiMin, name: "Min", color: colors.stat.min, defaultActive: false }),
|
||||
priceLine({ unit: Unit.index, number: 70 }),
|
||||
priceLine({ unit: Unit.index, number: 50, defaultActive: false }),
|
||||
priceLine({ unit: Unit.index, number: 30 }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Components",
|
||||
title: `RSI Components (${w.name})`,
|
||||
bottom: [
|
||||
line({ metric: rsi.averageGain, name: "Avg Gain", color: colors.profit, unit: Unit.usd }),
|
||||
line({ metric: rsi.averageLoss, name: "Avg Loss", color: colors.loss, unit: Unit.usd }),
|
||||
line({ metric: rsi.gains, name: "Gains", color: colors.profit, defaultActive: false, unit: Unit.usd }),
|
||||
line({ metric: rsi.losses, name: "Losses", color: colors.loss, defaultActive: false, unit: Unit.usd }),
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -840,127 +729,33 @@ export function createMarketSection() {
|
||||
name: "Compare",
|
||||
title: "Stochastic RSI Comparison",
|
||||
bottom: [
|
||||
line({
|
||||
metric: technical.rsi._24h.stochRsiK.percent,
|
||||
name: "1d K",
|
||||
color: colors.time._24h,
|
||||
unit: Unit.index,
|
||||
}),
|
||||
line({
|
||||
metric: technical.rsi._1w.stochRsiK.percent,
|
||||
name: "1w K",
|
||||
color: colors.time._1w,
|
||||
unit: Unit.index,
|
||||
}),
|
||||
line({
|
||||
metric: technical.rsi._1m.stochRsiK.percent,
|
||||
name: "1m K",
|
||||
color: colors.time._1m,
|
||||
unit: Unit.index,
|
||||
}),
|
||||
line({
|
||||
metric: technical.rsi._1y.stochRsiK.percent,
|
||||
name: "1y K",
|
||||
color: colors.time._1y,
|
||||
unit: Unit.index,
|
||||
}),
|
||||
...priceLines({ unit: Unit.index, numbers: [80, 20] }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "1 Day",
|
||||
title: "Stochastic RSI (1d)",
|
||||
bottom: [
|
||||
line({
|
||||
metric: technical.rsi._24h.stochRsiK.percent,
|
||||
name: "K",
|
||||
color: colors.indicator.fast,
|
||||
unit: Unit.index,
|
||||
}),
|
||||
line({
|
||||
metric: technical.rsi._24h.stochRsiD.percent,
|
||||
name: "D",
|
||||
color: colors.indicator.slow,
|
||||
unit: Unit.index,
|
||||
}),
|
||||
...priceLines({ unit: Unit.index, numbers: [80, 20] }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "1 Week",
|
||||
title: "Stochastic RSI (1w)",
|
||||
bottom: [
|
||||
line({
|
||||
metric: technical.rsi._1w.stochRsiK.percent,
|
||||
name: "K",
|
||||
color: colors.indicator.fast,
|
||||
unit: Unit.index,
|
||||
}),
|
||||
line({
|
||||
metric: technical.rsi._1w.stochRsiD.percent,
|
||||
name: "D",
|
||||
color: colors.indicator.slow,
|
||||
unit: Unit.index,
|
||||
}),
|
||||
...priceLines({ unit: Unit.index, numbers: [80, 20] }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "1 Month",
|
||||
title: "Stochastic RSI (1m)",
|
||||
bottom: [
|
||||
line({
|
||||
metric: technical.rsi._1m.stochRsiK.percent,
|
||||
name: "K",
|
||||
color: colors.indicator.fast,
|
||||
unit: Unit.index,
|
||||
}),
|
||||
line({
|
||||
metric: technical.rsi._1m.stochRsiD.percent,
|
||||
name: "D",
|
||||
color: colors.indicator.slow,
|
||||
unit: Unit.index,
|
||||
}),
|
||||
...priceLines({ unit: Unit.index, numbers: [80, 20] }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "1 Year",
|
||||
title: "Stochastic RSI (1y)",
|
||||
bottom: [
|
||||
line({
|
||||
metric: technical.rsi._1y.stochRsiK.percent,
|
||||
name: "K",
|
||||
color: colors.indicator.fast,
|
||||
unit: Unit.index,
|
||||
}),
|
||||
line({
|
||||
metric: technical.rsi._1y.stochRsiD.percent,
|
||||
name: "D",
|
||||
color: colors.indicator.slow,
|
||||
unit: Unit.index,
|
||||
}),
|
||||
...ROLLING_WINDOWS.flatMap((w) =>
|
||||
indexRatio({ pattern: technical.rsi[w.key].stochRsiK, name: `${w.name} K`, color: w.color }),
|
||||
),
|
||||
...priceLines({ unit: Unit.index, numbers: [80, 20] }),
|
||||
],
|
||||
},
|
||||
...ROLLING_WINDOWS.map((w) => {
|
||||
const rsi = technical.rsi[w.key];
|
||||
return {
|
||||
name: w.name,
|
||||
title: `Stochastic RSI (${w.name})`,
|
||||
bottom: [
|
||||
...indexRatio({ pattern: rsi.stochRsi, name: "Raw", color: colors.indicator.main, defaultActive: false }),
|
||||
...indexRatio({ pattern: rsi.stochRsiK, name: "K", color: colors.indicator.fast }),
|
||||
...indexRatio({ pattern: rsi.stochRsiD, name: "D", color: colors.indicator.slow }),
|
||||
...priceLines({ unit: Unit.index, numbers: [80, 20] }),
|
||||
],
|
||||
};
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Stochastic",
|
||||
title: "Stochastic Oscillator",
|
||||
bottom: [
|
||||
line({
|
||||
metric: technical.stochK.percent,
|
||||
name: "K",
|
||||
color: colors.indicator.fast,
|
||||
unit: Unit.index,
|
||||
}),
|
||||
line({
|
||||
metric: technical.stochD.percent,
|
||||
name: "D",
|
||||
color: colors.indicator.slow,
|
||||
unit: Unit.index,
|
||||
}),
|
||||
...indexRatio({ pattern: technical.stochK, name: "K", color: colors.indicator.fast }),
|
||||
...indexRatio({ pattern: technical.stochD, name: "D", color: colors.indicator.slow }),
|
||||
...priceLines({ unit: Unit.index, numbers: [80, 20] }),
|
||||
],
|
||||
},
|
||||
@@ -970,32 +765,9 @@ export function createMarketSection() {
|
||||
{
|
||||
name: "Compare",
|
||||
title: "MACD Comparison",
|
||||
bottom: [
|
||||
line({
|
||||
metric: technical.macd._24h.line,
|
||||
name: "1d",
|
||||
color: colors.time._24h,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: technical.macd._1w.line,
|
||||
name: "1w",
|
||||
color: colors.time._1w,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: technical.macd._1m.line,
|
||||
name: "1m",
|
||||
color: colors.time._1m,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
line({
|
||||
metric: technical.macd._1y.line,
|
||||
name: "1y",
|
||||
color: colors.time._1y,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
],
|
||||
bottom: ROLLING_WINDOWS.map((w) =>
|
||||
line({ metric: technical.macd[w.key].line, name: w.name, color: w.color, unit: Unit.usd }),
|
||||
),
|
||||
},
|
||||
...ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
|
||||
@@ -118,7 +118,6 @@ export function createNetworkSection() {
|
||||
},
|
||||
]);
|
||||
|
||||
|
||||
const countTypes = /** @type {const} */ ([
|
||||
{
|
||||
name: "Funded",
|
||||
@@ -568,7 +567,7 @@ export function createNetworkSection() {
|
||||
name: "Base",
|
||||
title: "OP_RETURN Burned",
|
||||
bottom: satsBtcUsd({
|
||||
pattern: supply.burned.opReturn.base,
|
||||
pattern: scripts.value.opReturn.base,
|
||||
name: "sum",
|
||||
}),
|
||||
},
|
||||
@@ -580,7 +579,7 @@ export function createNetworkSection() {
|
||||
title: "OP_RETURN Burned Rolling",
|
||||
bottom: ROLLING_WINDOWS.flatMap((w) =>
|
||||
satsBtcUsd({
|
||||
pattern: supply.burned.opReturn.sum[w.key],
|
||||
pattern: scripts.value.opReturn.sum[w.key],
|
||||
name: w.name,
|
||||
color: w.color,
|
||||
}),
|
||||
@@ -590,7 +589,7 @@ export function createNetworkSection() {
|
||||
name: w.name,
|
||||
title: `OP_RETURN Burned ${w.name}`,
|
||||
bottom: satsBtcUsd({
|
||||
pattern: supply.burned.opReturn.sum[w.key],
|
||||
pattern: scripts.value.opReturn.sum[w.key],
|
||||
name: w.name,
|
||||
color: w.color,
|
||||
}),
|
||||
@@ -601,7 +600,7 @@ export function createNetworkSection() {
|
||||
name: "Cumulative",
|
||||
title: "OP_RETURN Burned (Total)",
|
||||
bottom: satsBtcUsd({
|
||||
pattern: supply.burned.opReturn.cumulative,
|
||||
pattern: scripts.value.opReturn.cumulative,
|
||||
name: "all-time",
|
||||
}),
|
||||
},
|
||||
@@ -1074,7 +1073,8 @@ export function createNetworkSection() {
|
||||
title: "UTXO Count 30d Change",
|
||||
bottom: [
|
||||
baseline({
|
||||
metric: cohorts.utxo.all.outputs.unspentCount.delta.absolute._1m,
|
||||
metric:
|
||||
cohorts.utxo.all.outputs.unspentCount.delta.absolute._1m,
|
||||
name: "30d Change",
|
||||
unit: Unit.count,
|
||||
}),
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
createCohortFolderFull,
|
||||
createCohortFolderWithAdjusted,
|
||||
createCohortFolderLongTerm,
|
||||
createCohortFolderAgeRange,
|
||||
createCohortFolderAgeRangeWithMatured,
|
||||
createCohortFolderBasicWithMarketCap,
|
||||
createCohortFolderBasicWithoutMarketCap,
|
||||
createCohortFolderWithoutRelative,
|
||||
@@ -14,12 +14,13 @@ import {
|
||||
createAddressCohortFolder,
|
||||
createGroupedCohortFolderWithAdjusted,
|
||||
createGroupedCohortFolderWithNupl,
|
||||
createGroupedCohortFolderAgeRange,
|
||||
createGroupedCohortFolderAgeRangeWithMatured,
|
||||
createGroupedCohortFolderBasicWithMarketCap,
|
||||
createGroupedCohortFolderBasicWithoutMarketCap,
|
||||
createGroupedCohortFolderAddress,
|
||||
createGroupedAddressCohortFolder,
|
||||
} from "./distribution/index.js";
|
||||
import { createUtxoProfitabilitySection } from "./distribution/utxo-profitability.js";
|
||||
import { createMarketSection } from "./market.js";
|
||||
import { createNetworkSection } from "./network.js";
|
||||
import { createMiningSection } from "./mining.js";
|
||||
@@ -110,26 +111,26 @@ export function createPartialOptions() {
|
||||
{
|
||||
name: "Older Than",
|
||||
tree: [
|
||||
createGroupedCohortFolderBasicWithMarketCap({
|
||||
createGroupedCohortFolderWithAdjusted({
|
||||
name: "Compare",
|
||||
title: "Over Age",
|
||||
list: overAge,
|
||||
all: cohortAll,
|
||||
}),
|
||||
...overAge.map(createCohortFolderBasicWithMarketCap),
|
||||
...overAge.map(createCohortFolderWithAdjusted),
|
||||
],
|
||||
},
|
||||
// Range
|
||||
{
|
||||
name: "Range",
|
||||
tree: [
|
||||
createGroupedCohortFolderAgeRange({
|
||||
createGroupedCohortFolderAgeRangeWithMatured({
|
||||
name: "Compare",
|
||||
title: "Age Ranges",
|
||||
list: ageRange,
|
||||
all: cohortAll,
|
||||
}),
|
||||
...ageRange.map(createCohortFolderAgeRange),
|
||||
...ageRange.map(createCohortFolderAgeRangeWithMatured),
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -246,13 +247,13 @@ export function createPartialOptions() {
|
||||
{
|
||||
name: "Epochs",
|
||||
tree: [
|
||||
createGroupedCohortFolderBasicWithoutMarketCap({
|
||||
createGroupedCohortFolderWithAdjusted({
|
||||
name: "Compare",
|
||||
title: "Epochs",
|
||||
list: epoch,
|
||||
all: cohortAll,
|
||||
}),
|
||||
...epoch.map(createCohortFolderBasicWithoutMarketCap),
|
||||
...epoch.map(createCohortFolderWithAdjusted),
|
||||
],
|
||||
},
|
||||
|
||||
@@ -260,15 +261,18 @@ export function createPartialOptions() {
|
||||
{
|
||||
name: "Years",
|
||||
tree: [
|
||||
createGroupedCohortFolderBasicWithoutMarketCap({
|
||||
createGroupedCohortFolderWithAdjusted({
|
||||
name: "Compare",
|
||||
title: "Years",
|
||||
list: class_,
|
||||
all: cohortAll,
|
||||
}),
|
||||
...class_.map(createCohortFolderBasicWithoutMarketCap),
|
||||
...class_.map(createCohortFolderWithAdjusted),
|
||||
],
|
||||
},
|
||||
|
||||
// UTXO Profitability bands
|
||||
createUtxoProfitabilitySection(),
|
||||
],
|
||||
},
|
||||
|
||||
|
||||
@@ -233,6 +233,9 @@
|
||||
* @property {Color} color
|
||||
* @property {AgeRangePattern} tree
|
||||
*
|
||||
* Age range cohort with matured supply
|
||||
* @typedef {CohortAgeRange & { matured: AnyValuePattern }} CohortAgeRangeWithMatured
|
||||
*
|
||||
* Basic cohort WITH RelToMarketCap (geAmount.*, ltAmount.*)
|
||||
* @typedef {Object} CohortBasicWithMarketCap
|
||||
* @property {string} name
|
||||
|
||||
Reference in New Issue
Block a user