mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-24 22:59:58 -07:00
global: snapshot part 8
This commit is contained in:
@@ -57,8 +57,8 @@ function percentileSeries(p, n = (x) => x) {
|
||||
function singleWeightFolder({ avgPrice, avgName, inProfit, inLoss, percentiles, color, weightLabel, title, min, max }) {
|
||||
return [
|
||||
{
|
||||
name: "Profitability",
|
||||
title: title(`Cost Basis Profitability (${weightLabel})`),
|
||||
name: "Average",
|
||||
title: title(`Cost Basis Average (${weightLabel})`),
|
||||
top: [
|
||||
price({ series: inProfit, name: "In Profit", color: colors.profit }),
|
||||
price({ series: avgPrice, name: avgName, color }),
|
||||
@@ -91,7 +91,7 @@ export function createCostBasisSectionWithPercentiles({ cohort, title }) {
|
||||
{
|
||||
name: "Per Coin",
|
||||
tree: singleWeightFolder({
|
||||
avgPrice: tree.realized.price, avgName: "Average",
|
||||
avgPrice: tree.realized.price, avgName: "All",
|
||||
inProfit: cb.inProfit.perCoin, inLoss: cb.inLoss.perCoin,
|
||||
percentiles: cb.perCoin, color, weightLabel: "BTC-weighted", title,
|
||||
min: cb.min, max: cb.max,
|
||||
@@ -100,32 +100,11 @@ export function createCostBasisSectionWithPercentiles({ cohort, title }) {
|
||||
{
|
||||
name: "Per Dollar",
|
||||
tree: singleWeightFolder({
|
||||
avgPrice: tree.realized.investor.price, avgName: "Average",
|
||||
avgPrice: tree.realized.investor.price, avgName: "All",
|
||||
inProfit: cb.inProfit.perDollar, inLoss: cb.inLoss.perDollar,
|
||||
percentiles: cb.perDollar, color, weightLabel: "USD-weighted", title,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Profitability",
|
||||
tree: [
|
||||
{
|
||||
name: "In Profit",
|
||||
title: title("Cost Basis In Profit"),
|
||||
top: [
|
||||
price({ series: cb.inProfit.perCoin, name: "Per Coin", color: colors.realized }),
|
||||
price({ series: cb.inProfit.perDollar, name: "Per Dollar", color: colors.investor }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "In Loss",
|
||||
title: title("Cost Basis In Loss"),
|
||||
top: [
|
||||
price({ series: cb.inLoss.perCoin, name: "Per Coin", color: colors.realized }),
|
||||
price({ series: cb.inLoss.perDollar, name: "Per Dollar", color: colors.investor }),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Supply Density",
|
||||
title: title("Cost Basis Supply Density"),
|
||||
@@ -156,7 +135,7 @@ export function createCostBasisSectionWithPercentiles({ cohort, title }) {
|
||||
function groupedWeightFolder({ list, all, getAvgPrice, getInProfit, getInLoss, getPercentiles, avgTitle, weightLabel, title }) {
|
||||
return [
|
||||
{
|
||||
name: "Profitability",
|
||||
name: "Average",
|
||||
tree: [
|
||||
{
|
||||
name: "In Profit",
|
||||
@@ -230,27 +209,6 @@ export function createGroupedCostBasisSectionWithPercentiles({ list, all, title
|
||||
avgTitle: "Average", weightLabel: "USD-weighted",
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "Profitability",
|
||||
tree: [
|
||||
{
|
||||
name: "In Profit",
|
||||
title: title("Cost Basis In Profit"),
|
||||
top: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => [
|
||||
price({ series: tree.costBasis.inProfit.perCoin, name: `${name} (coin)`, color }),
|
||||
price({ series: tree.costBasis.inProfit.perDollar, name: `${name} (dollar)`, color }),
|
||||
]),
|
||||
},
|
||||
{
|
||||
name: "In Loss",
|
||||
title: title("Cost Basis In Loss"),
|
||||
top: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => [
|
||||
price({ series: tree.costBasis.inLoss.perCoin, name: `${name} (coin)`, color }),
|
||||
price({ series: tree.costBasis.inLoss.perDollar, name: `${name} (dollar)`, color }),
|
||||
]),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Supply Density",
|
||||
title: title("Cost Basis Supply Density"),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,7 +20,7 @@ import {
|
||||
line,
|
||||
baseline,
|
||||
percentRatio,
|
||||
sumsTree,
|
||||
sumsTreeBaseline,
|
||||
rollingPercentRatioTree,
|
||||
} from "../series.js";
|
||||
import { Unit } from "../../utils/units.js";
|
||||
@@ -101,7 +101,7 @@ export function createCohortFolderAll(cohort) {
|
||||
return {
|
||||
name: cohort.name || "all",
|
||||
tree: [
|
||||
createHoldingsSectionAll({ cohort, title }),
|
||||
...createHoldingsSectionAll({ cohort, title }),
|
||||
createValuationSectionFull({ cohort, title }),
|
||||
createPricesSectionFull({ cohort, title }),
|
||||
createCostBasisSectionWithPercentiles({ cohort, title }),
|
||||
@@ -121,7 +121,7 @@ export function createCohortFolderFull(cohort) {
|
||||
return {
|
||||
name: cohort.name || "all",
|
||||
tree: [
|
||||
createHoldingsSectionWithRelative({ cohort, title }),
|
||||
...createHoldingsSectionWithRelative({ cohort, title }),
|
||||
createValuationSectionFull({ cohort, title }),
|
||||
createPricesSectionFull({ cohort, title }),
|
||||
createCostBasisSectionWithPercentiles({ cohort, title }),
|
||||
@@ -141,7 +141,7 @@ export function createCohortFolderWithAdjusted(cohort) {
|
||||
return {
|
||||
name: cohort.name || "all",
|
||||
tree: [
|
||||
createHoldingsSectionWithOwnSupply({ cohort, title }),
|
||||
...createHoldingsSectionWithOwnSupply({ cohort, title }),
|
||||
createValuationSection({ cohort, title }),
|
||||
createPricesSectionBasic({ cohort, title }),
|
||||
createProfitabilitySectionWithInvestedCapitalPct({ cohort, title }),
|
||||
@@ -160,7 +160,7 @@ export function createCohortFolderWithNupl(cohort) {
|
||||
return {
|
||||
name: cohort.name || "all",
|
||||
tree: [
|
||||
createHoldingsSectionWithRelative({ cohort, title }),
|
||||
...createHoldingsSectionWithRelative({ cohort, title }),
|
||||
createValuationSectionFull({ cohort, title }),
|
||||
createPricesSectionFull({ cohort, title }),
|
||||
createCostBasisSectionWithPercentiles({ cohort, title }),
|
||||
@@ -180,7 +180,7 @@ export function createCohortFolderLongTerm(cohort) {
|
||||
return {
|
||||
name: cohort.name || "all",
|
||||
tree: [
|
||||
createHoldingsSectionWithRelative({ cohort, title }),
|
||||
...createHoldingsSectionWithRelative({ cohort, title }),
|
||||
createValuationSectionFull({ cohort, title }),
|
||||
createPricesSectionFull({ cohort, title }),
|
||||
createCostBasisSectionWithPercentiles({ cohort, title }),
|
||||
@@ -200,7 +200,7 @@ export function createCohortFolderAgeRange(cohort) {
|
||||
return {
|
||||
name: cohort.name || "all",
|
||||
tree: [
|
||||
createHoldingsSectionWithOwnSupply({ cohort, title }),
|
||||
...createHoldingsSectionWithOwnSupply({ cohort, title }),
|
||||
createValuationSection({ cohort, title }),
|
||||
createPricesSectionBasic({ cohort, title }),
|
||||
createProfitabilitySectionWithInvestedCapitalPct({ cohort, title }),
|
||||
@@ -237,7 +237,7 @@ export function createCohortFolderBasicWithMarketCap(cohort) {
|
||||
return {
|
||||
name: cohort.name || "all",
|
||||
tree: [
|
||||
createHoldingsSection({ cohort, title }),
|
||||
...createHoldingsSection({ cohort, title }),
|
||||
createValuationSection({ cohort, title }),
|
||||
createPricesSectionBasic({ cohort, title }),
|
||||
createProfitabilitySectionWithNupl({ cohort, title }),
|
||||
@@ -256,7 +256,7 @@ export function createCohortFolderBasicWithoutMarketCap(cohort) {
|
||||
return {
|
||||
name: cohort.name || "all",
|
||||
tree: [
|
||||
createHoldingsSection({ cohort, title }),
|
||||
...createHoldingsSection({ cohort, title }),
|
||||
createValuationSection({ cohort, title }),
|
||||
createPricesSectionBasic({ cohort, title }),
|
||||
createProfitabilitySectionBasicWithInvestedCapitalPct({ cohort, title }),
|
||||
@@ -275,7 +275,7 @@ export function createCohortFolderAddress(cohort) {
|
||||
return {
|
||||
name: cohort.name || "all",
|
||||
tree: [
|
||||
createHoldingsSectionAddress({ cohort, title }),
|
||||
...createHoldingsSectionAddress({ cohort, title }),
|
||||
createValuationSection({ cohort, title }),
|
||||
createPricesSectionBasic({ cohort, title }),
|
||||
createProfitabilitySectionAddress({ cohort, title }),
|
||||
@@ -294,7 +294,7 @@ export function createCohortFolderWithoutRelative(cohort) {
|
||||
return {
|
||||
name: cohort.name || "all",
|
||||
tree: [
|
||||
createHoldingsSectionWithProfitLoss({ cohort, title }),
|
||||
...createHoldingsSectionWithProfitLoss({ cohort, title }),
|
||||
createValuationSection({ cohort, title }),
|
||||
createPricesSectionBasic({ cohort, title }),
|
||||
createProfitabilitySectionWithProfitLoss({ cohort, title }),
|
||||
@@ -313,7 +313,7 @@ export function createAddressCohortFolder(cohort) {
|
||||
return {
|
||||
name: cohort.name || "all",
|
||||
tree: [
|
||||
createHoldingsSectionAddressAmount({ cohort, title }),
|
||||
...createHoldingsSectionAddressAmount({ cohort, title }),
|
||||
createValuationSection({ cohort, title }),
|
||||
createPricesSectionBasic({ cohort, title }),
|
||||
createProfitabilitySectionWithNupl({ cohort, title }),
|
||||
@@ -340,7 +340,7 @@ export function createGroupedCohortFolderFull({
|
||||
return {
|
||||
name: name || "all",
|
||||
tree: [
|
||||
createGroupedHoldingsSectionWithRelative({ list, all, title }),
|
||||
...createGroupedHoldingsSectionWithRelative({ list, all, title }),
|
||||
createGroupedValuationSectionWithOwnMarketCap({ list, all, title }),
|
||||
createGroupedPricesSection({ list, all, title }),
|
||||
createGroupedCostBasisSectionWithPercentiles({ list, all, title }),
|
||||
@@ -364,7 +364,7 @@ export function createGroupedCohortFolderWithAdjusted({
|
||||
return {
|
||||
name: name || "all",
|
||||
tree: [
|
||||
createGroupedHoldingsSectionWithOwnSupply({ list, all, title }),
|
||||
...createGroupedHoldingsSectionWithOwnSupply({ list, all, title }),
|
||||
createGroupedValuationSection({ list, all, title }),
|
||||
createGroupedPricesSection({ list, all, title }),
|
||||
createGroupedProfitabilitySectionWithInvestedCapitalPct({
|
||||
@@ -391,7 +391,7 @@ export function createGroupedCohortFolderWithNupl({
|
||||
return {
|
||||
name: name || "all",
|
||||
tree: [
|
||||
createGroupedHoldingsSectionWithRelative({ list, all, title }),
|
||||
...createGroupedHoldingsSectionWithRelative({ list, all, title }),
|
||||
createGroupedValuationSection({ list, all, title }),
|
||||
createGroupedPricesSection({ list, all, title }),
|
||||
createGroupedCostBasisSectionWithPercentiles({ list, all, title }),
|
||||
@@ -415,7 +415,7 @@ export function createGroupedCohortFolderLongTerm({
|
||||
return {
|
||||
name: name || "all",
|
||||
tree: [
|
||||
createGroupedHoldingsSectionWithRelative({ list, all, title }),
|
||||
...createGroupedHoldingsSectionWithRelative({ list, all, title }),
|
||||
createGroupedValuationSectionWithOwnMarketCap({ list, all, title }),
|
||||
createGroupedPricesSection({ list, all, title }),
|
||||
createGroupedCostBasisSectionWithPercentiles({ list, all, title }),
|
||||
@@ -439,7 +439,7 @@ export function createGroupedCohortFolderAgeRange({
|
||||
return {
|
||||
name: name || "all",
|
||||
tree: [
|
||||
createGroupedHoldingsSectionWithOwnSupply({ list, all, title }),
|
||||
...createGroupedHoldingsSectionWithOwnSupply({ list, all, title }),
|
||||
createGroupedValuationSection({ list, all, title }),
|
||||
createGroupedPricesSection({ list, all, title }),
|
||||
createGroupedProfitabilitySectionWithInvestedCapitalPct({
|
||||
@@ -471,14 +471,17 @@ export function createGroupedCohortFolderAgeRangeWithMatured({
|
||||
const title = formatCohortTitle(groupTitle);
|
||||
folder.tree.push({
|
||||
name: "Matured",
|
||||
title: title("Matured Supply"),
|
||||
bottom: list.flatMap((cohort) =>
|
||||
satsBtcUsd({
|
||||
pattern: cohort.matured.base,
|
||||
name: cohort.name,
|
||||
color: cohort.color,
|
||||
}),
|
||||
),
|
||||
tree: ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: title(`Matured Supply (${w.title})`),
|
||||
bottom: list.flatMap((cohort) =>
|
||||
satsBtcUsd({
|
||||
pattern: cohort.matured.sum[w.key],
|
||||
name: cohort.name,
|
||||
color: cohort.color,
|
||||
}),
|
||||
),
|
||||
})),
|
||||
});
|
||||
return folder;
|
||||
}
|
||||
@@ -497,7 +500,7 @@ export function createGroupedCohortFolderBasicWithMarketCap({
|
||||
return {
|
||||
name: name || "all",
|
||||
tree: [
|
||||
createGroupedHoldingsSection({ list, all, title }),
|
||||
...createGroupedHoldingsSection({ list, all, title }),
|
||||
createGroupedValuationSection({ list, all, title }),
|
||||
createGroupedPricesSection({ list, all, title }),
|
||||
createGroupedProfitabilitySection({ list, all, title }),
|
||||
@@ -520,7 +523,7 @@ export function createGroupedCohortFolderBasicWithoutMarketCap({
|
||||
return {
|
||||
name: name || "all",
|
||||
tree: [
|
||||
createGroupedHoldingsSection({ list, all, title }),
|
||||
...createGroupedHoldingsSection({ list, all, title }),
|
||||
createGroupedValuationSection({ list, all, title }),
|
||||
createGroupedPricesSection({ list, all, title }),
|
||||
createGroupedProfitabilitySectionBasicWithInvestedCapitalPct({
|
||||
@@ -547,7 +550,7 @@ export function createGroupedCohortFolderAddress({
|
||||
return {
|
||||
name: name || "all",
|
||||
tree: [
|
||||
createGroupedHoldingsSectionAddress({ list, all, title }),
|
||||
...createGroupedHoldingsSectionAddress({ list, all, title }),
|
||||
createGroupedValuationSection({ list, all, title }),
|
||||
createGroupedPricesSection({ list, all, title }),
|
||||
createGroupedProfitabilitySectionBasicWithInvestedCapitalPct({
|
||||
@@ -574,7 +577,7 @@ export function createGroupedCohortFolderWithoutRelative({
|
||||
return {
|
||||
name: name || "all",
|
||||
tree: [
|
||||
createGroupedHoldingsSectionWithProfitLoss({ list, all, title }),
|
||||
...createGroupedHoldingsSectionWithProfitLoss({ list, all, title }),
|
||||
createGroupedValuationSection({ list, all, title }),
|
||||
createGroupedPricesSection({ list, all, title }),
|
||||
createGroupedProfitabilitySectionWithProfitLoss({ list, all, title }),
|
||||
@@ -597,7 +600,7 @@ export function createGroupedAddressCohortFolder({
|
||||
return {
|
||||
name: name || "all",
|
||||
tree: [
|
||||
createGroupedHoldingsSectionAddressAmount({ list, all, title }),
|
||||
...createGroupedHoldingsSectionAddressAmount({ list, all, title }),
|
||||
createGroupedValuationSection({ list, all, title }),
|
||||
createGroupedPricesSection({ list, all, title }),
|
||||
createGroupedProfitabilitySection({ list, all, title }),
|
||||
@@ -637,11 +640,10 @@ function singleBucketFolder({ name, color, pattern }) {
|
||||
name: "Change",
|
||||
tree: [
|
||||
{
|
||||
...sumsTree({
|
||||
...sumsTreeBaseline({
|
||||
windows: pattern.supply.all.delta.absolute,
|
||||
title: `${name}: Supply Change`,
|
||||
unit: Unit.sats,
|
||||
series: baseline,
|
||||
}),
|
||||
name: "Absolute",
|
||||
},
|
||||
@@ -650,7 +652,7 @@ function singleBucketFolder({ name, color, pattern }) {
|
||||
windows: pattern.supply.all.delta.rate,
|
||||
title: `${name}: Supply Rate`,
|
||||
}),
|
||||
name: "Rate",
|
||||
name: "Growth Rate",
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -743,7 +745,7 @@ function groupedBucketCharts(list, titlePrefix) {
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Rate",
|
||||
name: "Growth Rate",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
|
||||
@@ -197,17 +197,6 @@ function unrealizedTreeMid(u, title) {
|
||||
// Invested Capital, Sentiment, NUPL
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Invested capital (Full unrealized only)
|
||||
* @param {FullRelativePattern | AllRelativePattern} u
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
*/
|
||||
function investedCapitalSeries(u) {
|
||||
return [
|
||||
line({ series: u.investedCapital.inProfit.usd, name: "In Profit", color: colors.profit, unit: Unit.usd }),
|
||||
line({ series: u.investedCapital.inLoss.usd, name: "In Loss", color: colors.loss, unit: Unit.usd }),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sentiment (Full unrealized only)
|
||||
@@ -238,7 +227,7 @@ function nuplSeries(nupl) {
|
||||
/**
|
||||
* Flat metric folder: Compare + windows + Cumulative + optional % of Realized Cap
|
||||
* @param {Object} args
|
||||
* @param {{ sum: Record<string, { usd: AnySeriesPattern }>, cumulative: { usd: AnySeriesPattern }, base: { usd: AnySeriesPattern } }} args.pattern
|
||||
* @param {{ sum: Record<string, { usd: AnySeriesPattern }>, cumulative: { usd: AnySeriesPattern } }} args.pattern
|
||||
* @param {string} args.metricTitle
|
||||
* @param {Color} args.color
|
||||
* @param {(name: string) => string} args.title
|
||||
@@ -328,7 +317,7 @@ function realizedNetFolder({ netPnl, title, toRcap, extraChange = [] }) {
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Rate",
|
||||
name: "Growth Rate",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
@@ -340,7 +329,7 @@ function realizedNetFolder({ netPnl, title, toRcap, extraChange = [] }) {
|
||||
...ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: title(`Net Realized P&L Rate (${w.title})`),
|
||||
bottom: percentRatioBaseline({ pattern: netPnl.delta.rate[w.key], name: "Rate" }),
|
||||
bottom: percentRatioBaseline({ pattern: netPnl.delta.rate[w.key], name: "Growth Rate" }),
|
||||
})),
|
||||
],
|
||||
},
|
||||
@@ -561,11 +550,6 @@ export function createProfitabilitySectionAll({ cohort, title }) {
|
||||
tree: [
|
||||
{ name: "Unrealized", tree: unrealizedTreeAll(u, title) },
|
||||
realizedSubfolderFull(r, title),
|
||||
{
|
||||
name: "Invested Capital",
|
||||
title: title("Invested Capital In Profit & Loss"),
|
||||
bottom: investedCapitalSeries(u),
|
||||
},
|
||||
{ name: "Sentiment", title: title("Market Sentiment"), bottom: sentimentSeries(u) },
|
||||
],
|
||||
};
|
||||
@@ -584,11 +568,6 @@ export function createProfitabilitySectionFull({ cohort, title }) {
|
||||
tree: [
|
||||
{ name: "Unrealized", tree: unrealizedTreeFull(u, title) },
|
||||
realizedSubfolderFull(r, title),
|
||||
{
|
||||
name: "Invested Capital",
|
||||
title: title("Invested Capital In Profit & Loss"),
|
||||
bottom: investedCapitalSeries(u),
|
||||
},
|
||||
{ name: "Sentiment", title: title("Market Sentiment"), bottom: sentimentSeries(u) },
|
||||
],
|
||||
};
|
||||
@@ -628,11 +607,6 @@ export function createProfitabilitySectionLongTerm({ cohort, title }) {
|
||||
tree: [
|
||||
{ name: "Unrealized", tree: unrealizedTreeLongTerm(u, title) },
|
||||
realizedSubfolderFull(r, title),
|
||||
{
|
||||
name: "Invested Capital",
|
||||
title: title("Invested Capital In Profit & Loss"),
|
||||
bottom: investedCapitalSeries(u),
|
||||
},
|
||||
{ name: "Sentiment", title: title("Market Sentiment"), bottom: sentimentSeries(u) },
|
||||
],
|
||||
};
|
||||
@@ -716,17 +690,23 @@ function groupedRealizedPnlSum(list, all, title) {
|
||||
return [
|
||||
{
|
||||
name: "Profit",
|
||||
title: title("Realized Profit"),
|
||||
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
line({ series: tree.realized.profit.base.usd, name, color, unit: Unit.usd }),
|
||||
),
|
||||
tree: ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: title(`Realized Profit (${w.title})`),
|
||||
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
line({ series: tree.realized.profit.sum[w.key].usd, name, color, unit: Unit.usd }),
|
||||
),
|
||||
})),
|
||||
},
|
||||
{
|
||||
name: "Loss",
|
||||
title: title("Realized Loss"),
|
||||
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
line({ series: tree.realized.loss.base.usd, name, color, unit: Unit.usd }),
|
||||
),
|
||||
tree: ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: title(`Realized Loss (${w.title})`),
|
||||
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
line({ series: tree.realized.loss.sum[w.key].usd, name, color, unit: Unit.usd }),
|
||||
),
|
||||
})),
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -882,7 +862,7 @@ function groupedRealizedNetPnlDeltaTree(list, all, title) {
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Rate",
|
||||
name: "Growth Rate",
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
@@ -920,10 +900,13 @@ function groupedRealizedSubfolderFull(list, all, title) {
|
||||
{ name: "P&L", tree: groupedRealizedPnlSumFull(list, all, title) },
|
||||
{
|
||||
name: "Net",
|
||||
title: title("Net Realized P&L"),
|
||||
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
baseline({ series: tree.realized.netPnl.base.usd, name, color, unit: Unit.usd }),
|
||||
),
|
||||
tree: ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: title(`Net Realized P&L (${w.title})`),
|
||||
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
baseline({ series: tree.realized.netPnl.sum[w.key].usd, name, color, unit: Unit.usd }),
|
||||
),
|
||||
})),
|
||||
},
|
||||
groupedRealizedNetPnlDeltaTree(list, all, title),
|
||||
{ name: "Rolling", tree: groupedRollingRealizedChartsFull(list, all, title) },
|
||||
|
||||
@@ -1,176 +1,144 @@
|
||||
/**
|
||||
* Valuation section builders
|
||||
* Capitalization section builders
|
||||
*
|
||||
* Structure:
|
||||
* - Realized Cap: Total value at cost basis (USD)
|
||||
* - 30d Change: Recent realized cap changes
|
||||
* - Total: Realized Cap (USD)
|
||||
* - Profitability: Invested Capital (Total + In Profit + In Loss) [full only]
|
||||
* - MVRV: Market Value to Realized Value ratio
|
||||
*
|
||||
* For cohorts WITH full ratio patterns: MVRV uses createRatioChart (price + percentiles)
|
||||
* For cohorts WITHOUT full ratio patterns: MVRV is simple baseline
|
||||
* - % of Own Market Cap [full only]
|
||||
* - Change: Rolling window absolute changes
|
||||
* - Growth Rate: Rolling window rate of change
|
||||
*/
|
||||
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import { ROLLING_WINDOWS, line, baseline, mapWindows, sumsTree, rollingPercentRatioTree, percentRatio, percentRatioBaseline } from "../series.js";
|
||||
import { colors } from "../../utils/colors.js";
|
||||
import { ROLLING_WINDOWS, line, baseline, mapWindows, sumsTreeBaseline, rollingPercentRatioTree, percentRatio, percentRatioBaseline } from "../series.js";
|
||||
import { createRatioChart, mapCohortsWithAll, flatMapCohortsWithAll } from "../shared.js";
|
||||
|
||||
// ============================================================================
|
||||
// Shared building blocks
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @param {UtxoCohortObject | CohortWithoutRelative} cohort
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
* Single cohort: Change + Growth Rate items (flat)
|
||||
* @param {UtxoCohortObject["tree"]} tree
|
||||
* @param {(name: string) => string} title
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function createSingleRealizedCapSeries(cohort) {
|
||||
const { color, tree } = cohort;
|
||||
function singleDeltaItems(tree, title) {
|
||||
return [
|
||||
line({
|
||||
series: tree.realized.cap.usd,
|
||||
name: "Realized Cap",
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
{ ...sumsTreeBaseline({ windows: mapWindows(tree.realized.cap.delta.absolute, (c) => c.usd), title: title("Realized Cap Change"), unit: Unit.usd }), name: "Change" },
|
||||
{ ...rollingPercentRatioTree({ windows: tree.realized.cap.delta.rate, title: title("Realized Cap Rate") }), name: "Growth Rate" },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create valuation section for cohorts with full ratio patterns
|
||||
* (CohortAll, CohortFull, CohortWithPercentiles)
|
||||
* Grouped: Change + Growth Rate + MVRV items (flat)
|
||||
* @param {readonly (UtxoCohortObject | CohortWithoutRelative)[]} list
|
||||
* @param {CohortAll} all
|
||||
* @param {(name: string) => string} title
|
||||
* @returns {PartialOptionsTree}
|
||||
*/
|
||||
function groupedDeltaAndMvrv(list, all, title) {
|
||||
return [
|
||||
{
|
||||
name: "Change",
|
||||
tree: ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: title(`Realized Cap Change (${w.title})`),
|
||||
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
baseline({ series: tree.realized.cap.delta.absolute[w.key].usd, name, color, unit: Unit.usd }),
|
||||
),
|
||||
})),
|
||||
},
|
||||
{
|
||||
name: "Growth Rate",
|
||||
tree: ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: title(`Realized Cap Rate (${w.title})`),
|
||||
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
percentRatioBaseline({ pattern: tree.realized.cap.delta.rate[w.key], name, color }),
|
||||
),
|
||||
})),
|
||||
},
|
||||
{
|
||||
name: "MVRV",
|
||||
title: title("MVRV"),
|
||||
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
baseline({ series: tree.realized.mvrv, name, color, unit: Unit.ratio, base: 1 }),
|
||||
),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Single Cohort Sections
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Full capitalization (has invested capital, own market cap ratio, full MVRV)
|
||||
* @param {{ cohort: CohortAll | CohortFull | CohortLongTerm, title: (name: string) => string }} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createValuationSectionFull({ cohort, title }) {
|
||||
const { tree, color } = cohort;
|
||||
return {
|
||||
name: "Valuation",
|
||||
name: "Capitalization",
|
||||
tree: [
|
||||
{ name: "Total", title: title("Realized Cap"), bottom: [line({ series: tree.realized.cap.usd, name: "Realized Cap", color, unit: Unit.usd })] },
|
||||
{
|
||||
name: "Realized Cap",
|
||||
tree: [
|
||||
{
|
||||
name: "USD",
|
||||
title: title("Realized Cap"),
|
||||
bottom: createSingleRealizedCapSeries(cohort),
|
||||
},
|
||||
{
|
||||
name: "% of Own Market Cap",
|
||||
title: title("Realized Cap (% of Own Market Cap)"),
|
||||
bottom: percentRatioBaseline({ pattern: tree.realized.cap.toOwnMcap, name: "Rel. to Own Market Cap", color }),
|
||||
},
|
||||
name: "Profitability",
|
||||
title: title("Invested Capital"),
|
||||
bottom: [
|
||||
line({ series: tree.realized.cap.usd, name: "Total", color: colors.default, unit: Unit.usd }),
|
||||
line({ series: tree.unrealized.investedCapital.inProfit.usd, name: "In Profit", color: colors.profit, unit: Unit.usd }),
|
||||
line({ series: tree.unrealized.investedCapital.inLoss.usd, name: "In Loss", color: colors.loss, unit: Unit.usd }),
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Change",
|
||||
tree: [
|
||||
{ ...sumsTree({ windows: mapWindows(tree.realized.cap.delta.absolute, (c) => c.usd), title: title("Realized Cap Change"), unit: Unit.usd, series: baseline }), name: "Absolute" },
|
||||
{ ...rollingPercentRatioTree({ windows: tree.realized.cap.delta.rate, title: title("Realized Cap Rate") }), name: "Rate" },
|
||||
],
|
||||
},
|
||||
createRatioChart({
|
||||
title,
|
||||
pricePattern: tree.realized.price,
|
||||
ratio: tree.realized.price,
|
||||
color,
|
||||
name: "MVRV",
|
||||
}),
|
||||
createRatioChart({ title, pricePattern: tree.realized.price, ratio: tree.realized.price, color, name: "MVRV" }),
|
||||
{ name: "% of Own Market Cap", title: title("Realized Cap (% of Own Market Cap)"), bottom: percentRatioBaseline({ pattern: tree.realized.cap.toOwnMcap, name: "Rel. to Own Market Cap", color }) },
|
||||
...singleDeltaItems(tree, title),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create valuation section for cohorts with basic ratio patterns
|
||||
* (CohortWithAdjusted, CohortBasic, CohortAddr, CohortWithoutRelative)
|
||||
* Basic capitalization (no invested capital, simple MVRV)
|
||||
* @param {{ cohort: CohortWithAdjusted | CohortBasic | CohortAddr | CohortWithoutRelative, title: (name: string) => string }} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createValuationSection({ cohort, title }) {
|
||||
const { tree } = cohort;
|
||||
return {
|
||||
name: "Valuation",
|
||||
name: "Capitalization",
|
||||
tree: [
|
||||
{
|
||||
name: "Realized Cap",
|
||||
title: title("Realized Cap"),
|
||||
bottom: createSingleRealizedCapSeries(cohort),
|
||||
},
|
||||
{
|
||||
name: "Change",
|
||||
tree: [
|
||||
{ ...sumsTree({ windows: mapWindows(tree.realized.cap.delta.absolute, (c) => c.usd), title: title("Realized Cap Change"), unit: Unit.usd, series: baseline }), name: "Absolute" },
|
||||
{ ...rollingPercentRatioTree({ windows: tree.realized.cap.delta.rate, title: title("Realized Cap Rate") }), name: "Rate" },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "MVRV",
|
||||
title: title("MVRV"),
|
||||
bottom: [
|
||||
baseline({
|
||||
series: tree.realized.mvrv,
|
||||
name: "MVRV",
|
||||
unit: Unit.ratio,
|
||||
base: 1,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{ name: "Total", title: title("Realized Cap"), bottom: [line({ series: tree.realized.cap.usd, name: "Realized Cap", color: cohort.color, unit: Unit.usd })] },
|
||||
...singleDeltaItems(tree, title),
|
||||
{ name: "MVRV", title: title("MVRV"), bottom: [baseline({ series: tree.realized.mvrv, name: "MVRV", unit: Unit.ratio, base: 1 })] },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Grouped Cohort Sections
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @param {{ list: readonly (UtxoCohortObject | CohortWithoutRelative)[], all: CohortAll, title: (name: string) => string }} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createGroupedValuationSection({ list, all, title }) {
|
||||
return {
|
||||
name: "Valuation",
|
||||
name: "Capitalization",
|
||||
tree: [
|
||||
{
|
||||
name: "Realized Cap",
|
||||
name: "Total",
|
||||
title: title("Realized Cap"),
|
||||
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
line({
|
||||
series: tree.realized.cap.usd,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.usd,
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "Change",
|
||||
tree: [
|
||||
{
|
||||
name: "Absolute",
|
||||
tree: ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: title(`Realized Cap Change (${w.title})`),
|
||||
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
baseline({ series: tree.realized.cap.delta.absolute[w.key].usd, name, color, unit: Unit.usd }),
|
||||
),
|
||||
})),
|
||||
},
|
||||
{
|
||||
name: "Rate",
|
||||
tree: ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: title(`Realized Cap Rate (${w.title})`),
|
||||
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
percentRatio({ pattern: tree.realized.cap.delta.rate[w.key], name, color }),
|
||||
),
|
||||
})),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "MVRV",
|
||||
title: title("MVRV"),
|
||||
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
baseline({
|
||||
series: tree.realized.mvrv,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.ratio,
|
||||
base: 1,
|
||||
}),
|
||||
line({ series: tree.realized.cap.usd, name, color, unit: Unit.usd }),
|
||||
),
|
||||
},
|
||||
...groupedDeltaAndMvrv(list, all, title),
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -179,71 +147,25 @@ export function createGroupedValuationSection({ list, all, title }) {
|
||||
* @param {{ list: readonly (CohortAll | CohortFull | CohortLongTerm)[], all: CohortAll, title: (name: string) => string }} args
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function createGroupedValuationSectionWithOwnMarketCap({
|
||||
list,
|
||||
all,
|
||||
title,
|
||||
}) {
|
||||
export function createGroupedValuationSectionWithOwnMarketCap({ list, all, title }) {
|
||||
return {
|
||||
name: "Valuation",
|
||||
name: "Capitalization",
|
||||
tree: [
|
||||
{
|
||||
name: "Realized Cap",
|
||||
tree: [
|
||||
{
|
||||
name: "USD",
|
||||
title: title("Realized Cap"),
|
||||
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
line({ series: tree.realized.cap.usd, name, color, unit: Unit.usd }),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "% of Own Market Cap",
|
||||
title: title("Realized Cap (% of Own Market Cap)"),
|
||||
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
percentRatio({ pattern: tree.realized.cap.toOwnMcap, name, color }),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Change",
|
||||
tree: [
|
||||
{
|
||||
name: "Absolute",
|
||||
tree: ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: title(`Realized Cap Change (${w.title})`),
|
||||
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
baseline({ series: tree.realized.cap.delta.absolute[w.key].usd, name, color, unit: Unit.usd }),
|
||||
),
|
||||
})),
|
||||
},
|
||||
{
|
||||
name: "Rate",
|
||||
tree: ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: title(`Realized Cap Rate (${w.title})`),
|
||||
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
percentRatio({ pattern: tree.realized.cap.delta.rate[w.key], name, color }),
|
||||
),
|
||||
})),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "MVRV",
|
||||
title: title("MVRV"),
|
||||
name: "Total",
|
||||
title: title("Realized Cap"),
|
||||
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
baseline({
|
||||
series: tree.realized.mvrv,
|
||||
name,
|
||||
color,
|
||||
unit: Unit.ratio,
|
||||
base: 1,
|
||||
}),
|
||||
line({ series: tree.realized.cap.usd, name, color, unit: Unit.usd }),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "% of Own Market Cap",
|
||||
title: title("Realized Cap (% of Own Market Cap)"),
|
||||
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
|
||||
percentRatio({ pattern: tree.realized.cap.toOwnMcap, name, color }),
|
||||
),
|
||||
},
|
||||
...groupedDeltaAndMvrv(list, all, title),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -408,24 +408,16 @@ export function statsAtWindow(pattern, window) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Rolling folder tree from a _1m1w1y24hPattern (4 rolling windows)
|
||||
* Rolling folder tree with line series
|
||||
* @param {Object} args
|
||||
* @param {{ _24h: AnySeriesPattern, _1w: AnySeriesPattern, _1m: AnySeriesPattern, _1y: AnySeriesPattern }} args.windows
|
||||
* @param {string} args.title - Compare chart title
|
||||
* @param {(w: typeof ROLLING_WINDOWS[number]) => string} args.windowTitle - Individual window chart title
|
||||
* @param {string} args.title
|
||||
* @param {(w: typeof ROLLING_WINDOWS[number]) => string} args.windowTitle
|
||||
* @param {Unit} args.unit
|
||||
* @param {string} args.name
|
||||
* @param {(args: {series: AnySeriesPattern, name: string, color: Color, unit: Unit}) => AnyFetchedSeriesBlueprint} [args.series]
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
function rollingWindowsTree({
|
||||
windows,
|
||||
title,
|
||||
windowTitle,
|
||||
unit,
|
||||
name,
|
||||
series = line,
|
||||
}) {
|
||||
function rollingWindowsTreeLine({ windows, title, windowTitle, unit, name }) {
|
||||
return {
|
||||
name,
|
||||
tree: [
|
||||
@@ -433,25 +425,43 @@ function rollingWindowsTree({
|
||||
name: "Compare",
|
||||
title,
|
||||
bottom: ROLLING_WINDOWS.map((w) =>
|
||||
series({
|
||||
series: windows[w.key],
|
||||
name: w.name,
|
||||
color: w.color,
|
||||
unit,
|
||||
}),
|
||||
line({ series: windows[w.key], name: w.name, color: w.color, unit }),
|
||||
),
|
||||
},
|
||||
...ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: windowTitle(w),
|
||||
bottom: [
|
||||
series({
|
||||
series: windows[w.key],
|
||||
name: w.name,
|
||||
color: w.color,
|
||||
unit,
|
||||
}),
|
||||
],
|
||||
bottom: [line({ series: windows[w.key], name: w.name, unit })],
|
||||
})),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Rolling folder tree with baseline series
|
||||
* @param {Object} args
|
||||
* @param {{ _24h: AnySeriesPattern, _1w: AnySeriesPattern, _1m: AnySeriesPattern, _1y: AnySeriesPattern }} args.windows
|
||||
* @param {string} args.title
|
||||
* @param {(w: typeof ROLLING_WINDOWS[number]) => string} args.windowTitle
|
||||
* @param {Unit} args.unit
|
||||
* @param {string} args.name
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
function rollingWindowsTreeBaseline({ windows, title, windowTitle, unit, name }) {
|
||||
return {
|
||||
name,
|
||||
tree: [
|
||||
{
|
||||
name: "Compare",
|
||||
title,
|
||||
bottom: ROLLING_WINDOWS.map((w) =>
|
||||
baseline({ series: windows[w.key], name: w.name, color: w.color, unit }),
|
||||
),
|
||||
},
|
||||
...ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: windowTitle(w),
|
||||
bottom: [baseline({ series: windows[w.key], name: w.name, unit })],
|
||||
})),
|
||||
],
|
||||
};
|
||||
@@ -584,17 +594,32 @@ export function sumsAndAveragesCumulative({ sum, average, cumulative, title, uni
|
||||
* @param {{ _24h: AnySeriesPattern, _1w: AnySeriesPattern, _1m: AnySeriesPattern, _1y: AnySeriesPattern }} args.windows
|
||||
* @param {string} args.title
|
||||
* @param {Unit} args.unit
|
||||
* @param {(args: {series: AnySeriesPattern, name: string, color: Color, unit: Unit}) => AnyFetchedSeriesBlueprint} [args.series]
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function sumsTree({ windows, title, unit, series }) {
|
||||
return rollingWindowsTree({
|
||||
export function sumsTree({ windows, title, unit }) {
|
||||
return rollingWindowsTreeLine({
|
||||
windows,
|
||||
title,
|
||||
windowTitle: (w) => `${title} ${w.title} Sum`,
|
||||
unit,
|
||||
name: "Sums",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} args
|
||||
* @param {{ _24h: AnySeriesPattern, _1w: AnySeriesPattern, _1m: AnySeriesPattern, _1y: AnySeriesPattern }} args.windows
|
||||
* @param {string} args.title
|
||||
* @param {Unit} args.unit
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function sumsTreeBaseline({ windows, title, unit }) {
|
||||
return rollingWindowsTreeBaseline({
|
||||
windows,
|
||||
title,
|
||||
windowTitle: (w) => `${title} ${w.title} Sum`,
|
||||
unit,
|
||||
name: "Sums",
|
||||
...(series ? { series } : {}),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -819,20 +844,14 @@ export function percentRatioBaseline({ pattern, name, color, defaultActive }) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Rolling folder tree where each window is a BpsPercentRatioPattern (percent + ratio)
|
||||
* Rolling folder tree with percentRatio series (colored in compare, plain in individual)
|
||||
* @param {Object} args
|
||||
* @param {{ _24h: { percent: AnySeriesPattern, ratio: AnySeriesPattern }, _1w: { percent: AnySeriesPattern, ratio: AnySeriesPattern }, _1m: { percent: AnySeriesPattern, ratio: AnySeriesPattern }, _1y: { percent: AnySeriesPattern, ratio: AnySeriesPattern } }} args.windows
|
||||
* @param {string} args.title
|
||||
* @param {string} [args.name]
|
||||
* @param {(args: {pattern: { percent: AnySeriesPattern, ratio: AnySeriesPattern }, name: string, color?: Color}) => AnyFetchedSeriesBlueprint[]} [args.series]
|
||||
* @returns {PartialOptionsGroup}
|
||||
*/
|
||||
export function rollingPercentRatioTree({
|
||||
windows,
|
||||
title,
|
||||
name = "Sums",
|
||||
series = percentRatio,
|
||||
}) {
|
||||
export function rollingPercentRatioTree({ windows, title, name = "Sums" }) {
|
||||
return {
|
||||
name,
|
||||
tree: [
|
||||
@@ -840,17 +859,13 @@ export function rollingPercentRatioTree({
|
||||
name: "Compare",
|
||||
title: `${title} Rolling`,
|
||||
bottom: ROLLING_WINDOWS.flatMap((w) =>
|
||||
percentRatio({
|
||||
pattern: windows[w.key],
|
||||
name: w.name,
|
||||
color: w.color,
|
||||
}),
|
||||
percentRatio({ pattern: windows[w.key], name: w.name, color: w.color }),
|
||||
),
|
||||
},
|
||||
...ROLLING_WINDOWS.map((w) => ({
|
||||
name: w.name,
|
||||
title: `${title} (${w.title})`,
|
||||
bottom: series({ pattern: windows[w.key], name: w.name }),
|
||||
bottom: percentRatioBaseline({ pattern: windows[w.key], name: w.name }),
|
||||
})),
|
||||
],
|
||||
};
|
||||
@@ -900,7 +915,6 @@ export function deltaTree({ delta, title, unit, extract }) {
|
||||
windows: delta.rate,
|
||||
title: `${title} Growth Rate`,
|
||||
name: "Growth Rate",
|
||||
series: percentRatioBaseline,
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -152,10 +152,10 @@ export function satsBtcUsdBaseline({ pattern, name, color, defaultActive }) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create sats/btc/usd series from any value pattern using base or cumulative key
|
||||
* Create sats/btc/usd series from a value pattern's cumulative
|
||||
* @param {Object} args
|
||||
* @param {{ base: AnyValuePattern, cumulative: AnyValuePattern }} args.source
|
||||
* @param {'base' | 'cumulative'} args.key
|
||||
* @param {{ cumulative: AnyValuePattern }} args.source
|
||||
* @param {'cumulative'} args.key
|
||||
* @param {string} args.name
|
||||
* @param {Color} [args.color]
|
||||
* @param {boolean} [args.defaultActive]
|
||||
@@ -173,10 +173,10 @@ export function satsBtcUsdFrom({ source, key, name, color, defaultActive }) {
|
||||
/**
|
||||
* Create coinbase/subsidy/fee series from separate sources
|
||||
* @param {Object} args
|
||||
* @param {{ base: AnyValuePattern, cumulative: AnyValuePattern }} args.coinbase
|
||||
* @param {{ base: AnyValuePattern, cumulative: AnyValuePattern }} args.subsidy
|
||||
* @param {{ base: AnyValuePattern, cumulative: AnyValuePattern }} args.fee
|
||||
* @param {'base' | 'cumulative'} args.key
|
||||
* @param {{ cumulative: AnyValuePattern }} args.coinbase
|
||||
* @param {{ cumulative: AnyValuePattern }} args.subsidy
|
||||
* @param {{ cumulative: AnyValuePattern }} args.fee
|
||||
* @param {'cumulative'} args.key
|
||||
* @returns {FetchedLineSeriesBlueprint[]}
|
||||
*/
|
||||
export function revenueBtcSatsUsd({ coinbase, subsidy, fee, key }) {
|
||||
|
||||
@@ -1,15 +1,4 @@
|
||||
import { localhost } from "../utils/env.js";
|
||||
import { INDEX_LABEL } from "../utils/serde.js";
|
||||
|
||||
/**
|
||||
* Check if a series pattern has at least one chartable index
|
||||
* @param {AnySeriesPattern} node
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function hasChartableIndex(node) {
|
||||
const indexes = node.indexes();
|
||||
return indexes.some((idx) => idx in INDEX_LABEL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk a series tree and collect all chartable series patterns
|
||||
@@ -20,7 +9,7 @@ function hasChartableIndex(node) {
|
||||
function walkSeries(node, map, path) {
|
||||
if (node && "by" in node) {
|
||||
const seriesNode = /** @type {AnySeriesPattern} */ (node);
|
||||
if (!hasChartableIndex(seriesNode)) return;
|
||||
if (!seriesNode.by.day1) return;
|
||||
map.set(seriesNode, path);
|
||||
} else if (node && typeof node === "object") {
|
||||
for (const [key, value] of Object.entries(node)) {
|
||||
|
||||
Reference in New Issue
Block a user