mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-24 22:59:58 -07:00
2217 lines
60 KiB
JavaScript
2217 lines
60 KiB
JavaScript
/**
|
|
* UTXO cohort folder builders
|
|
* Creates option trees for UTXO-based cohorts (no addrCount)
|
|
*
|
|
* Cohort capabilities (based on brk client patterns):
|
|
*
|
|
* With adjustedSopr (RealizedPattern3/4):
|
|
* - all, term.short, maxAge.*
|
|
*
|
|
* Without adjustedSopr (RealizedPattern/2):
|
|
* - term.long, minAge.*, ageRange.*, epoch.*, all amount cohorts
|
|
*
|
|
* With cost basis percentiles (CostBasisPattern2):
|
|
* - all, term.*, ageRange.*
|
|
*
|
|
* Without percentiles (CostBasisPattern):
|
|
* - maxAge.*, minAge.*, epoch.*, all amount cohorts
|
|
*
|
|
* Folder builders:
|
|
* - createCohortFolderFull: adjustedSopr + percentiles (all, term.short)
|
|
* - createCohortFolderWithAdjusted: adjustedSopr only (maxAge.*)
|
|
* - createCohortFolderWithPercentiles: percentiles only (term.long, ageRange.*)
|
|
* - createCohortFolderBasic: neither (minAge.*, epoch.*, amount cohorts)
|
|
*/
|
|
|
|
import {
|
|
createSingleSupplySeries,
|
|
createGroupedSupplyTotalSeries,
|
|
createGroupedSupplyInProfitSeries,
|
|
createGroupedSupplyInLossSeries,
|
|
createUtxoCountSeries,
|
|
createRealizedPriceSeries,
|
|
createRealizedPriceRatioSeries,
|
|
createCostBasisPercentilesSeries,
|
|
} from "./shared.js";
|
|
import { Unit } from "../../utils/units.js";
|
|
import { line, baseline } from "../series.js";
|
|
|
|
// ============================================================================
|
|
// Folder Builders (4 variants based on pattern capabilities)
|
|
// ============================================================================
|
|
|
|
/**
|
|
* All folder: for the special "All" cohort (adjustedSopr + percentiles but no RelToMarketCap)
|
|
* @param {PartialContext} ctx
|
|
* @param {CohortAll} args
|
|
* @returns {PartialOptionsGroup}
|
|
*/
|
|
export function createCohortFolderAll(ctx, args) {
|
|
const title = args.title ? `of ${args.title}` : "";
|
|
return {
|
|
name: args.name || "all",
|
|
tree: [
|
|
createSingleSupplyChart(ctx, args, title),
|
|
createSingleUtxoCountChart(args, title),
|
|
createSingleRealizedSectionWithAdjusted(ctx, args, title),
|
|
createSingleUnrealizedSectionAll(ctx, args, title),
|
|
createSingleCostBasisSectionWithPercentiles(args, title),
|
|
...createSingleActivitySectionWithAdjusted(ctx, args, title),
|
|
],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Full folder: adjustedSopr + percentiles + RelToMarketCap (term.short only)
|
|
* @param {PartialContext} ctx
|
|
* @param {CohortFull | CohortGroupFull} args
|
|
* @returns {PartialOptionsGroup}
|
|
*/
|
|
export function createCohortFolderFull(ctx, args) {
|
|
if ("list" in args) {
|
|
const { list } = args;
|
|
const title = args.title ? `by ${args.title}` : "";
|
|
return {
|
|
name: args.name || "all",
|
|
tree: [
|
|
createGroupedSupplySection(ctx, list, title),
|
|
createGroupedUtxoCountChart(list, title),
|
|
createGroupedRealizedSectionWithAdjusted(ctx, list, title),
|
|
createGroupedUnrealizedSectionFull(ctx, list, title),
|
|
createGroupedCostBasisSectionWithPercentiles(list, title),
|
|
...createGroupedActivitySectionWithAdjusted(list, title),
|
|
],
|
|
};
|
|
}
|
|
const title = args.title ? `of ${args.title}` : "";
|
|
return {
|
|
name: args.name || "all",
|
|
tree: [
|
|
createSingleSupplyChart(ctx, args, title),
|
|
createSingleUtxoCountChart(args, title),
|
|
createSingleRealizedSectionWithAdjusted(ctx, args, title),
|
|
createSingleUnrealizedSectionFull(ctx, args, title),
|
|
createSingleCostBasisSectionWithPercentiles(args, title),
|
|
...createSingleActivitySectionWithAdjusted(ctx, args, title),
|
|
],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Adjusted folder: adjustedSopr only, no percentiles (maxAge.*)
|
|
* @param {PartialContext} ctx
|
|
* @param {CohortWithAdjusted | CohortGroupWithAdjusted} args
|
|
* @returns {PartialOptionsGroup}
|
|
*/
|
|
export function createCohortFolderWithAdjusted(ctx, args) {
|
|
if ("list" in args) {
|
|
const { list } = args;
|
|
const title = args.title ? `by ${args.title}` : "";
|
|
return {
|
|
name: args.name || "all",
|
|
tree: [
|
|
createGroupedSupplySection(ctx, list, title),
|
|
createGroupedUtxoCountChart(list, title),
|
|
createGroupedRealizedSectionWithAdjusted(ctx, list, title),
|
|
createGroupedUnrealizedSectionWithMarketCap(ctx, list, title),
|
|
createGroupedCostBasisSection(list, title),
|
|
...createGroupedActivitySectionWithAdjusted(list, title),
|
|
],
|
|
};
|
|
}
|
|
const title = args.title ? `of ${args.title}` : "";
|
|
return {
|
|
name: args.name || "all",
|
|
tree: [
|
|
createSingleSupplyChart(ctx, args, title),
|
|
createSingleUtxoCountChart(args, title),
|
|
createSingleRealizedSectionWithAdjusted(ctx, args, title),
|
|
createSingleUnrealizedSectionWithMarketCap(ctx, args, title),
|
|
createSingleCostBasisSection(args, title),
|
|
...createSingleActivitySectionWithAdjusted(ctx, args, title),
|
|
],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Percentiles folder: percentiles only, no adjustedSopr (term.long, ageRange.*)
|
|
* @param {PartialContext} ctx
|
|
* @param {CohortWithPercentiles | CohortGroupWithPercentiles} args
|
|
* @returns {PartialOptionsGroup}
|
|
*/
|
|
export function createCohortFolderWithPercentiles(ctx, args) {
|
|
if ("list" in args) {
|
|
const { list } = args;
|
|
const title = args.title ? `by ${args.title}` : "";
|
|
return {
|
|
name: args.name || "all",
|
|
tree: [
|
|
createGroupedSupplySection(ctx, list, title),
|
|
createGroupedUtxoCountChart(list, title),
|
|
createGroupedRealizedSectionBasic(ctx, list, title),
|
|
createGroupedUnrealizedSectionWithOwnCaps(ctx, list, title),
|
|
createGroupedCostBasisSectionWithPercentiles(list, title),
|
|
...createGroupedActivitySectionBasic(list, title),
|
|
],
|
|
};
|
|
}
|
|
const title = args.title ? `of ${args.title}` : "";
|
|
return {
|
|
name: args.name || "all",
|
|
tree: [
|
|
createSingleSupplyChart(ctx, args, title),
|
|
createSingleUtxoCountChart(args, title),
|
|
createSingleRealizedSectionBasic(ctx, args, title),
|
|
createSingleUnrealizedSectionWithOwnCaps(ctx, args, title),
|
|
createSingleCostBasisSectionWithPercentiles(args, title),
|
|
...createSingleActivitySectionBasic(ctx, args, title),
|
|
],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Basic folder: no adjustedSopr, no percentiles (minAge.*, epoch.*, amount cohorts)
|
|
* @param {PartialContext} ctx
|
|
* @param {CohortBasic | CohortGroupBasic} args
|
|
* @returns {PartialOptionsGroup}
|
|
*/
|
|
export function createCohortFolderBasic(ctx, args) {
|
|
if ("list" in args) {
|
|
const { list } = args;
|
|
const title = args.title ? `by ${args.title}` : "";
|
|
return {
|
|
name: args.name || "all",
|
|
tree: [
|
|
createGroupedSupplySection(ctx, list, title),
|
|
createGroupedUtxoCountChart(list, title),
|
|
createGroupedRealizedSectionBasic(ctx, list, title),
|
|
createGroupedUnrealizedSectionBase(ctx, list, title),
|
|
createGroupedCostBasisSection(list, title),
|
|
...createGroupedActivitySectionBasic(list, title),
|
|
],
|
|
};
|
|
}
|
|
const title = args.title ? `of ${args.title}` : "";
|
|
return {
|
|
name: args.name || "all",
|
|
tree: [
|
|
createSingleSupplyChart(ctx, args, title),
|
|
createSingleUtxoCountChart(args, title),
|
|
createSingleRealizedSectionBasic(ctx, args, title),
|
|
createSingleUnrealizedSectionBase(ctx, args, title),
|
|
createSingleCostBasisSection(args, title),
|
|
...createSingleActivitySectionBasic(ctx, args, title),
|
|
],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create supply chart for single cohort
|
|
* @param {PartialContext} ctx
|
|
* @param {UtxoCohortObject} cohort
|
|
* @param {string} title
|
|
* @returns {PartialChartOption}
|
|
*/
|
|
function createSingleSupplyChart(ctx, cohort, title) {
|
|
return {
|
|
name: "supply",
|
|
title: `Supply ${title}`,
|
|
bottom: createSingleSupplySeries(ctx, cohort),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create supply section for grouped cohorts
|
|
* @param {PartialContext} ctx
|
|
* @param {readonly UtxoCohortObject[]} list
|
|
* @param {string} title
|
|
* @returns {PartialOptionsGroup}
|
|
*/
|
|
function createGroupedSupplySection(ctx, list, title) {
|
|
return {
|
|
name: "supply",
|
|
tree: [
|
|
{
|
|
name: "total",
|
|
title: `Supply ${title}`,
|
|
bottom: createGroupedSupplyTotalSeries(ctx, list),
|
|
},
|
|
{
|
|
name: "in profit",
|
|
title: `Supply In Profit ${title}`,
|
|
bottom: createGroupedSupplyInProfitSeries(list),
|
|
},
|
|
{
|
|
name: "in loss",
|
|
title: `Supply In Loss ${title}`,
|
|
bottom: createGroupedSupplyInLossSeries(list),
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create UTXO count chart for single cohort
|
|
* @param {UtxoCohortObject} cohort
|
|
* @param {string} title
|
|
* @returns {PartialChartOption}
|
|
*/
|
|
function createSingleUtxoCountChart(cohort, title) {
|
|
return {
|
|
name: "utxo count",
|
|
title: `UTXO Count ${title}`,
|
|
bottom: createUtxoCountSeries( [cohort], false),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create UTXO count chart for grouped cohorts
|
|
* @param {readonly UtxoCohortObject[]} list
|
|
* @param {string} title
|
|
* @returns {PartialChartOption}
|
|
*/
|
|
function createGroupedUtxoCountChart(list, title) {
|
|
return {
|
|
name: "utxo count",
|
|
title: `UTXO Count ${title}`,
|
|
bottom: createUtxoCountSeries( list, true),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create realized section with adjusted SOPR (for cohorts with RealizedPattern3/4)
|
|
* @param {PartialContext} ctx
|
|
* @param {CohortAll | CohortFull | CohortWithAdjusted} cohort
|
|
* @param {string} title
|
|
* @returns {PartialOptionsGroup}
|
|
*/
|
|
function createSingleRealizedSectionWithAdjusted(ctx, cohort, title) {
|
|
return {
|
|
name: "Realized",
|
|
tree: [
|
|
createSingleRealizedPriceChart(cohort, title),
|
|
{
|
|
name: "capitalization",
|
|
title: `Realized Capitalization ${title}`,
|
|
bottom: createSingleRealizedCapSeries(ctx, cohort),
|
|
},
|
|
...createSingleRealizedPnlSection(ctx, cohort, title),
|
|
createSingleSoprSectionWithAdjusted(ctx, cohort, title),
|
|
],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create realized section with adjusted SOPR for grouped cohorts
|
|
* @param {PartialContext} ctx
|
|
* @param {readonly (CohortFull | CohortWithAdjusted)[]} list
|
|
* @param {string} title
|
|
* @returns {PartialOptionsGroup}
|
|
*/
|
|
function createGroupedRealizedSectionWithAdjusted(ctx, list, title) {
|
|
return {
|
|
name: "Realized",
|
|
tree: [
|
|
{
|
|
name: "Price",
|
|
title: `Realized Price ${title}`,
|
|
top: createRealizedPriceSeries(list),
|
|
},
|
|
{
|
|
name: "Ratio",
|
|
title: `Realized Price Ratio ${title}`,
|
|
bottom: createRealizedPriceRatioSeries(ctx, list),
|
|
},
|
|
{
|
|
name: "capitalization",
|
|
title: `Realized Capitalization ${title}`,
|
|
bottom: createGroupedRealizedCapSeries(list),
|
|
},
|
|
...createGroupedRealizedPnlSections(ctx, list, title),
|
|
createGroupedSoprSectionWithAdjusted(ctx, list, title),
|
|
],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create realized section without adjusted SOPR (for cohorts with RealizedPattern/2)
|
|
* @param {PartialContext} ctx
|
|
* @param {CohortWithPercentiles | CohortBasic} cohort
|
|
* @param {string} title
|
|
* @returns {PartialOptionsGroup}
|
|
*/
|
|
function createSingleRealizedSectionBasic(ctx, cohort, title) {
|
|
return {
|
|
name: "Realized",
|
|
tree: [
|
|
createSingleRealizedPriceChart(cohort, title),
|
|
{
|
|
name: "capitalization",
|
|
title: `Realized Capitalization ${title}`,
|
|
bottom: createSingleRealizedCapSeries(ctx, cohort),
|
|
},
|
|
...createSingleRealizedPnlSection(ctx, cohort, title),
|
|
createSingleSoprSectionBasic(ctx, cohort, title),
|
|
],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create realized section without adjusted SOPR for grouped cohorts
|
|
* @param {PartialContext} ctx
|
|
* @param {readonly (CohortWithPercentiles | CohortBasic)[]} list
|
|
* @param {string} title
|
|
* @returns {PartialOptionsGroup}
|
|
*/
|
|
function createGroupedRealizedSectionBasic(ctx, list, title) {
|
|
return {
|
|
name: "Realized",
|
|
tree: [
|
|
{
|
|
name: "Price",
|
|
title: `Realized Price ${title}`,
|
|
top: createRealizedPriceSeries(list),
|
|
},
|
|
{
|
|
name: "Ratio",
|
|
title: `Realized Price Ratio ${title}`,
|
|
bottom: createRealizedPriceRatioSeries(ctx, list),
|
|
},
|
|
{
|
|
name: "capitalization",
|
|
title: `Realized Capitalization ${title}`,
|
|
bottom: createGroupedRealizedCapSeries(list),
|
|
},
|
|
...createGroupedRealizedPnlSections(ctx, list, title),
|
|
createGroupedSoprSectionBasic(ctx, list, title),
|
|
],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create realized price chart for single cohort
|
|
* @param {UtxoCohortObject} cohort
|
|
* @param {string} title
|
|
* @returns {PartialChartOption}
|
|
*/
|
|
function createSingleRealizedPriceChart(cohort, title) {
|
|
const { tree, color } = cohort;
|
|
|
|
return {
|
|
name: "price",
|
|
title: `Realized Price ${title}`,
|
|
top: [
|
|
line({
|
|
metric: tree.realized.realizedPrice,
|
|
name: "realized",
|
|
color,
|
|
unit: Unit.usd,
|
|
}),
|
|
],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create realized cap series for single cohort
|
|
* @param {PartialContext} ctx
|
|
* @param {UtxoCohortObject} cohort
|
|
* @returns {AnyFetchedSeriesBlueprint[]}
|
|
*/
|
|
function createSingleRealizedCapSeries(ctx, cohort) {
|
|
const { colors, createPriceLine } = ctx;
|
|
const { color, tree } = cohort;
|
|
|
|
return [
|
|
line({
|
|
metric: tree.realized.realizedCap,
|
|
name: "Capitalization",
|
|
color,
|
|
unit: Unit.usd,
|
|
}),
|
|
baseline({
|
|
metric: tree.realized.realizedCap30dDelta,
|
|
name: "30d change",
|
|
unit: Unit.usd,
|
|
defaultActive: false,
|
|
}),
|
|
createPriceLine({ unit: Unit.usd, defaultActive: false }),
|
|
...("realizedCapRelToOwnMarketCap" in tree.realized
|
|
? [
|
|
baseline({
|
|
metric: tree.realized.realizedCapRelToOwnMarketCap,
|
|
name: "ratio",
|
|
unit: Unit.pctOwnMcap,
|
|
options: { baseValue: { price: 100 } },
|
|
color: [colors.red, colors.green],
|
|
}),
|
|
createPriceLine({
|
|
unit: Unit.pctOwnMcap,
|
|
defaultActive: true,
|
|
number: 100,
|
|
}),
|
|
]
|
|
: []),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Create realized cap series for grouped cohorts
|
|
* @param {readonly UtxoCohortObject[]} list
|
|
* @returns {AnyFetchedSeriesBlueprint[]}
|
|
*/
|
|
function createGroupedRealizedCapSeries(list) {
|
|
return list.map(({ color, name, tree }) =>
|
|
line({
|
|
metric: tree.realized.realizedCap,
|
|
name,
|
|
color,
|
|
unit: Unit.usd,
|
|
}),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Create realized PnL section for single cohort
|
|
* @param {PartialContext} ctx
|
|
* @param {UtxoCohortObject} cohort
|
|
* @param {string} title
|
|
* @returns {PartialOptionsTree}
|
|
*/
|
|
function createSingleRealizedPnlSection(ctx, cohort, title) {
|
|
const {
|
|
colors,
|
|
createPriceLine,
|
|
fromBlockCountWithUnit,
|
|
fromBitcoinPatternWithUnit,
|
|
} = ctx;
|
|
const { tree } = cohort;
|
|
|
|
return [
|
|
{
|
|
name: "pnl",
|
|
title: `Realized Profit And Loss ${title}`,
|
|
bottom: [
|
|
...fromBlockCountWithUnit(
|
|
tree.realized.realizedProfit,
|
|
"Profit",
|
|
Unit.usd,
|
|
colors.green,
|
|
),
|
|
...fromBlockCountWithUnit(
|
|
tree.realized.realizedLoss,
|
|
"Loss",
|
|
Unit.usd,
|
|
colors.red,
|
|
),
|
|
...fromBitcoinPatternWithUnit(
|
|
tree.realized.negRealizedLoss,
|
|
"Negative Loss",
|
|
Unit.usd,
|
|
colors.red,
|
|
),
|
|
...("realizedProfitToLossRatio" in tree.realized
|
|
? [
|
|
line({
|
|
metric: tree.realized.realizedProfitToLossRatio,
|
|
name: "Profit / Loss",
|
|
color: colors.yellow,
|
|
unit: Unit.ratio,
|
|
}),
|
|
]
|
|
: []),
|
|
line({
|
|
metric: tree.realized.totalRealizedPnl,
|
|
name: "Total",
|
|
color: colors.default,
|
|
unit: Unit.usd,
|
|
defaultActive: false,
|
|
}),
|
|
baseline({
|
|
metric: tree.realized.realizedProfitRelToRealizedCap.sum,
|
|
name: "Profit",
|
|
color: colors.green,
|
|
unit: Unit.pctRcap,
|
|
}),
|
|
baseline({
|
|
metric: tree.realized.realizedLossRelToRealizedCap.sum,
|
|
name: "Loss",
|
|
color: colors.red,
|
|
unit: Unit.pctRcap,
|
|
}),
|
|
createPriceLine({ unit: Unit.pctRcap }),
|
|
createPriceLine({ unit: Unit.usd, defaultActive: false }),
|
|
],
|
|
},
|
|
{
|
|
name: "Net pnl",
|
|
title: `Net Realized Profit And Loss ${title}`,
|
|
bottom: [
|
|
...fromBlockCountWithUnit(
|
|
tree.realized.netRealizedPnl,
|
|
"Net",
|
|
Unit.usd,
|
|
),
|
|
baseline({
|
|
metric: tree.realized.netRealizedPnlCumulative30dDelta,
|
|
name: "Cumulative 30d change",
|
|
unit: Unit.usd,
|
|
defaultActive: false,
|
|
}),
|
|
baseline({
|
|
metric: tree.realized.netRealizedPnlRelToRealizedCap.sum,
|
|
name: "Net",
|
|
unit: Unit.pctRcap,
|
|
}),
|
|
baseline({
|
|
metric:
|
|
tree.realized.netRealizedPnlCumulative30dDeltaRelToRealizedCap,
|
|
name: "Cumulative 30d change",
|
|
unit: Unit.pctRcap,
|
|
defaultActive: false,
|
|
}),
|
|
baseline({
|
|
metric: tree.realized.netRealizedPnlCumulative30dDeltaRelToMarketCap,
|
|
name: "Cumulative 30d change",
|
|
unit: Unit.pctMcap,
|
|
}),
|
|
createPriceLine({ unit: Unit.pctMcap }),
|
|
createPriceLine({ unit: Unit.pctRcap }),
|
|
createPriceLine({ unit: Unit.usd }),
|
|
],
|
|
},
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Create realized PnL sections for grouped cohorts
|
|
* @param {PartialContext} ctx
|
|
* @param {readonly UtxoCohortObject[]} list
|
|
* @param {string} title
|
|
* @returns {PartialOptionsTree}
|
|
*/
|
|
function createGroupedRealizedPnlSections(ctx, list, title) {
|
|
const { createPriceLine } = ctx;
|
|
|
|
return [
|
|
{
|
|
name: "profit",
|
|
title: `Realized Profit ${title}`,
|
|
bottom: [
|
|
...list.flatMap(({ color, name, tree }) => [
|
|
line({
|
|
metric: tree.realized.realizedProfit.sum,
|
|
name,
|
|
color,
|
|
unit: Unit.usd,
|
|
}),
|
|
baseline({
|
|
metric: tree.realized.realizedProfitRelToRealizedCap.sum,
|
|
name,
|
|
color,
|
|
unit: Unit.pctRcap,
|
|
}),
|
|
]),
|
|
createPriceLine({ unit: Unit.usd }),
|
|
],
|
|
},
|
|
{
|
|
name: "loss",
|
|
title: `Realized Loss ${title}`,
|
|
bottom: [
|
|
...list.flatMap(({ color, name, tree }) => [
|
|
line({
|
|
metric: tree.realized.realizedLoss.sum,
|
|
name,
|
|
color,
|
|
unit: Unit.usd,
|
|
}),
|
|
baseline({
|
|
metric: tree.realized.realizedLossRelToRealizedCap.sum,
|
|
name,
|
|
color,
|
|
unit: Unit.pctRcap,
|
|
}),
|
|
]),
|
|
createPriceLine({ unit: Unit.usd }),
|
|
],
|
|
},
|
|
{
|
|
name: "Total pnl",
|
|
title: `Total Realized Profit And Loss ${title}`,
|
|
bottom: [
|
|
...list.flatMap(({ color, name, tree }) => [
|
|
line({
|
|
metric: tree.realized.totalRealizedPnl,
|
|
name,
|
|
color,
|
|
unit: Unit.usd,
|
|
}),
|
|
...("realizedProfitToLossRatio" in tree.realized
|
|
? [
|
|
line({
|
|
metric: tree.realized.realizedProfitToLossRatio,
|
|
name,
|
|
color,
|
|
unit: Unit.ratio,
|
|
}),
|
|
]
|
|
: []),
|
|
]),
|
|
],
|
|
},
|
|
{
|
|
name: "Net pnl",
|
|
title: `Net Realized Profit And Loss ${title}`,
|
|
bottom: [
|
|
...list.flatMap(({ color, name, tree }) => [
|
|
baseline({
|
|
metric: tree.realized.netRealizedPnl.sum,
|
|
name,
|
|
color,
|
|
unit: Unit.usd,
|
|
}),
|
|
baseline({
|
|
metric: tree.realized.netRealizedPnlRelToRealizedCap.sum,
|
|
name,
|
|
color,
|
|
unit: Unit.pctRcap,
|
|
}),
|
|
]),
|
|
createPriceLine({ unit: Unit.usd }),
|
|
createPriceLine({ unit: Unit.pctRcap }),
|
|
],
|
|
},
|
|
{
|
|
name: "cumulative",
|
|
tree: [
|
|
{
|
|
name: "profit",
|
|
title: `Cumulative Realized Profit ${title}`,
|
|
bottom: list.flatMap(({ color, name, tree }) => [
|
|
line({
|
|
metric: tree.realized.realizedProfit.cumulative,
|
|
name,
|
|
color,
|
|
unit: Unit.usd,
|
|
}),
|
|
]),
|
|
},
|
|
{
|
|
name: "loss",
|
|
title: `Cumulative Realized Loss ${title}`,
|
|
bottom: list.flatMap(({ color, name, tree }) => [
|
|
line({
|
|
metric: tree.realized.realizedLoss.cumulative,
|
|
name,
|
|
color,
|
|
unit: Unit.usd,
|
|
}),
|
|
]),
|
|
},
|
|
{
|
|
name: "Net pnl",
|
|
title: `Cumulative Net Realized Profit And Loss ${title}`,
|
|
bottom: [
|
|
...list.flatMap(({ color, name, tree }) => [
|
|
baseline({
|
|
metric: tree.realized.netRealizedPnl.cumulative,
|
|
name,
|
|
color,
|
|
unit: Unit.usd,
|
|
defaultActive: false,
|
|
}),
|
|
]),
|
|
createPriceLine({ unit: Unit.usd }),
|
|
],
|
|
},
|
|
{
|
|
name: "Net pnl 30d change",
|
|
title: `Cumulative Net Realized Profit And Loss 30 Day Change ${title}`,
|
|
bottom: [
|
|
...list.flatMap(({ color, name, tree }) => [
|
|
baseline({
|
|
metric: tree.realized.netRealizedPnlCumulative30dDelta,
|
|
name,
|
|
color,
|
|
unit: Unit.usd,
|
|
}),
|
|
baseline({
|
|
metric:
|
|
tree.realized
|
|
.netRealizedPnlCumulative30dDeltaRelToRealizedCap,
|
|
name,
|
|
color,
|
|
unit: Unit.pctRcap,
|
|
}),
|
|
baseline({
|
|
metric:
|
|
tree.realized.netRealizedPnlCumulative30dDeltaRelToMarketCap,
|
|
name,
|
|
color,
|
|
unit: Unit.pctMcap,
|
|
}),
|
|
]),
|
|
createPriceLine({ unit: Unit.usd }),
|
|
createPriceLine({ unit: Unit.pctMcap }),
|
|
createPriceLine({ unit: Unit.pctRcap }),
|
|
],
|
|
},
|
|
],
|
|
},
|
|
];
|
|
}
|
|
|
|
// ============================================================================
|
|
// SOPR Chart Builders (Composable)
|
|
// ============================================================================
|
|
|
|
/**
|
|
* @typedef {Object} CohortWithBaseSopr
|
|
* @property {string} name
|
|
* @property {Color} color
|
|
* @property {{ realized: { sopr: any, sopr7dEma: any, sopr30dEma: any } }} tree
|
|
*/
|
|
|
|
/**
|
|
* @typedef {Object} CohortWithAdjustedSopr
|
|
* @property {string} name
|
|
* @property {Color} color
|
|
* @property {{ realized: { adjustedSopr: any, adjustedSopr7dEma: any, adjustedSopr30dEma: any } }} tree
|
|
*/
|
|
|
|
/**
|
|
* Create single base SOPR chart
|
|
* @param {PartialContext} ctx
|
|
* @param {CohortWithBaseSopr} cohort
|
|
* @param {string} title
|
|
* @returns {PartialChartOption}
|
|
*/
|
|
function createSingleBaseSoprChart(ctx, cohort, title) {
|
|
const { colors, createPriceLine } = ctx;
|
|
const { tree } = cohort;
|
|
|
|
return {
|
|
name: "Normal",
|
|
title: `Spent Output Profit Ratio ${title}`,
|
|
bottom: [
|
|
baseline({
|
|
metric: tree.realized.sopr,
|
|
name: "SOPR",
|
|
unit: Unit.ratio,
|
|
options: { baseValue: { price: 1 } },
|
|
}),
|
|
baseline({
|
|
metric: tree.realized.sopr7dEma,
|
|
name: "7d EMA",
|
|
color: [colors.lime, colors.rose],
|
|
unit: Unit.ratio,
|
|
defaultActive: false,
|
|
options: { baseValue: { price: 1 } },
|
|
}),
|
|
baseline({
|
|
metric: tree.realized.sopr30dEma,
|
|
name: "30d EMA",
|
|
color: [colors.avocado, colors.pink],
|
|
unit: Unit.ratio,
|
|
defaultActive: false,
|
|
options: { baseValue: { price: 1 } },
|
|
}),
|
|
createPriceLine({ number: 1, unit: Unit.ratio }),
|
|
],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create single adjusted SOPR chart (only for age cohorts)
|
|
* @param {PartialContext} ctx
|
|
* @param {CohortWithAdjustedSopr} cohort
|
|
* @param {string} title
|
|
* @returns {PartialChartOption}
|
|
*/
|
|
function createSingleAdjustedSoprChart(ctx, cohort, title) {
|
|
const { colors, createPriceLine } = ctx;
|
|
const { tree } = cohort;
|
|
|
|
return {
|
|
name: "Adjusted",
|
|
title: `Adjusted Spent Output Profit Ratio ${title}`,
|
|
bottom: [
|
|
baseline({
|
|
metric: tree.realized.adjustedSopr,
|
|
name: "Adjusted",
|
|
color: [colors.yellow, colors.fuchsia],
|
|
unit: Unit.ratio,
|
|
options: { baseValue: { price: 1 } },
|
|
}),
|
|
baseline({
|
|
metric: tree.realized.adjustedSopr7dEma,
|
|
name: "Adj. 7d EMA",
|
|
color: [colors.amber, colors.purple],
|
|
unit: Unit.ratio,
|
|
defaultActive: false,
|
|
options: { baseValue: { price: 1 } },
|
|
}),
|
|
baseline({
|
|
metric: tree.realized.adjustedSopr30dEma,
|
|
name: "Adj. 30d EMA",
|
|
color: [colors.orange, colors.violet],
|
|
unit: Unit.ratio,
|
|
defaultActive: false,
|
|
options: { baseValue: { price: 1 } },
|
|
}),
|
|
createPriceLine({ number: 1, unit: Unit.ratio }),
|
|
],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create grouped base SOPR chart
|
|
* @param {PartialContext} ctx
|
|
* @param {readonly CohortWithBaseSopr[]} list
|
|
* @param {string} title
|
|
* @returns {PartialChartOption}
|
|
*/
|
|
function createGroupedBaseSoprChart(ctx, list, title) {
|
|
const { createPriceLine } = ctx;
|
|
|
|
return {
|
|
name: "Normal",
|
|
title: `Spent Output Profit Ratio ${title}`,
|
|
bottom: [
|
|
...list.flatMap(({ color, name, tree }) => [
|
|
baseline({
|
|
metric: tree.realized.sopr,
|
|
name,
|
|
color,
|
|
unit: Unit.ratio,
|
|
options: { baseValue: { price: 1 } },
|
|
}),
|
|
baseline({
|
|
metric: tree.realized.sopr7dEma,
|
|
name: `${name} 7d`,
|
|
color,
|
|
unit: Unit.ratio,
|
|
defaultActive: false,
|
|
options: { baseValue: { price: 1 } },
|
|
}),
|
|
baseline({
|
|
metric: tree.realized.sopr30dEma,
|
|
name: `${name} 30d`,
|
|
color,
|
|
unit: Unit.ratio,
|
|
defaultActive: false,
|
|
options: { baseValue: { price: 1 } },
|
|
}),
|
|
]),
|
|
createPriceLine({ number: 1, unit: Unit.ratio }),
|
|
],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create grouped adjusted SOPR chart (only for age cohorts)
|
|
* @param {PartialContext} ctx
|
|
* @param {readonly CohortWithAdjustedSopr[]} list
|
|
* @param {string} title
|
|
* @returns {PartialChartOption}
|
|
*/
|
|
function createGroupedAdjustedSoprChart(ctx, list, title) {
|
|
const { createPriceLine } = ctx;
|
|
|
|
return {
|
|
name: "Adjusted",
|
|
title: `Adjusted Spent Output Profit Ratio ${title}`,
|
|
bottom: [
|
|
...list.flatMap(({ color, name, tree }) => [
|
|
baseline({
|
|
metric: tree.realized.adjustedSopr,
|
|
name,
|
|
color,
|
|
unit: Unit.ratio,
|
|
options: { baseValue: { price: 1 } },
|
|
}),
|
|
baseline({
|
|
metric: tree.realized.adjustedSopr7dEma,
|
|
name: `${name} 7d`,
|
|
color,
|
|
unit: Unit.ratio,
|
|
defaultActive: false,
|
|
options: { baseValue: { price: 1 } },
|
|
}),
|
|
baseline({
|
|
metric: tree.realized.adjustedSopr30dEma,
|
|
name: `${name} 30d`,
|
|
color,
|
|
unit: Unit.ratio,
|
|
defaultActive: false,
|
|
options: { baseValue: { price: 1 } },
|
|
}),
|
|
]),
|
|
createPriceLine({ number: 1, unit: Unit.ratio }),
|
|
],
|
|
};
|
|
}
|
|
|
|
// ============================================================================
|
|
// SOPR Section Composers
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Create SOPR section with adjusted SOPR (for cohorts with RealizedPattern3/4)
|
|
* @param {PartialContext} ctx
|
|
* @param {CohortAll | CohortFull | CohortWithAdjusted} cohort
|
|
* @param {string} title
|
|
* @returns {PartialOptionsGroup}
|
|
*/
|
|
function createSingleSoprSectionWithAdjusted(ctx, cohort, title) {
|
|
return {
|
|
name: "sopr",
|
|
tree: [
|
|
createSingleBaseSoprChart(ctx, cohort, title),
|
|
createSingleAdjustedSoprChart(ctx, cohort, title),
|
|
],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create grouped SOPR section with adjusted SOPR
|
|
* @param {PartialContext} ctx
|
|
* @param {readonly (CohortFull | CohortWithAdjusted)[]} list
|
|
* @param {string} title
|
|
* @returns {PartialOptionsGroup}
|
|
*/
|
|
function createGroupedSoprSectionWithAdjusted(ctx, list, title) {
|
|
return {
|
|
name: "sopr",
|
|
tree: [
|
|
createGroupedBaseSoprChart(ctx, list, title),
|
|
createGroupedAdjustedSoprChart(ctx, list, title),
|
|
],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create SOPR section without adjusted SOPR (for cohorts with RealizedPattern/2)
|
|
* @param {PartialContext} ctx
|
|
* @param {CohortWithPercentiles | CohortBasic} cohort
|
|
* @param {string} title
|
|
* @returns {PartialOptionsGroup}
|
|
*/
|
|
function createSingleSoprSectionBasic(ctx, cohort, title) {
|
|
return {
|
|
name: "sopr",
|
|
tree: [createSingleBaseSoprChart(ctx, cohort, title)],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create grouped SOPR section without adjusted SOPR
|
|
* @param {PartialContext} ctx
|
|
* @param {readonly (CohortWithPercentiles | CohortBasic)[]} list
|
|
* @param {string} title
|
|
* @returns {PartialOptionsGroup}
|
|
*/
|
|
function createGroupedSoprSectionBasic(ctx, list, title) {
|
|
return {
|
|
name: "sopr",
|
|
tree: [createGroupedBaseSoprChart(ctx, list, title)],
|
|
};
|
|
}
|
|
|
|
// ============================================================================
|
|
// Unrealized Section Helpers (by relative pattern capability)
|
|
// ============================================================================
|
|
|
|
/**
|
|
* @param {PartialContext} ctx
|
|
* @param {RelativeWithMarketCap} rel
|
|
*/
|
|
function createUnrealizedPnlRelToMarketCapMetrics(ctx, rel) {
|
|
const { colors } = ctx;
|
|
return [
|
|
line({
|
|
metric: rel.unrealizedProfitRelToMarketCap,
|
|
name: "Profit",
|
|
color: colors.green,
|
|
unit: Unit.pctMcap,
|
|
}),
|
|
line({
|
|
metric: rel.unrealizedLossRelToMarketCap,
|
|
name: "Loss",
|
|
color: colors.red,
|
|
unit: Unit.pctMcap,
|
|
defaultActive: false,
|
|
}),
|
|
line({
|
|
metric: rel.negUnrealizedLossRelToMarketCap,
|
|
name: "Negative Loss",
|
|
color: colors.red,
|
|
unit: Unit.pctMcap,
|
|
}),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param {PartialContext} ctx
|
|
* @param {RelativeWithOwnMarketCap} rel
|
|
*/
|
|
function createUnrealizedPnlRelToOwnMarketCapMetrics(ctx, rel) {
|
|
const { colors, createPriceLine } = ctx;
|
|
return [
|
|
line({
|
|
metric: rel.unrealizedProfitRelToOwnMarketCap,
|
|
name: "Profit",
|
|
color: colors.green,
|
|
unit: Unit.pctOwnMcap,
|
|
}),
|
|
line({
|
|
metric: rel.unrealizedLossRelToOwnMarketCap,
|
|
name: "Loss",
|
|
color: colors.red,
|
|
unit: Unit.pctOwnMcap,
|
|
defaultActive: false,
|
|
}),
|
|
line({
|
|
metric: rel.negUnrealizedLossRelToOwnMarketCap,
|
|
name: "Negative Loss",
|
|
color: colors.red,
|
|
unit: Unit.pctOwnMcap,
|
|
}),
|
|
createPriceLine({ unit: Unit.pctOwnMcap, number: 100 }),
|
|
createPriceLine({ unit: Unit.pctOwnMcap }),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param {PartialContext} ctx
|
|
* @param {RelativeWithOwnPnl} rel
|
|
*/
|
|
function createUnrealizedPnlRelToOwnPnlMetrics(ctx, rel) {
|
|
const { colors, createPriceLine } = ctx;
|
|
return [
|
|
line({
|
|
metric: rel.unrealizedProfitRelToOwnTotalUnrealizedPnl,
|
|
name: "Profit",
|
|
color: colors.green,
|
|
unit: Unit.pctOwnPnl,
|
|
}),
|
|
line({
|
|
metric: rel.unrealizedLossRelToOwnTotalUnrealizedPnl,
|
|
name: "Loss",
|
|
color: colors.red,
|
|
unit: Unit.pctOwnPnl,
|
|
defaultActive: false,
|
|
}),
|
|
line({
|
|
metric: rel.negUnrealizedLossRelToOwnTotalUnrealizedPnl,
|
|
name: "Negative Loss",
|
|
color: colors.red,
|
|
unit: Unit.pctOwnPnl,
|
|
}),
|
|
createPriceLine({ unit: Unit.pctOwnPnl, number: 100 }),
|
|
createPriceLine({ unit: Unit.pctOwnPnl }),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param {RelativeWithMarketCap} rel
|
|
*/
|
|
function createNetUnrealizedPnlRelToMarketCapMetrics(rel) {
|
|
return [
|
|
baseline({
|
|
metric: rel.netUnrealizedPnlRelToMarketCap,
|
|
name: "Net",
|
|
unit: Unit.pctMcap,
|
|
}),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param {PartialContext} ctx
|
|
* @param {RelativeWithOwnMarketCap} rel
|
|
*/
|
|
function createNetUnrealizedPnlRelToOwnMarketCapMetrics(ctx, rel) {
|
|
const { createPriceLine } = ctx;
|
|
return [
|
|
baseline({
|
|
metric: rel.netUnrealizedPnlRelToOwnMarketCap,
|
|
name: "Net",
|
|
unit: Unit.pctOwnMcap,
|
|
}),
|
|
createPriceLine({ unit: Unit.pctOwnMcap }),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param {PartialContext} ctx
|
|
* @param {RelativeWithOwnPnl} rel
|
|
*/
|
|
function createNetUnrealizedPnlRelToOwnPnlMetrics(ctx, rel) {
|
|
const { createPriceLine } = ctx;
|
|
return [
|
|
baseline({
|
|
metric: rel.netUnrealizedPnlRelToOwnTotalUnrealizedPnl,
|
|
name: "Net",
|
|
unit: Unit.pctOwnPnl,
|
|
}),
|
|
createPriceLine({ unit: Unit.pctOwnPnl }),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Base unrealized metrics (always present)
|
|
* @param {PartialContext} ctx
|
|
* @param {{ unrealized: { totalUnrealizedPnl: AnyMetricPattern, unrealizedProfit: AnyMetricPattern, unrealizedLoss: AnyMetricPattern, negUnrealizedLoss: AnyMetricPattern } }} tree
|
|
*/
|
|
function createUnrealizedPnlBaseMetrics(ctx, tree) {
|
|
const { colors } = ctx;
|
|
return [
|
|
line({
|
|
metric: tree.unrealized.totalUnrealizedPnl,
|
|
name: "Total",
|
|
color: colors.default,
|
|
unit: Unit.usd,
|
|
}),
|
|
line({
|
|
metric: tree.unrealized.unrealizedProfit,
|
|
name: "Profit",
|
|
color: colors.green,
|
|
unit: Unit.usd,
|
|
}),
|
|
line({
|
|
metric: tree.unrealized.unrealizedLoss,
|
|
name: "Loss",
|
|
color: colors.red,
|
|
unit: Unit.usd,
|
|
defaultActive: false,
|
|
}),
|
|
line({
|
|
metric: tree.unrealized.negUnrealizedLoss,
|
|
name: "Negative Loss",
|
|
color: colors.red,
|
|
unit: Unit.usd,
|
|
}),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Base net unrealized metric (always present)
|
|
* @param {{ unrealized: { netUnrealizedPnl: AnyMetricPattern } }} tree
|
|
*/
|
|
function createNetUnrealizedPnlBaseMetric(tree) {
|
|
return baseline({
|
|
metric: tree.unrealized.netUnrealizedPnl,
|
|
name: "Net",
|
|
unit: Unit.ratio,
|
|
options: { baseValue: { price: 0 } },
|
|
});
|
|
}
|
|
|
|
// ============================================================================
|
|
// Unrealized Section Variants (by cohort capability)
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Unrealized section for All cohort (only RelToOwnPnl)
|
|
* @param {PartialContext} ctx
|
|
* @param {CohortAll} cohort
|
|
* @param {string} title
|
|
* @returns {PartialOptionsGroup}
|
|
*/
|
|
function createSingleUnrealizedSectionAll(ctx, cohort, title) {
|
|
const { createPriceLine } = ctx;
|
|
const { tree } = cohort;
|
|
return {
|
|
name: "Unrealized",
|
|
tree: [
|
|
{
|
|
name: "pnl",
|
|
title: `Unrealized Profit And Loss ${title}`,
|
|
bottom: [
|
|
...createUnrealizedPnlBaseMetrics(ctx, tree),
|
|
...createUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative),
|
|
createPriceLine({ unit: Unit.usd, defaultActive: false }),
|
|
],
|
|
},
|
|
{
|
|
name: "Net pnl",
|
|
title: `Net Unrealized Profit And Loss ${title}`,
|
|
bottom: [
|
|
createNetUnrealizedPnlBaseMetric(tree),
|
|
...createNetUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative),
|
|
createPriceLine({ unit: Unit.usd }),
|
|
],
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Unrealized section for Full cohort (all capabilities: MarketCap + OwnMarketCap + OwnPnl)
|
|
* @param {PartialContext} ctx
|
|
* @param {CohortFull} cohort
|
|
* @param {string} title
|
|
* @returns {PartialOptionsGroup}
|
|
*/
|
|
function createSingleUnrealizedSectionFull(ctx, cohort, title) {
|
|
const { createPriceLine } = ctx;
|
|
const { tree } = cohort;
|
|
return {
|
|
name: "Unrealized",
|
|
tree: [
|
|
{
|
|
name: "pnl",
|
|
title: `Unrealized Profit And Loss ${title}`,
|
|
bottom: [
|
|
...createUnrealizedPnlBaseMetrics(ctx, tree),
|
|
...createUnrealizedPnlRelToMarketCapMetrics(ctx, tree.relative),
|
|
...createUnrealizedPnlRelToOwnMarketCapMetrics(ctx, tree.relative),
|
|
...createUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative),
|
|
createPriceLine({ unit: Unit.usd, defaultActive: false }),
|
|
createPriceLine({ unit: Unit.pctMcap, defaultActive: false }),
|
|
],
|
|
},
|
|
{
|
|
name: "Net pnl",
|
|
title: `Net Unrealized Profit And Loss ${title}`,
|
|
bottom: [
|
|
createNetUnrealizedPnlBaseMetric(tree),
|
|
...createNetUnrealizedPnlRelToMarketCapMetrics(tree.relative),
|
|
...createNetUnrealizedPnlRelToOwnMarketCapMetrics(ctx, tree.relative),
|
|
...createNetUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative),
|
|
createPriceLine({ unit: Unit.usd }),
|
|
createPriceLine({ unit: Unit.pctMcap }),
|
|
],
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Unrealized section for WithAdjusted cohort (only MarketCap)
|
|
* @param {PartialContext} ctx
|
|
* @param {CohortWithAdjusted} cohort
|
|
* @param {string} title
|
|
* @returns {PartialOptionsGroup}
|
|
*/
|
|
function createSingleUnrealizedSectionWithMarketCap(ctx, cohort, title) {
|
|
const { createPriceLine } = ctx;
|
|
const { tree } = cohort;
|
|
return {
|
|
name: "Unrealized",
|
|
tree: [
|
|
{
|
|
name: "pnl",
|
|
title: `Unrealized Profit And Loss ${title}`,
|
|
bottom: [
|
|
...createUnrealizedPnlBaseMetrics(ctx, tree),
|
|
...createUnrealizedPnlRelToMarketCapMetrics(ctx, tree.relative),
|
|
createPriceLine({ unit: Unit.usd, defaultActive: false }),
|
|
createPriceLine({ unit: Unit.pctMcap, defaultActive: false }),
|
|
],
|
|
},
|
|
{
|
|
name: "Net pnl",
|
|
title: `Net Unrealized Profit And Loss ${title}`,
|
|
bottom: [
|
|
createNetUnrealizedPnlBaseMetric(tree),
|
|
...createNetUnrealizedPnlRelToMarketCapMetrics(tree.relative),
|
|
createPriceLine({ unit: Unit.usd }),
|
|
createPriceLine({ unit: Unit.pctMcap }),
|
|
],
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Unrealized section for patterns with OwnMarketCap + OwnPnl (RelativePattern2, RelativePattern5)
|
|
* Used by: LongTerm, AgeRange
|
|
* @param {PartialContext} ctx
|
|
* @param {{ tree: { unrealized: PatternAll["unrealized"], relative: RelativeWithOwnMarketCap } }} cohort
|
|
* @param {string} title
|
|
* @returns {PartialOptionsGroup}
|
|
*/
|
|
function createSingleUnrealizedSectionWithOwnCaps(ctx, cohort, title) {
|
|
const { createPriceLine } = ctx;
|
|
const { tree } = cohort;
|
|
return {
|
|
name: "Unrealized",
|
|
tree: [
|
|
{
|
|
name: "pnl",
|
|
title: `Unrealized Profit And Loss ${title}`,
|
|
bottom: [
|
|
...createUnrealizedPnlBaseMetrics(ctx, tree),
|
|
...createUnrealizedPnlRelToOwnMarketCapMetrics(ctx, tree.relative),
|
|
...createUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative),
|
|
createPriceLine({ unit: Unit.usd, defaultActive: false }),
|
|
],
|
|
},
|
|
{
|
|
name: "Net pnl",
|
|
title: `Net Unrealized Profit And Loss ${title}`,
|
|
bottom: [
|
|
createNetUnrealizedPnlBaseMetric(tree),
|
|
...createNetUnrealizedPnlRelToOwnMarketCapMetrics(ctx, tree.relative),
|
|
...createNetUnrealizedPnlRelToOwnPnlMetrics(ctx, tree.relative),
|
|
createPriceLine({ unit: Unit.usd }),
|
|
],
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Unrealized section with only base metrics (no relative unrealized)
|
|
* Used by: Epoch cohorts (RelativePattern4)
|
|
* @param {PartialContext} ctx
|
|
* @param {{ tree: { unrealized: PatternAll["unrealized"] } }} cohort
|
|
* @param {string} title
|
|
* @returns {PartialOptionsGroup}
|
|
*/
|
|
function createSingleUnrealizedSectionBase(ctx, cohort, title) {
|
|
const { createPriceLine } = ctx;
|
|
const { tree } = cohort;
|
|
return {
|
|
name: "Unrealized",
|
|
tree: [
|
|
{
|
|
name: "pnl",
|
|
title: `Unrealized Profit And Loss ${title}`,
|
|
bottom: [
|
|
...createUnrealizedPnlBaseMetrics(ctx, tree),
|
|
createPriceLine({ unit: Unit.usd, defaultActive: false }),
|
|
],
|
|
},
|
|
{
|
|
name: "Net pnl",
|
|
title: `Net Unrealized Profit And Loss ${title}`,
|
|
bottom: [
|
|
createNetUnrealizedPnlBaseMetric(tree),
|
|
createPriceLine({ unit: Unit.usd }),
|
|
],
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Grouped unrealized base charts (profit, loss, total pnl)
|
|
* @param {readonly { color: Color, name: string, tree: { unrealized: PatternAll["unrealized"] } }[]} list
|
|
* @param {string} title
|
|
*/
|
|
function createGroupedUnrealizedBaseCharts(list, title) {
|
|
return [
|
|
{
|
|
name: "profit",
|
|
title: `Unrealized Profit ${title}`,
|
|
bottom: list.flatMap(({ color, name, tree }) => [
|
|
line({
|
|
metric: tree.unrealized.unrealizedProfit,
|
|
name,
|
|
color,
|
|
unit: Unit.usd,
|
|
}),
|
|
]),
|
|
},
|
|
{
|
|
name: "loss",
|
|
title: `Unrealized Loss ${title}`,
|
|
bottom: list.flatMap(({ color, name, tree }) => [
|
|
line({
|
|
metric: tree.unrealized.unrealizedLoss,
|
|
name,
|
|
color,
|
|
unit: Unit.usd,
|
|
}),
|
|
]),
|
|
},
|
|
{
|
|
name: "total pnl",
|
|
title: `Unrealized Total Profit And Loss ${title}`,
|
|
bottom: list.flatMap(({ color, name, tree }) => [
|
|
line({
|
|
metric: tree.unrealized.totalUnrealizedPnl,
|
|
name,
|
|
color,
|
|
unit: Unit.usd,
|
|
}),
|
|
]),
|
|
},
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Grouped unrealized section for Full cohorts (all relative capabilities)
|
|
* @param {PartialContext} ctx
|
|
* @param {readonly CohortFull[]} list
|
|
* @param {string} title
|
|
* @returns {PartialOptionsGroup}
|
|
*/
|
|
function createGroupedUnrealizedSectionFull(ctx, list, title) {
|
|
const { createPriceLine } = ctx;
|
|
return {
|
|
name: "Unrealized",
|
|
tree: [
|
|
...createGroupedUnrealizedBaseCharts(list, title),
|
|
{
|
|
name: "Net pnl",
|
|
title: `Net Unrealized Profit And Loss ${title}`,
|
|
bottom: [
|
|
...list.flatMap(({ color, name, tree }) => [
|
|
baseline({
|
|
metric: tree.unrealized.netUnrealizedPnl,
|
|
name,
|
|
color,
|
|
unit: Unit.ratio,
|
|
}),
|
|
baseline({
|
|
metric: tree.relative.netUnrealizedPnlRelToMarketCap,
|
|
name,
|
|
color,
|
|
unit: Unit.pctMcap,
|
|
}),
|
|
baseline({
|
|
metric: tree.relative.netUnrealizedPnlRelToOwnMarketCap,
|
|
name,
|
|
color,
|
|
unit: Unit.pctOwnMcap,
|
|
}),
|
|
baseline({
|
|
metric: tree.relative.netUnrealizedPnlRelToOwnTotalUnrealizedPnl,
|
|
name,
|
|
color,
|
|
unit: Unit.pctOwnPnl,
|
|
}),
|
|
]),
|
|
createPriceLine({ unit: Unit.usd }),
|
|
createPriceLine({ unit: Unit.pctMcap }),
|
|
createPriceLine({ unit: Unit.pctOwnMcap }),
|
|
createPriceLine({ unit: Unit.pctOwnPnl }),
|
|
],
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Grouped unrealized section for WithAdjusted cohorts (only MarketCap)
|
|
* @param {PartialContext} ctx
|
|
* @param {readonly CohortWithAdjusted[]} list
|
|
* @param {string} title
|
|
* @returns {PartialOptionsGroup}
|
|
*/
|
|
function createGroupedUnrealizedSectionWithMarketCap(ctx, list, title) {
|
|
const { createPriceLine } = ctx;
|
|
return {
|
|
name: "Unrealized",
|
|
tree: [
|
|
...createGroupedUnrealizedBaseCharts(list, title),
|
|
{
|
|
name: "Net pnl",
|
|
title: `Net Unrealized Profit And Loss ${title}`,
|
|
bottom: [
|
|
...list.flatMap(({ color, name, tree }) => [
|
|
baseline({
|
|
metric: tree.unrealized.netUnrealizedPnl,
|
|
name,
|
|
color,
|
|
unit: Unit.ratio,
|
|
}),
|
|
baseline({
|
|
metric: tree.relative.netUnrealizedPnlRelToMarketCap,
|
|
name,
|
|
color,
|
|
unit: Unit.pctMcap,
|
|
}),
|
|
]),
|
|
createPriceLine({ unit: Unit.usd }),
|
|
createPriceLine({ unit: Unit.pctMcap }),
|
|
],
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Grouped unrealized section for WithPercentiles cohorts (OwnMarketCap + OwnPnl)
|
|
* @param {PartialContext} ctx
|
|
* @param {readonly CohortWithPercentiles[]} list
|
|
* @param {string} title
|
|
* @returns {PartialOptionsGroup}
|
|
*/
|
|
function createGroupedUnrealizedSectionWithOwnCaps(ctx, list, title) {
|
|
const { createPriceLine } = ctx;
|
|
return {
|
|
name: "Unrealized",
|
|
tree: [
|
|
...createGroupedUnrealizedBaseCharts(list, title),
|
|
{
|
|
name: "Net pnl",
|
|
title: `Net Unrealized Profit And Loss ${title}`,
|
|
bottom: [
|
|
...list.flatMap(({ color, name, tree }) => [
|
|
baseline({
|
|
metric: tree.unrealized.netUnrealizedPnl,
|
|
name,
|
|
color,
|
|
unit: Unit.ratio,
|
|
}),
|
|
baseline({
|
|
metric: tree.relative.netUnrealizedPnlRelToOwnMarketCap,
|
|
name,
|
|
color,
|
|
unit: Unit.pctOwnMcap,
|
|
}),
|
|
baseline({
|
|
metric: tree.relative.netUnrealizedPnlRelToOwnTotalUnrealizedPnl,
|
|
name,
|
|
color,
|
|
unit: Unit.pctOwnPnl,
|
|
}),
|
|
]),
|
|
createPriceLine({ unit: Unit.usd }),
|
|
createPriceLine({ unit: Unit.pctOwnMcap }),
|
|
createPriceLine({ unit: Unit.pctOwnPnl }),
|
|
],
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Grouped unrealized section for Basic cohorts (base only)
|
|
* @param {PartialContext} ctx
|
|
* @param {readonly CohortBasic[]} list
|
|
* @param {string} title
|
|
* @returns {PartialOptionsGroup}
|
|
*/
|
|
function createGroupedUnrealizedSectionBase(ctx, list, title) {
|
|
const { createPriceLine } = ctx;
|
|
return {
|
|
name: "Unrealized",
|
|
tree: [
|
|
...createGroupedUnrealizedBaseCharts(list, title),
|
|
{
|
|
name: "Net pnl",
|
|
title: `Net Unrealized Profit And Loss ${title}`,
|
|
bottom: [
|
|
...list.flatMap(({ color, name, tree }) => [
|
|
baseline({
|
|
metric: tree.unrealized.netUnrealizedPnl,
|
|
name,
|
|
color,
|
|
unit: Unit.ratio,
|
|
}),
|
|
]),
|
|
createPriceLine({ unit: Unit.usd }),
|
|
],
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create cost basis section for single cohort WITH percentiles
|
|
* @param {CohortAll | CohortFull | CohortWithPercentiles} cohort
|
|
* @param {string} title
|
|
* @returns {PartialOptionsGroup}
|
|
*/
|
|
function createSingleCostBasisSectionWithPercentiles(cohort, title) {
|
|
const { color, tree } = cohort;
|
|
|
|
return {
|
|
name: "Cost Basis",
|
|
tree: [
|
|
{
|
|
name: "Average",
|
|
title: `Cost Basis ${title}`,
|
|
top: [
|
|
line({
|
|
metric: tree.realized.realizedPrice,
|
|
name: "Average",
|
|
color,
|
|
unit: Unit.usd,
|
|
}),
|
|
line({
|
|
metric: tree.costBasis.min,
|
|
name: "Min",
|
|
color,
|
|
unit: Unit.usd,
|
|
defaultActive: false,
|
|
}),
|
|
line({
|
|
metric: tree.costBasis.max,
|
|
name: "Max",
|
|
color,
|
|
unit: Unit.usd,
|
|
}),
|
|
],
|
|
},
|
|
{
|
|
name: "percentiles",
|
|
title: `Cost Basis Percentiles ${title}`,
|
|
top: createCostBasisPercentilesSeries( [cohort], false),
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create cost basis section for grouped cohorts WITH percentiles
|
|
* @param {readonly (CohortFull | CohortWithPercentiles)[]} list
|
|
* @param {string} title
|
|
* @returns {PartialOptionsGroup}
|
|
*/
|
|
function createGroupedCostBasisSectionWithPercentiles(list, title) {
|
|
return {
|
|
name: "Cost Basis",
|
|
tree: [
|
|
{
|
|
name: "Average",
|
|
title: `Average Cost Basis ${title}`,
|
|
top: list.map(({ color, name, tree }) =>
|
|
line({
|
|
metric: tree.realized.realizedPrice,
|
|
name,
|
|
color,
|
|
unit: Unit.usd,
|
|
}),
|
|
),
|
|
},
|
|
{
|
|
name: "Min",
|
|
title: `Min Cost Basis ${title}`,
|
|
top: list.map(({ color, name, tree }) =>
|
|
line({ metric: tree.costBasis.min, name, color, unit: Unit.usd }),
|
|
),
|
|
},
|
|
{
|
|
name: "Max",
|
|
title: `Max Cost Basis ${title}`,
|
|
top: list.map(({ color, name, tree }) =>
|
|
line({ metric: tree.costBasis.max, name, color, unit: Unit.usd }),
|
|
),
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create cost basis section for single cohort (no percentiles)
|
|
* @param {CohortWithAdjusted | CohortBasic} cohort
|
|
* @param {string} title
|
|
* @returns {PartialOptionsGroup}
|
|
*/
|
|
function createSingleCostBasisSection(cohort, title) {
|
|
const { color, tree } = cohort;
|
|
|
|
return {
|
|
name: "Cost Basis",
|
|
tree: [
|
|
{
|
|
name: "cost basis",
|
|
title: `Cost Basis ${title}`,
|
|
top: [
|
|
line({
|
|
metric: tree.realized.realizedPrice,
|
|
name: "Average",
|
|
color,
|
|
unit: Unit.usd,
|
|
}),
|
|
line({
|
|
metric: tree.costBasis.min,
|
|
name: "Min",
|
|
color,
|
|
unit: Unit.usd,
|
|
defaultActive: false,
|
|
}),
|
|
line({
|
|
metric: tree.costBasis.max,
|
|
name: "Max",
|
|
color,
|
|
unit: Unit.usd,
|
|
}),
|
|
],
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create cost basis section for grouped cohorts (no percentiles)
|
|
* @param {readonly (CohortWithAdjusted | CohortBasic)[]} list
|
|
* @param {string} title
|
|
* @returns {PartialOptionsGroup}
|
|
*/
|
|
function createGroupedCostBasisSection(list, title) {
|
|
return {
|
|
name: "Cost Basis",
|
|
tree: [
|
|
{
|
|
name: "Average",
|
|
title: `Average Cost Basis ${title}`,
|
|
top: list.map(({ color, name, tree }) =>
|
|
line({
|
|
metric: tree.realized.realizedPrice,
|
|
name,
|
|
color,
|
|
unit: Unit.usd,
|
|
}),
|
|
),
|
|
},
|
|
{
|
|
name: "Min",
|
|
title: `Min Cost Basis ${title}`,
|
|
top: list.map(({ color, name, tree }) =>
|
|
line({ metric: tree.costBasis.min, name, color, unit: Unit.usd }),
|
|
),
|
|
},
|
|
{
|
|
name: "Max",
|
|
title: `Max Cost Basis ${title}`,
|
|
top: list.map(({ color, name, tree }) =>
|
|
line({ metric: tree.costBasis.max, name, color, unit: Unit.usd }),
|
|
),
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create activity section with adjusted values (for cohorts with RealizedPattern3/4)
|
|
* @param {PartialContext} ctx
|
|
* @param {CohortAll | CohortFull | CohortWithAdjusted} cohort
|
|
* @param {string} title
|
|
* @returns {PartialOptionsTree}
|
|
*/
|
|
function createSingleActivitySectionWithAdjusted(ctx, cohort, title) {
|
|
const { colors } = ctx;
|
|
const { tree, color } = cohort;
|
|
|
|
return [
|
|
{
|
|
name: "Sell Side Risk",
|
|
title: `Sell Side Risk Ratio ${title}`,
|
|
bottom: [
|
|
line({
|
|
metric: tree.realized.sellSideRiskRatio,
|
|
name: "Raw",
|
|
color: colors.orange,
|
|
unit: Unit.ratio,
|
|
}),
|
|
line({
|
|
metric: tree.realized.sellSideRiskRatio7dEma,
|
|
name: "7d EMA",
|
|
color: colors.red,
|
|
unit: Unit.ratio,
|
|
defaultActive: false,
|
|
}),
|
|
line({
|
|
metric: tree.realized.sellSideRiskRatio30dEma,
|
|
name: "30d EMA",
|
|
color: colors.rose,
|
|
unit: Unit.ratio,
|
|
defaultActive: false,
|
|
}),
|
|
],
|
|
},
|
|
{
|
|
name: "value",
|
|
tree: [
|
|
{
|
|
name: "created",
|
|
title: `Value Created ${title}`,
|
|
bottom: [
|
|
line({
|
|
metric: tree.realized.valueCreated,
|
|
name: "Normal",
|
|
color: colors.emerald,
|
|
unit: Unit.usd,
|
|
}),
|
|
line({
|
|
metric: tree.realized.adjustedValueCreated,
|
|
name: "Adjusted",
|
|
color: colors.lime,
|
|
unit: Unit.usd,
|
|
}),
|
|
],
|
|
},
|
|
{
|
|
name: "destroyed",
|
|
title: `Value Destroyed ${title}`,
|
|
bottom: [
|
|
line({
|
|
metric: tree.realized.valueDestroyed,
|
|
name: "Normal",
|
|
color: colors.red,
|
|
unit: Unit.usd,
|
|
}),
|
|
line({
|
|
metric: tree.realized.adjustedValueDestroyed,
|
|
name: "Adjusted",
|
|
color: colors.pink,
|
|
unit: Unit.usd,
|
|
}),
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
name: "Coins Destroyed",
|
|
title: `Coins Destroyed ${title}`,
|
|
bottom: [
|
|
line({
|
|
metric: tree.activity.coinblocksDestroyed.sum,
|
|
name: "Coinblocks",
|
|
color,
|
|
unit: Unit.coinblocks,
|
|
}),
|
|
line({
|
|
metric: tree.activity.coinblocksDestroyed.cumulative,
|
|
name: "Cumulative",
|
|
color,
|
|
unit: Unit.coinblocks,
|
|
defaultActive: false,
|
|
}),
|
|
line({
|
|
metric: tree.activity.coindaysDestroyed.sum,
|
|
name: "Coindays",
|
|
color,
|
|
unit: Unit.coindays,
|
|
}),
|
|
line({
|
|
metric: tree.activity.coindaysDestroyed.cumulative,
|
|
name: "Cumulative",
|
|
color,
|
|
unit: Unit.coindays,
|
|
defaultActive: false,
|
|
}),
|
|
],
|
|
},
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Create activity section without adjusted values (for cohorts with RealizedPattern/2)
|
|
* @param {PartialContext} ctx
|
|
* @param {CohortWithPercentiles | CohortBasic} cohort
|
|
* @param {string} title
|
|
* @returns {PartialOptionsTree}
|
|
*/
|
|
function createSingleActivitySectionBasic(ctx, cohort, title) {
|
|
const { colors } = ctx;
|
|
const { tree, color } = cohort;
|
|
|
|
return [
|
|
{
|
|
name: "Sell Side Risk",
|
|
title: `Sell Side Risk Ratio ${title}`,
|
|
bottom: [
|
|
line({
|
|
metric: tree.realized.sellSideRiskRatio,
|
|
name: "Raw",
|
|
color: colors.orange,
|
|
unit: Unit.ratio,
|
|
}),
|
|
line({
|
|
metric: tree.realized.sellSideRiskRatio7dEma,
|
|
name: "7d EMA",
|
|
color: colors.red,
|
|
unit: Unit.ratio,
|
|
defaultActive: false,
|
|
}),
|
|
line({
|
|
metric: tree.realized.sellSideRiskRatio30dEma,
|
|
name: "30d EMA",
|
|
color: colors.rose,
|
|
unit: Unit.ratio,
|
|
defaultActive: false,
|
|
}),
|
|
],
|
|
},
|
|
{
|
|
name: "value",
|
|
tree: [
|
|
{
|
|
name: "created",
|
|
title: `Value Created ${title}`,
|
|
bottom: [
|
|
line({
|
|
metric: tree.realized.valueCreated,
|
|
name: "Value Created",
|
|
color: colors.emerald,
|
|
unit: Unit.usd,
|
|
}),
|
|
],
|
|
},
|
|
{
|
|
name: "destroyed",
|
|
title: `Value Destroyed ${title}`,
|
|
bottom: [
|
|
line({
|
|
metric: tree.realized.valueDestroyed,
|
|
name: "Value Destroyed",
|
|
color: colors.red,
|
|
unit: Unit.usd,
|
|
}),
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
name: "Coins Destroyed",
|
|
title: `Coins Destroyed ${title}`,
|
|
bottom: [
|
|
line({
|
|
metric: tree.activity.coinblocksDestroyed.sum,
|
|
name: "Coinblocks",
|
|
color,
|
|
unit: Unit.coinblocks,
|
|
}),
|
|
line({
|
|
metric: tree.activity.coinblocksDestroyed.cumulative,
|
|
name: "Cumulative",
|
|
color,
|
|
unit: Unit.coinblocks,
|
|
defaultActive: false,
|
|
}),
|
|
line({
|
|
metric: tree.activity.coindaysDestroyed.sum,
|
|
name: "Coindays",
|
|
color,
|
|
unit: Unit.coindays,
|
|
}),
|
|
line({
|
|
metric: tree.activity.coindaysDestroyed.cumulative,
|
|
name: "Cumulative",
|
|
color,
|
|
unit: Unit.coindays,
|
|
defaultActive: false,
|
|
}),
|
|
],
|
|
},
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Create activity section for grouped cohorts with adjusted values (for cohorts with RealizedPattern3/4)
|
|
* @param {readonly (CohortFull | CohortWithAdjusted)[]} list
|
|
* @param {string} title
|
|
* @returns {PartialOptionsTree}
|
|
*/
|
|
function createGroupedActivitySectionWithAdjusted(list, title) {
|
|
return [
|
|
{
|
|
name: "Sell Side Risk",
|
|
title: `Sell Side Risk Ratio ${title}`,
|
|
bottom: list.flatMap(({ color, name, tree }) => [
|
|
line({
|
|
metric: tree.realized.sellSideRiskRatio,
|
|
name,
|
|
color,
|
|
unit: Unit.ratio,
|
|
}),
|
|
]),
|
|
},
|
|
{
|
|
name: "value",
|
|
tree: [
|
|
{
|
|
name: "created",
|
|
tree: [
|
|
{
|
|
name: "Normal",
|
|
title: `Value Created ${title}`,
|
|
bottom: list.flatMap(({ color, name, tree }) => [
|
|
line({
|
|
metric: tree.realized.valueCreated,
|
|
name,
|
|
color,
|
|
unit: Unit.usd,
|
|
}),
|
|
]),
|
|
},
|
|
{
|
|
name: "Adjusted",
|
|
title: `Adjusted Value Created ${title}`,
|
|
bottom: list.flatMap(({ color, name, tree }) => [
|
|
line({
|
|
metric: tree.realized.adjustedValueCreated,
|
|
name,
|
|
color,
|
|
unit: Unit.usd,
|
|
}),
|
|
]),
|
|
},
|
|
],
|
|
},
|
|
{
|
|
name: "destroyed",
|
|
tree: [
|
|
{
|
|
name: "Normal",
|
|
title: `Value Destroyed ${title}`,
|
|
bottom: list.flatMap(({ color, name, tree }) => [
|
|
line({
|
|
metric: tree.realized.valueDestroyed,
|
|
name,
|
|
color,
|
|
unit: Unit.usd,
|
|
}),
|
|
]),
|
|
},
|
|
{
|
|
name: "Adjusted",
|
|
title: `Adjusted Value Destroyed ${title}`,
|
|
bottom: list.flatMap(({ color, name, tree }) => [
|
|
line({
|
|
metric: tree.realized.adjustedValueDestroyed,
|
|
name,
|
|
color,
|
|
unit: Unit.usd,
|
|
}),
|
|
]),
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
name: "Coins Destroyed",
|
|
tree: [
|
|
{
|
|
name: "Sum",
|
|
title: `Sum of Coins Destroyed ${title}`,
|
|
bottom: list.flatMap(({ color, name, tree }) => [
|
|
line({
|
|
metric: tree.activity.coinblocksDestroyed.sum,
|
|
name,
|
|
color,
|
|
unit: Unit.coinblocks,
|
|
}),
|
|
line({
|
|
metric: tree.activity.coindaysDestroyed.sum,
|
|
name,
|
|
color,
|
|
unit: Unit.coindays,
|
|
}),
|
|
]),
|
|
},
|
|
{
|
|
name: "Cumulative",
|
|
title: `Cumulative Coins Destroyed ${title}`,
|
|
bottom: list.flatMap(({ color, name, tree }) => [
|
|
line({
|
|
metric: tree.activity.coinblocksDestroyed.cumulative,
|
|
name,
|
|
color,
|
|
unit: Unit.coinblocks,
|
|
}),
|
|
line({
|
|
metric: tree.activity.coindaysDestroyed.cumulative,
|
|
name,
|
|
color,
|
|
unit: Unit.coindays,
|
|
}),
|
|
]),
|
|
},
|
|
],
|
|
},
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Create activity section for grouped cohorts without adjusted values (for cohorts with RealizedPattern/2)
|
|
* @param {readonly (CohortWithPercentiles | CohortBasic)[]} list
|
|
* @param {string} title
|
|
* @returns {PartialOptionsTree}
|
|
*/
|
|
function createGroupedActivitySectionBasic(list, title) {
|
|
return [
|
|
{
|
|
name: "Sell Side Risk",
|
|
title: `Sell Side Risk Ratio ${title}`,
|
|
bottom: list.flatMap(({ color, name, tree }) => [
|
|
line({
|
|
metric: tree.realized.sellSideRiskRatio,
|
|
name,
|
|
color,
|
|
unit: Unit.ratio,
|
|
}),
|
|
]),
|
|
},
|
|
{
|
|
name: "value",
|
|
tree: [
|
|
{
|
|
name: "created",
|
|
title: `Value Created ${title}`,
|
|
bottom: list.flatMap(({ color, name, tree }) => [
|
|
line({
|
|
metric: tree.realized.valueCreated,
|
|
name,
|
|
color,
|
|
unit: Unit.usd,
|
|
}),
|
|
]),
|
|
},
|
|
{
|
|
name: "destroyed",
|
|
title: `Value Destroyed ${title}`,
|
|
bottom: list.flatMap(({ color, name, tree }) => [
|
|
line({
|
|
metric: tree.realized.valueDestroyed,
|
|
name,
|
|
color,
|
|
unit: Unit.usd,
|
|
}),
|
|
]),
|
|
},
|
|
],
|
|
},
|
|
{
|
|
name: "Coins Destroyed",
|
|
tree: [
|
|
{
|
|
name: "Sum",
|
|
title: `Sum of Coins Destroyed ${title}`,
|
|
bottom: list.flatMap(({ color, name, tree }) => [
|
|
line({
|
|
metric: tree.activity.coinblocksDestroyed.sum,
|
|
name,
|
|
color,
|
|
unit: Unit.coinblocks,
|
|
}),
|
|
line({
|
|
metric: tree.activity.coindaysDestroyed.sum,
|
|
name,
|
|
color,
|
|
unit: Unit.coindays,
|
|
}),
|
|
]),
|
|
},
|
|
{
|
|
name: "Cumulative",
|
|
title: `Cumulative Coins Destroyed ${title}`,
|
|
bottom: list.flatMap(({ color, name, tree }) => [
|
|
line({
|
|
metric: tree.activity.coinblocksDestroyed.cumulative,
|
|
name,
|
|
color,
|
|
unit: Unit.coinblocks,
|
|
}),
|
|
line({
|
|
metric: tree.activity.coindaysDestroyed.cumulative,
|
|
name,
|
|
color,
|
|
unit: Unit.coindays,
|
|
}),
|
|
]),
|
|
},
|
|
],
|
|
},
|
|
];
|
|
}
|