global: snapshot part 13

This commit is contained in:
nym21
2026-03-21 13:25:06 +01:00
parent 485f118a5f
commit 1ed4f258b4
13 changed files with 918 additions and 12813 deletions

View File

@@ -1063,10 +1063,9 @@ mod tests {
}
#[test]
fn test_mixed_empty_fills_loss_from_shortest_leaf() {
// Integration test: "loss" child returns same base as parent (because
// its children like neg_realized_loss break the prefix). The mixed-empty
// fix should fill it from shortest leaf "utxos_realized_loss".
fn test_loss_with_neg_suffix_has_correct_field_parts() {
// Integration test: "loss" child has suffix-named children (realized_loss,
// realized_loss_neg) so it returns a proper base that differs from parent.
use brk_types::{SeriesLeaf, SeriesLeafWithSchema, TreeNode};
fn leaf(name: &str) -> TreeNode {
@@ -1084,7 +1083,7 @@ mod tests {
TreeNode::Branch(
[
("base".into(), leaf("utxos_realized_loss")),
("negative".into(), leaf("utxos_neg_realized_loss")),
("negative".into(), leaf("utxos_realized_loss_neg")),
]
.into_iter()
.collect(),
@@ -1115,9 +1114,7 @@ mod tests {
assert!(!result.has_outlier);
assert_eq!(result.field_parts.get("cap"), Some(&"realized_cap".to_string()));
assert_eq!(result.field_parts.get("mvrv"), Some(&"mvrv".to_string()));
// loss stays empty after first pass (child returned same base as parent).
// The second pass (fill_mixed_empty_field_parts) fills it for templated
// children, but that requires pattern_lookup which this test doesn't set up.
assert_eq!(result.field_parts.get("loss"), Some(&"".to_string()));
// loss branch returns base "utxos_realized_loss" which yields field_part "realized_loss"
assert_eq!(result.field_parts.get("loss"), Some(&"realized_loss".to_string()));
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -52,7 +52,7 @@ impl RealizedCore {
let minimal = RealizedMinimal::forced_import(cfg)?;
let neg_loss_base = LazyPerBlock::from_height_source::<NegCentsUnsignedToDollars>(
&cfg.name("neg_realized_loss"),
&cfg.name("realized_loss_neg"),
cfg.version + Version::ONE,
minimal.loss.block.cents.read_only_boxed_clone(),
cfg.indexes,
@@ -60,7 +60,7 @@ impl RealizedCore {
let neg_loss_sum = minimal.loss.sum.0.map_with_suffix(|suffix, slot| {
LazyPerBlock::from_height_source::<NegCentsUnsignedToDollars>(
&cfg.name(&format!("neg_realized_loss_sum_{suffix}")),
&cfg.name(&format!("realized_loss_neg_sum_{suffix}")),
cfg.version + Version::ONE,
slot.cents.height.read_only_boxed_clone(),
cfg.indexes,

View File

@@ -6,8 +6,7 @@ use vecdb::{AnyStoredVec, AnyVec, Exit, Rw, StorageMode, WritableVec};
use crate::{distribution::state::{CohortState, CostBasisOps, RealizedOps}, prices};
use crate::internal::{
AmountPerBlock, HalveCents, HalveDollars, HalveSats, HalveSatsToBitcoin,
LazyAmountPerBlock, LazyRollingDeltasFromHeight,
AmountPerBlock, LazyRollingDeltasFromHeight,
};
use crate::distribution::metrics::ImportConfig;
@@ -16,20 +15,12 @@ use crate::distribution::metrics::ImportConfig;
#[derive(Traversable)]
pub struct SupplyBase<M: StorageMode = Rw> {
pub total: AmountPerBlock<M>,
pub half: LazyAmountPerBlock,
pub delta: LazyRollingDeltasFromHeight<Sats, SatsSigned, BasisPointsSigned32>,
}
impl SupplyBase {
pub(crate) fn forced_import(cfg: &ImportConfig) -> Result<Self> {
let supply = cfg.import("supply", Version::ZERO)?;
let supply_half = LazyAmountPerBlock::from_block_source::<
HalveSats,
HalveSatsToBitcoin,
HalveCents,
HalveDollars,
>(&cfg.name("supply_half"), &supply, cfg.version);
let supply: AmountPerBlock = cfg.import("supply", Version::ZERO)?;
let delta = LazyRollingDeltasFromHeight::new(
&cfg.name("supply_delta"),
@@ -41,7 +32,6 @@ impl SupplyBase {
Ok(Self {
total: supply,
half: supply_half,
delta,
})
}

View File

@@ -6,7 +6,9 @@ use vecdb::{AnyStoredVec, AnyVec, Exit, Rw, StorageMode, WritableVec};
use crate::{distribution::state::UnrealizedState, prices};
use crate::internal::AmountPerBlock;
use crate::internal::{
AmountPerBlock, HalveCents, HalveDollars, HalveSats, HalveSatsToBitcoin, LazyAmountPerBlock,
};
use crate::distribution::metrics::ImportConfig;
@@ -20,6 +22,7 @@ pub struct SupplyCore<M: StorageMode = Rw> {
#[traversable(flatten)]
pub base: SupplyBase<M>,
pub half: LazyAmountPerBlock,
pub in_profit: AmountPerBlock<M>,
pub in_loss: AmountPerBlock<M>,
}
@@ -29,8 +32,16 @@ impl SupplyCore {
let v0 = Version::ZERO;
let base = SupplyBase::forced_import(cfg)?;
let half = LazyAmountPerBlock::from_block_source::<
HalveSats,
HalveSatsToBitcoin,
HalveCents,
HalveDollars,
>(&cfg.name("supply_half"), &base.total, cfg.version);
Ok(Self {
base,
half,
in_profit: cfg.import("supply_in_profit", v0)?,
in_loss: cfg.import("supply_in_loss", v0)?,
})

View File

@@ -30,7 +30,7 @@ impl UnrealizedBasic {
let loss: FiatPerBlock<Cents> = cfg.import("unrealized_loss", v1)?;
let neg_loss = LazyPerBlock::from_computed::<NegCentsUnsignedToDollars>(
&cfg.name("neg_unrealized_loss"),
&cfg.name("unrealized_loss_neg"),
cfg.version,
loss.cents.height.read_only_boxed_clone(),
&loss.cents,

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -71,35 +71,6 @@ function volumeFolderWithProfitability(activity, color, title) {
};
}
/**
* Full activity items: volume (with profitability), coindays, dormancy
* @param {FullActivityPattern} activity
* @param {Color} color
* @param {(name: string) => string} title
* @returns {PartialOptionsTree}
*/
function fullVolumeTree(activity, color, title) {
return [
volumeFolderWithProfitability(activity, color, title),
{
name: "Coindays Destroyed",
tree: chartsFromCount({
pattern: activity.coindaysDestroyed,
title: title("Coindays Destroyed"),
unit: Unit.coindays,
color,
}),
},
{
name: "Dormancy",
tree: averagesArray({
windows: activity.dormancy,
title: title("Dormancy"),
unit: Unit.days,
}),
},
];
}
// ============================================================================
// Shared SOPR Helpers
@@ -180,64 +151,46 @@ function singleSellSideRiskTree(sellSideRisk, title) {
// ============================================================================
/**
* Full activity with adjusted SOPR (All/STH)
* @param {{ cohort: CohortAll | CohortFull, title: (name: string) => string }} args
* @returns {PartialOptionsGroup}
* Single activity tree items shared between WithAdjusted and basic
* @param {CohortAll | CohortFull | CohortLongTerm} cohort
* @param {(name: string) => string} title
* @param {PartialOptionsGroup} soprFolder
* @returns {PartialOptionsTree}
*/
export function createActivitySectionWithAdjusted({ cohort, title }) {
function singleFullActivityTree(cohort, title, soprFolder) {
const { tree, color } = cohort;
const r = tree.realized;
const sopr = r.sopr;
return [
volumeFolderWithProfitability(tree.activity, color, title),
soprFolder,
{ name: "Coindays Destroyed", tree: chartsFromCount({ pattern: tree.activity.coindaysDestroyed, title: title("Coindays Destroyed"), unit: Unit.coindays, color }) },
{ name: "Dormancy", tree: averagesArray({ windows: tree.activity.dormancy, title: title("Dormancy"), unit: Unit.days }) },
{ name: "Sell Side Risk", tree: singleSellSideRiskTree(tree.realized.sellSideRiskRatio, title) },
];
}
/** @param {{ cohort: CohortAll | CohortFull, title: (name: string) => string }} args */
export function createActivitySectionWithAdjusted({ cohort, title }) {
const sopr = cohort.tree.realized.sopr;
return {
name: "Activity",
tree: [
...fullVolumeTree(tree.activity, color, title),
{
name: "SOPR",
tree: [
...singleRollingSoprTree(sopr.ratio, title),
{
name: "Adjusted",
tree: singleRollingSoprTree(
sopr.adjusted.ratio,
title,
"Adjusted ",
),
},
],
},
{
name: "Sell Side Risk",
tree: singleSellSideRiskTree(r.sellSideRiskRatio, title),
},
],
tree: singleFullActivityTree(cohort, title, {
name: "SOPR",
tree: [
...singleRollingSoprTree(sopr.ratio, title),
{ name: "Adjusted", tree: singleRollingSoprTree(sopr.adjusted.ratio, title, "Adjusted ") },
],
}),
};
}
/**
* Activity section for cohorts with rolling SOPR + sell side risk (LTH, also CohortFull | CohortLongTerm)
* @param {{ cohort: CohortFull | CohortLongTerm, title: (name: string) => string }} args
* @returns {PartialOptionsGroup}
*/
/** @param {{ cohort: CohortFull | CohortLongTerm, title: (name: string) => string }} args */
export function createActivitySection({ cohort, title }) {
const { tree, color } = cohort;
const r = tree.realized;
const sopr = r.sopr;
return {
name: "Activity",
tree: [
...fullVolumeTree(tree.activity, color, title),
{
name: "SOPR",
tree: singleRollingSoprTree(sopr.ratio, title),
},
{
name: "Sell Side Risk",
tree: singleSellSideRiskTree(r.sellSideRiskRatio, title),
},
],
tree: singleFullActivityTree(cohort, title, {
name: "SOPR",
tree: singleRollingSoprTree(cohort.tree.realized.sopr.ratio, title),
}),
};
}
@@ -316,6 +269,27 @@ export function createGroupedActivitySectionMinimal({ list, all, title }) {
};
}
/**
* Grouped volume folder with In Profit/Loss subfolders
* @template {{ name: string, color: Color }} T
* @template {{ name: string, color: Color }} A
* @param {readonly T[]} list
* @param {A} all
* @param {(name: string) => string} title
* @param {(c: T | A) => { sum: Record<string, AnyValuePattern>, cumulative: AnyValuePattern, inProfit: { sum: Record<string, AnyValuePattern>, cumulative: AnyValuePattern }, inLoss: { sum: Record<string, AnyValuePattern>, cumulative: AnyValuePattern } }} getTransferVolume
* @returns {PartialOptionsGroup}
*/
function groupedVolumeFolder(list, all, title, getTransferVolume) {
return {
name: "Volume",
tree: [
...groupedWindowsCumulativeSatsBtcUsd({ list, all, title, metricTitle: "Sent Volume", getMetric: (c) => getTransferVolume(c) }),
{ name: "In Profit", tree: groupedWindowsCumulativeSatsBtcUsd({ list, all, title, metricTitle: "Sent In Profit", getMetric: (c) => getTransferVolume(c).inProfit }) },
{ name: "In Loss", tree: groupedWindowsCumulativeSatsBtcUsd({ list, all, title, metricTitle: "Sent In Loss", getMetric: (c) => getTransferVolume(c).inLoss }) },
],
};
}
// ============================================================================
// Grouped SOPR Helpers
// ============================================================================
@@ -366,214 +340,88 @@ function groupedSoprCharts(list, all, getRatio, title, prefix = "") {
// ============================================================================
/**
* @param {{ list: readonly CohortFull[], all: CohortAll, title: (name: string) => string }} args
* @returns {PartialOptionsGroup}
* Grouped activity tree items shared between WithAdjusted and basic
* @param {readonly (CohortFull | CohortLongTerm)[]} list
* @param {CohortAll} all
* @param {(name: string) => string} title
* @param {PartialOptionsGroup} soprFolder
* @returns {PartialOptionsTree}
*/
function groupedFullActivityTree(list, all, title, soprFolder) {
return [
groupedVolumeFolder(list, all, title, (c) => c.tree.activity.transferVolume),
soprFolder,
...groupedActivitySharedItems(list, all, title),
];
}
/** @param {{ list: readonly CohortFull[], all: CohortAll, title: (name: string) => string }} args */
export function createGroupedActivitySectionWithAdjusted({ list, all, title }) {
return {
name: "Activity",
tree: [
{
name: "Volume",
tree: [
...groupedWindowsCumulativeSatsBtcUsd({
list,
all,
title,
metricTitle: "Sent Volume",
getMetric: (c) => c.tree.activity.transferVolume,
}),
{
name: "In Profit",
tree: groupedWindowsCumulativeSatsBtcUsd({
list,
all,
title,
metricTitle: "Sent In Profit",
getMetric: (c) => c.tree.activity.transferVolume.inProfit,
}),
},
{
name: "In Loss",
tree: groupedWindowsCumulativeSatsBtcUsd({
list,
all,
title,
metricTitle: "Sent In Loss",
getMetric: (c) => c.tree.activity.transferVolume.inLoss,
}),
},
],
},
{
name: "Coindays Destroyed",
tree: groupedWindowsCumulative({
list,
all,
title,
metricTitle: "Coindays Destroyed",
getWindowSeries: (c, key) =>
c.tree.activity.coindaysDestroyed.sum[key],
getCumulativeSeries: (c) =>
c.tree.activity.coindaysDestroyed.cumulative,
seriesFn: line,
unit: Unit.coindays,
}),
},
{
name: "Dormancy",
tree: ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`Dormancy (${w.title})`),
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
line({
series: tree.activity.dormancy[w.key],
name,
color,
unit: Unit.days,
}),
),
})),
},
{
name: "SOPR",
tree: [
...groupedSoprCharts(
list,
all,
(c) => c.tree.realized.sopr.ratio,
title,
),
{
name: "Adjusted",
tree: groupedSoprCharts(
list,
all,
(c) => c.tree.realized.sopr.adjusted.ratio,
title,
"Adjusted ",
),
},
],
},
{
name: "Sell Side Risk",
tree: ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`Sell Side Risk (${w.title})`),
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
line({
series: tree.realized.sellSideRiskRatio[w.key].ratio,
name,
color,
unit: Unit.ratio,
}),
),
})),
},
],
tree: groupedFullActivityTree(list, all, title, {
name: "SOPR",
tree: [
...groupedSoprCharts(list, all, (c) => c.tree.realized.sopr.ratio, title),
{ name: "Adjusted", tree: groupedSoprCharts(list, all, (c) => c.tree.realized.sopr.adjusted.ratio, title, "Adjusted ") },
],
}),
};
}
/** @param {{ list: readonly (CohortFull | CohortLongTerm)[], all: CohortAll, title: (name: string) => string }} args */
export function createGroupedActivitySection({ list, all, title }) {
return {
name: "Activity",
tree: groupedFullActivityTree(list, all, title, {
name: "SOPR",
tree: groupedSoprCharts(list, all, (c) => c.tree.realized.sopr.ratio, title),
}),
};
}
/**
* Grouped activity for cohorts with rolling SOPR + sell side risk (LTH-like)
* @param {{ list: readonly (CohortFull | CohortLongTerm)[], all: CohortAll, title: (name: string) => string }} args
* @returns {PartialOptionsGroup}
* Shared grouped activity items: coindays, dormancy, sell side risk
* @param {readonly (CohortFull | CohortLongTerm)[]} list
* @param {CohortAll} all
* @param {(name: string) => string} title
* @returns {PartialOptionsTree}
*/
export function createGroupedActivitySection({ list, all, title }) {
return {
name: "Activity",
tree: [
{
name: "Volume",
tree: [
...groupedWindowsCumulativeSatsBtcUsd({
list,
all,
title,
metricTitle: "Sent Volume",
getMetric: (c) => c.tree.activity.transferVolume,
}),
{
name: "In Profit",
tree: groupedWindowsCumulativeSatsBtcUsd({
list,
all,
title,
metricTitle: "Sent In Profit",
getMetric: (c) => c.tree.activity.transferVolume.inProfit,
}),
},
{
name: "In Loss",
tree: groupedWindowsCumulativeSatsBtcUsd({
list,
all,
title,
metricTitle: "Sent In Loss",
getMetric: (c) => c.tree.activity.transferVolume.inLoss,
}),
},
],
},
{
name: "Coindays Destroyed",
tree: groupedWindowsCumulative({
list,
all,
title,
metricTitle: "Coindays Destroyed",
getWindowSeries: (c, key) =>
c.tree.activity.coindaysDestroyed.sum[key],
getCumulativeSeries: (c) =>
c.tree.activity.coindaysDestroyed.cumulative,
seriesFn: line,
unit: Unit.coindays,
}),
},
{
name: "Dormancy",
tree: ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`Dormancy (${w.title})`),
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
line({
series: tree.activity.dormancy[w.key],
name,
color,
unit: Unit.days,
}),
),
})),
},
{
name: "SOPR",
tree: groupedSoprCharts(
list,
all,
(c) => c.tree.realized.sopr.ratio,
title,
function groupedActivitySharedItems(list, all, title) {
return [
{
name: "Coindays Destroyed",
tree: groupedWindowsCumulative({
list, all, title, metricTitle: "Coindays Destroyed",
getWindowSeries: (c, key) => c.tree.activity.coindaysDestroyed.sum[key],
getCumulativeSeries: (c) => c.tree.activity.coindaysDestroyed.cumulative,
seriesFn: line, unit: Unit.coindays,
}),
},
{
name: "Dormancy",
tree: ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`Dormancy (${w.title})`),
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
line({ series: tree.activity.dormancy[w.key], name, color, unit: Unit.days }),
),
},
{
name: "Sell Side Risk",
tree: ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`Sell Side Risk (${w.title})`),
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
line({
series: tree.realized.sellSideRiskRatio[w.key].ratio,
name,
color,
unit: Unit.ratio,
}),
),
})),
},
],
};
})),
},
{
name: "Sell Side Risk",
tree: ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`Sell Side Risk (${w.title})`),
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
line({ series: tree.realized.sellSideRiskRatio[w.key].ratio, name, color, unit: Unit.ratio }),
),
})),
},
];
}
/**
* Grouped activity for cohorts with activity but basic realized (AgeRange/MaxAge)
* @param {{ list: readonly (CohortAgeRange | CohortWithAdjusted)[], all: CohortAll, title: (name: string) => string }} args

View File

@@ -16,6 +16,7 @@ import {
baseline,
sumsTreeBaseline,
rollingPercentRatioTree,
percentRatio,
percentRatioBaseline,
} from "../series.js";
import {
@@ -29,7 +30,7 @@ import { priceLines } from "../constants.js";
/**
* Simple supply series (total + half only, no profit/loss)
* @param {{ total: AnyValuePattern, half: AnyValuePattern }} supply
* @param {{ total: AnyValuePattern }} supply
* @returns {AnyFetchedSeriesBlueprint[]}
*/
function simpleSupplySeries(supply) {
@@ -39,68 +40,6 @@ function simpleSupplySeries(supply) {
});
}
/**
* % of Own Supply series (profit/loss relative to own supply)
* @param {{ inProfit: { toOwn: { percent: AnySeriesPattern, ratio: AnySeriesPattern } }, inLoss: { toOwn: { percent: AnySeriesPattern, ratio: AnySeriesPattern } } }} supply
* @returns {AnyFetchedSeriesBlueprint[]}
*/
function ownSupplyPctSeries(supply) {
return [
line({
series: supply.inProfit.toOwn.percent,
name: "In Profit",
color: colors.profit,
unit: Unit.percentage,
}),
line({
series: supply.inLoss.toOwn.percent,
name: "In Loss",
color: colors.loss,
unit: Unit.percentage,
}),
line({
series: supply.inProfit.toOwn.ratio,
name: "In Profit",
color: colors.profit,
unit: Unit.ratio,
}),
line({
series: supply.inLoss.toOwn.ratio,
name: "In Loss",
color: colors.loss,
unit: Unit.ratio,
}),
...priceLines({ numbers: [100, 50, 0], unit: Unit.percentage }),
];
}
/**
* % of Circulating Supply series (total, profit, loss)
* @param {{ toCirculating: { percent: AnySeriesPattern }, inProfit: { toCirculating: { percent: AnySeriesPattern } }, inLoss: { toCirculating: { percent: AnySeriesPattern } } }} supply
* @returns {AnyFetchedSeriesBlueprint[]}
*/
function circulatingSupplyPctSeries(supply) {
return [
line({
series: supply.toCirculating.percent,
name: "Total",
color: colors.default,
unit: Unit.percentage,
}),
line({
series: supply.inProfit.toCirculating.percent,
name: "In Profit",
color: colors.profit,
unit: Unit.percentage,
}),
line({
series: supply.inLoss.toCirculating.percent,
name: "In Loss",
color: colors.loss,
unit: Unit.percentage,
}),
];
}
/**
* @param {readonly (UtxoCohortObject | CohortWithoutRelative)[]} list
@@ -244,7 +183,11 @@ function circulatingChart(supply, title) {
return {
name: "% of Circulating",
title: title("Supply (% of Circulating)"),
bottom: circulatingSupplyPctSeries(supply),
bottom: [
line({ series: supply.toCirculating.percent, name: "Total", color: colors.default, unit: Unit.percentage }),
line({ series: supply.inProfit.toCirculating.percent, name: "In Profit", color: colors.profit, unit: Unit.percentage }),
line({ series: supply.inLoss.toCirculating.percent, name: "In Loss", color: colors.loss, unit: Unit.percentage }),
],
};
}
@@ -257,7 +200,11 @@ function ownSupplyChart(supply, title) {
return {
name: "% of Own Supply",
title: title("Supply (% of Own)"),
bottom: ownSupplyPctSeries(supply),
bottom: [
...percentRatio({ pattern: supply.inProfit.toOwn, name: "In Profit", color: colors.profit }),
...percentRatio({ pattern: supply.inLoss.toOwn, name: "In Loss", color: colors.loss }),
...priceLines({ numbers: [100, 50, 0], unit: Unit.percentage }),
],
};
}
@@ -460,6 +407,35 @@ export function createHoldingsSectionAddressAmount({ cohort, title }) {
];
}
// ============================================================================
// Grouped Cohort Supply Helpers
// ============================================================================
/**
* @template {{ name: string, color: Color, tree: { supply: { total: AnyValuePattern } } }} T
* @param {readonly T[]} list
* @param {CohortAll} all
* @param {(name: string) => string} title
* @returns {PartialChartOption}
*/
function groupedSupplyTotal(list, all, title) {
return { name: "Total", title: title("Supply"), bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => satsBtcUsd({ pattern: tree.supply.total, name, color })) };
}
/**
* @template {{ name: string, color: Color, tree: { supply: { inProfit: AnyValuePattern, inLoss: AnyValuePattern } } }} T
* @param {readonly T[]} list
* @param {CohortAll} all
* @param {(name: string) => string} title
* @returns {PartialOptionsTree}
*/
function groupedSupplyProfitLoss(list, all, title) {
return [
{ name: "In Profit", title: title("Supply In Profit"), bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => satsBtcUsd({ pattern: tree.supply.inProfit, name, color })) },
{ name: "In Loss", title: title("Supply In Loss"), bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => satsBtcUsd({ pattern: tree.supply.inLoss, name, color })) },
];
}
// ============================================================================
// Grouped Cohort Holdings Sections
// ============================================================================
@@ -473,27 +449,8 @@ export function createGroupedHoldingsSectionAddress({ list, all, title }) {
{
name: "Supply",
tree: [
{
name: "Total",
title: title("Supply"),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.supply.total, name, color }),
),
},
{
name: "In Profit",
title: title("Supply In Profit"),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.supply.inProfit, name, color }),
),
},
{
name: "In Loss",
title: title("Supply In Loss"),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.supply.inLoss, name, color }),
),
},
groupedSupplyTotal(list, all, title),
...groupedSupplyProfitLoss(list, all, title),
...groupedDeltaItems(list, all, (c) => c.tree.supply.delta, Unit.sats, title, "Supply"),
],
},
@@ -524,7 +481,7 @@ export function createGroupedHoldingsSectionAddressAmount({ list, all, title })
{
name: "Supply",
tree: [
{ name: "Total", title: title("Supply"), bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => satsBtcUsd({ pattern: tree.supply.total, name, color })) },
groupedSupplyTotal(list, all, title),
...groupedDeltaItems(list, all, (c) => c.tree.supply.delta, Unit.sats, title, "Supply"),
],
},
@@ -551,7 +508,7 @@ export function createGroupedHoldingsSection({ list, all, title }) {
{
name: "Supply",
tree: [
{ name: "Total", title: title("Supply"), bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => satsBtcUsd({ pattern: tree.supply.total, name, color })) },
groupedSupplyTotal(list, all, title),
...groupedDeltaItems(list, all, (c) => c.tree.supply.delta, Unit.sats, title, "Supply"),
],
},
@@ -565,9 +522,8 @@ export function createGroupedHoldingsSectionWithProfitLoss({ list, all, title })
{
name: "Supply",
tree: [
{ name: "Total", title: title("Supply"), bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => satsBtcUsd({ pattern: tree.supply.total, name, color })) },
{ name: "In Profit", title: title("Supply In Profit"), bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => satsBtcUsd({ pattern: tree.supply.inProfit, name, color })) },
{ name: "In Loss", title: title("Supply In Loss"), bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => satsBtcUsd({ pattern: tree.supply.inLoss, name, color })) },
groupedSupplyTotal(list, all, title),
...groupedSupplyProfitLoss(list, all, title),
...groupedDeltaItems(list, all, (c) => c.tree.supply.delta, Unit.sats, title, "Supply"),
],
},
@@ -581,9 +537,8 @@ export function createGroupedHoldingsSectionWithOwnSupply({ list, all, title })
{
name: "Supply",
tree: [
{ name: "Total", title: title("Supply"), bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => satsBtcUsd({ pattern: tree.supply.total, name, color })) },
{ name: "In Profit", title: title("Supply In Profit"), bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => satsBtcUsd({ pattern: tree.supply.inProfit, name, color })) },
{ name: "In Loss", title: title("Supply In Loss"), bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => satsBtcUsd({ pattern: tree.supply.inLoss, name, color })) },
groupedSupplyTotal(list, all, title),
...groupedSupplyProfitLoss(list, all, title),
{ name: "% of Circulating", title: title("Supply (% of Circulating)"), bottom: mapCohorts(list, ({ name, color, tree }) => line({ series: tree.supply.toCirculating.percent, name, color, unit: Unit.percentage })) },
...groupedDeltaItems(list, all, (c) => c.tree.supply.delta, Unit.sats, title, "Supply"),
],
@@ -603,9 +558,8 @@ export function createGroupedHoldingsSectionWithRelative({ list, all, title }) {
{
name: "Supply",
tree: [
{ name: "Total", title: title("Supply"), bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => satsBtcUsd({ pattern: tree.supply.total, name, color })) },
{ name: "In Profit", title: title("Supply In Profit"), bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => satsBtcUsd({ pattern: tree.supply.inProfit, name, color })) },
{ name: "In Loss", title: title("Supply In Loss"), bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) => satsBtcUsd({ pattern: tree.supply.inLoss, name, color })) },
groupedSupplyTotal(list, all, title),
...groupedSupplyProfitLoss(list, all, title),
{ name: "% of Circulating", title: title("Supply (% of Circulating)"), bottom: mapCohorts(list, ({ name, color, tree }) => line({ series: tree.supply.toCirculating.percent, name, color, unit: Unit.percentage })) },
{ name: "% of Own Supply", title: title("Supply (% of Own)"), bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) => line({ series: tree.supply.inProfit.toOwn.percent, name, color, unit: Unit.percentage })) },
...groupedDeltaItems(list, all, (c) => c.tree.supply.delta, Unit.sats, title, "Supply"),

View File

@@ -36,7 +36,6 @@ import {
createHoldingsSectionWithRelative,
createHoldingsSectionWithOwnSupply,
createGroupedHoldingsSection,
createGroupedHoldingsSectionWithProfitLoss,
createGroupedHoldingsSectionAddress,
createGroupedHoldingsSectionAddressAmount,
createGroupedHoldingsSectionWithRelative,
@@ -68,18 +67,15 @@ import {
createProfitabilitySectionWithProfitLoss,
createProfitabilitySectionLongTerm,
createGroupedProfitabilitySection,
createGroupedProfitabilitySectionWithProfitLoss,
createGroupedProfitabilitySectionWithNupl,
createGroupedProfitabilitySectionWithInvestedCapitalPct,
createGroupedProfitabilitySectionBasicWithInvestedCapitalPct,
createGroupedProfitabilitySectionLongTerm,
} from "./profitability.js";
import {
createActivitySection,
createActivitySectionWithAdjusted,
createActivitySectionWithActivity,
createGroupedActivitySection,
createGroupedActivitySectionWithAdjusted,
createGroupedActivitySectionWithActivity,
createActivitySectionMinimal,
createGroupedActivitySectionMinimal,
@@ -327,30 +323,6 @@ export function createAddressCohortFolder(cohort) {
// Grouped Cohort Folder Builders
// ============================================================================
/**
* @param {CohortGroupFull} args
* @returns {PartialOptionsGroup}
*/
export function createGroupedCohortFolderFull({
name,
title: groupTitle,
list,
all,
}) {
const title = formatCohortTitle(groupTitle);
return {
name: name || "all",
tree: [
...createGroupedHoldingsSectionWithRelative({ list, all, title }),
createGroupedValuationSectionWithOwnMarketCap({ list, all, title }),
createGroupedPricesSectionFull({ list, all, title }),
createGroupedCostBasisSectionWithPercentiles({ list, all, title }),
createGroupedProfitabilitySectionWithNupl({ list, all, title }),
createGroupedActivitySectionWithAdjusted({ list, all, title }),
],
};
}
/**
* @param {CohortGroupWithAdjusted} args
* @returns {PartialOptionsGroup}
@@ -402,30 +374,6 @@ export function createGroupedCohortFolderWithNupl({
};
}
/**
* @param {CohortGroupLongTerm} args
* @returns {PartialOptionsGroup}
*/
export function createGroupedCohortFolderLongTerm({
name,
title: groupTitle,
list,
all,
}) {
const title = formatCohortTitle(groupTitle);
return {
name: name || "all",
tree: [
...createGroupedHoldingsSectionWithRelative({ list, all, title }),
createGroupedValuationSectionWithOwnMarketCap({ list, all, title }),
createGroupedPricesSectionFull({ list, all, title }),
createGroupedCostBasisSectionWithPercentiles({ list, all, title }),
createGroupedProfitabilitySectionLongTerm({ list, all, title }),
createGroupedActivitySection({ list, all, title }),
],
};
}
/**
* @param {CohortGroupAgeRange} args
* @returns {PartialOptionsGroup}
@@ -564,29 +512,6 @@ export function createGroupedCohortFolderAddress({
};
}
/**
* @param {CohortGroupWithoutRelative} args
* @returns {PartialOptionsGroup}
*/
export function createGroupedCohortFolderWithoutRelative({
name,
title: groupTitle,
list,
all,
}) {
const title = formatCohortTitle(groupTitle);
return {
name: name || "all",
tree: [
...createGroupedHoldingsSectionWithProfitLoss({ list, all, title }),
createGroupedValuationSection({ list, all, title }),
createGroupedPricesSection({ list, all, title }),
createGroupedProfitabilitySectionWithProfitLoss({ list, all, title }),
createGroupedActivitySectionMinimal({ list, all, title }),
],
};
}
/**
* @param {AddrCohortGroupObject} args
* @returns {PartialOptionsGroup}

View File

@@ -26,7 +26,6 @@ import { colors } from "../../utils/colors.js";
import { priceLine } from "../constants.js";
import {
mapCohortsWithAll,
flatMapCohorts,
flatMapCohortsWithAll,
groupedWindowsCumulativeUsd,
} from "../shared.js";
@@ -35,14 +34,6 @@ import {
// Core Series Builders
// ============================================================================
/**
* @param {AnySeriesPattern} s
* @param {Unit} unit
* @returns {AnyFetchedSeriesBlueprint}
*/
function netBaseline(s, unit) {
return baseline({ series: s, name: "Net", unit });
}
// ============================================================================
// Unrealized P&L Builders
@@ -110,18 +101,18 @@ function relPnlChart(profit, loss, name, title) {
}
/**
* Shared unrealized base items
* @param {AllRelativePattern | FullRelativePattern} u
* Core unrealized items: Overview + Net + NUPL + Profit + Loss
* @param {{ profit: { usd: AnySeriesPattern }, loss: { usd: AnySeriesPattern, negative: AnySeriesPattern }, netPnl: { usd: AnySeriesPattern }, nupl: NuplPattern }} u
* @param {(name: string) => string} title
* @returns {PartialOptionsTree}
*/
function unrealizedBase(u, title) {
function unrealizedCore(u, title) {
return [
unrealizedOverview(u.profit, u.loss, u.netPnl.usd, title),
{
name: "Net",
title: title("Net Unrealized P&L"),
bottom: [netBaseline(u.netPnl.usd, Unit.usd)],
bottom: [baseline({ series: u.netPnl.usd, name: "Net", unit: Unit.usd })],
},
{ name: "NUPL", title: title("NUPL"), bottom: nuplSeries(u.nupl) },
{
@@ -148,35 +139,42 @@ function unrealizedBase(u, title) {
}),
],
},
{
name: "% of Own P&L",
title: title("Unrealized P&L (% of Own P&L)"),
bottom: [
...percentRatioBaseline({
pattern: u.netPnl.toOwnGrossPnl,
name: "Net",
}),
...percentRatio({
pattern: u.profit.toOwnGrossPnl,
name: "Profit",
color: colors.profit,
defaultActive: false,
}),
...percentRatio({
pattern: u.loss.toOwnGrossPnl,
name: "Loss",
color: colors.loss,
defaultActive: false,
}),
],
},
];
}
/**
* % of Own P&L chart
* @param {AllRelativePattern | FullRelativePattern} u
* @param {(name: string) => string} title
* @returns {PartialChartOption}
*/
function ownPnlChart(u, title) {
return {
name: "% of Own P&L",
title: title("Unrealized P&L (% of Own P&L)"),
bottom: [
...percentRatioBaseline({ pattern: u.netPnl.toOwnGrossPnl, name: "Net" }),
...percentRatio({
pattern: u.profit.toOwnGrossPnl,
name: "Profit",
color: colors.profit,
defaultActive: false,
}),
...percentRatio({
pattern: u.loss.toOwnGrossPnl,
name: "Loss",
color: colors.loss,
defaultActive: false,
}),
],
};
}
/** @param {AllRelativePattern} u @param {(name: string) => string} title */
function unrealizedTreeAll(u, title) {
return [
...unrealizedBase(u, title),
...unrealizedCore(u, title),
ownPnlChart(u, title),
relPnlChart(u.profit.toMcap, u.loss.toMcap, "% of Market Cap", title),
];
}
@@ -184,7 +182,8 @@ function unrealizedTreeAll(u, title) {
/** @param {FullRelativePattern} u @param {(name: string) => string} title */
function unrealizedTreeFull(u, title) {
return [
...unrealizedBase(u, title),
...unrealizedCore(u, title),
ownPnlChart(u, title),
relPnlChart(u.profit.toMcap, u.loss.toMcap, "% of Market Cap", title),
relPnlChart(
u.profit.toOwnMcap,
@@ -198,7 +197,8 @@ function unrealizedTreeFull(u, title) {
/** @param {FullRelativePattern} u @param {(name: string) => string} title */
function unrealizedTreeLongTerm(u, title) {
return [
...unrealizedBase(u, title),
...unrealizedCore(u, title),
ownPnlChart(u, title),
{
name: "% of Market Cap",
title: title("Unrealized Loss (% of Market Cap)"),
@@ -217,48 +217,6 @@ function unrealizedTreeLongTerm(u, title) {
];
}
/**
* Unrealized tree for mid-tier cohorts (AgeRange/MaxAge — profit/loss/net, no relative)
* @param {BasicRelativePattern} u
* @param {(name: string) => string} title
* @returns {PartialOptionsTree}
*/
function unrealizedTreeMid(u, title) {
return [
unrealizedOverview(u.profit, u.loss, u.netPnl.usd, title),
{
name: "Net",
title: title("Net Unrealized P&L"),
bottom: [netBaseline(u.netPnl.usd, Unit.usd)],
},
{ name: "NUPL", title: title("NUPL"), bottom: nuplSeries(u.nupl) },
{
name: "Profit",
title: title("Unrealized Profit"),
bottom: [
line({
series: u.profit.usd,
name: "Profit",
color: colors.profit,
unit: Unit.usd,
}),
],
},
{
name: "Loss",
title: title("Unrealized Loss"),
bottom: [
line({
series: u.loss.usd,
name: "Loss",
color: colors.loss,
unit: Unit.usd,
}),
],
},
];
}
// ============================================================================
// Invested Capital, Sentiment, NUPL
// ============================================================================
@@ -401,11 +359,21 @@ function realizedNetFolder({ netPnl, title, extraChange = [] }) {
}),
],
},
{ ...sumsTreeBaseline({ windows: mapWindows(netPnl.delta.absolute, (c) => c.usd), title: title("Net Realized P&L Change"), unit: Unit.usd }), name: "Change" },
{
...sumsTreeBaseline({
windows: mapWindows(netPnl.delta.absolute, (c) => c.usd),
title: title("Net Realized P&L Change"),
unit: Unit.usd,
}),
name: "Change",
},
{
name: "Growth Rate",
tree: [
...rollingPercentRatioTree({ windows: netPnl.delta.rate, title: title("Net Realized P&L Rate") }).tree,
...rollingPercentRatioTree({
windows: netPnl.delta.rate,
title: title("Net Realized P&L Rate"),
}).tree,
...extraChange,
],
},
@@ -885,7 +853,7 @@ export function createProfitabilitySectionWithInvestedCapitalPct({
return {
name: "Profitability",
tree: [
{ name: "Unrealized", tree: unrealizedTreeMid(u, title) },
{ name: "Unrealized", tree: unrealizedCore(u, title) },
realizedSubfolderMid(r, title),
],
};
@@ -981,10 +949,26 @@ function groupedRealizedSubfolder(list, all, title) {
return {
name: "Realized",
tree: [
{ name: "Profit", tree: groupedWindowsCumulativeUsd({ list, all, title, metricTitle: "Realized Profit",
getMetric: (c) => c.tree.realized.profit }) },
{ name: "Loss", tree: groupedWindowsCumulativeUsd({ list, all, title, metricTitle: "Realized Loss",
getMetric: (c) => c.tree.realized.loss }) },
{
name: "Profit",
tree: groupedWindowsCumulativeUsd({
list,
all,
title,
metricTitle: "Realized Profit",
getMetric: (c) => c.tree.realized.profit,
}),
},
{
name: "Loss",
tree: groupedWindowsCumulativeUsd({
list,
all,
title,
metricTitle: "Realized Loss",
getMetric: (c) => c.tree.realized.loss,
}),
},
],
};
}
@@ -1004,7 +988,12 @@ function groupedRealizedNetPnlDeltaItems(list, all, title) {
name: w.name,
title: title(`Net Realized P&L Change (${w.title})`),
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
baseline({ series: tree.realized.netPnl.delta.absolute[w.key].usd, name, color, unit: Unit.usd }),
baseline({
series: tree.realized.netPnl.delta.absolute[w.key].usd,
name,
color,
unit: Unit.usd,
}),
),
})),
},
@@ -1014,7 +1003,11 @@ function groupedRealizedNetPnlDeltaItems(list, all, title) {
name: w.name,
title: title(`Net Realized P&L Rate (${w.title})`),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
percentRatioBaseline({ pattern: tree.realized.netPnl.delta.rate[w.key], name, color }),
percentRatioBaseline({
pattern: tree.realized.netPnl.delta.rate[w.key],
name,
color,
}),
),
})),
},
@@ -1212,116 +1205,6 @@ function groupedUnrealizedWithMarketCap(list, all, title) {
];
}
/**
* Grouped unrealized for LongTerm: Net → NUPL → Profit → Loss → Relative(Own P&L, Market Cap, Own Mcap)
* @param {readonly CohortLongTerm[]} list
* @param {CohortAll} all
* @param {(name: string) => string} title
* @returns {PartialOptionsTree}
*/
function groupedUnrealizedLongTerm(list, all, title) {
return [
...groupedUnrealizedMid(list, all, title),
{
name: "Relative",
tree: [
{
name: "Own P&L",
tree: [
{
name: "Net",
title: title("Net Unrealized P&L (% of Own P&L)"),
bottom: flatMapCohortsWithAll(
list,
all,
({ name, color, tree }) =>
percentRatioBaseline({
pattern: tree.unrealized.netPnl.toOwnGrossPnl,
name,
color,
}),
),
},
{
name: "Profit",
title: title("Unrealized Profit (% of Own P&L)"),
bottom: flatMapCohortsWithAll(
list,
all,
({ name, color, tree }) =>
percentRatio({
pattern: tree.unrealized.profit.toOwnGrossPnl,
name,
color,
}),
),
},
{
name: "Loss",
title: title("Unrealized Loss (% of Own P&L)"),
bottom: flatMapCohortsWithAll(
list,
all,
({ name, color, tree }) =>
percentRatio({
pattern: tree.unrealized.loss.toOwnGrossPnl,
name,
color,
}),
),
},
],
},
{
name: "Market Cap",
title: title("Unrealized Loss (% of Market Cap)"),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
percentRatio({ pattern: tree.unrealized.loss.toMcap, name, color }),
),
},
{
name: "Own Market Cap",
tree: [
{
name: "Net",
title: title("Net Unrealized P&L (% of Own Market Cap)"),
bottom: flatMapCohorts(list, ({ name, color, tree }) =>
percentRatioBaseline({
pattern: tree.unrealized.netPnl.toOwnMcap,
name,
color,
}),
),
},
{
name: "Profit",
title: title("Unrealized Profit (% of Own Market Cap)"),
bottom: flatMapCohorts(list, ({ name, color, tree }) =>
percentRatio({
pattern: tree.unrealized.profit.toOwnMcap,
name,
color,
}),
),
},
{
name: "Loss",
title: title("Unrealized Loss (% of Own Market Cap)"),
bottom: flatMapCohorts(list, ({ name, color, tree }) =>
percentRatio({
pattern: tree.unrealized.loss.toOwnMcap,
name,
color,
}),
),
},
],
},
],
},
];
}
/**
* Grouped sentiment (full unrealized only)
* @param {readonly (CohortAll | CohortFull | CohortLongTerm)[]} list
@@ -1501,23 +1384,3 @@ export function createGroupedProfitabilitySectionWithNupl({
],
};
}
/**
* Grouped section for LongTerm cohorts
* @param {{ list: readonly CohortLongTerm[], all: CohortAll, title: (name: string) => string }} args
* @returns {PartialOptionsGroup}
*/
export function createGroupedProfitabilitySectionLongTerm({
list,
all,
title,
}) {
return {
name: "Profitability",
tree: [
{ name: "Unrealized", tree: groupedUnrealizedLongTerm(list, all, title) },
groupedRealizedSubfolderFull(list, all, title),
groupedSentiment(list, all, title),
],
};
}

View File

@@ -67,24 +67,15 @@
* @typedef {Brk.AnySeriesEndpoint} AnySeriesEndpoint
* @typedef {Brk.AnySeriesData} AnySeriesData
* Relative patterns by capability:
* - BasicRelativePattern: minimal relative (investedCapitalIn*Pct, supplyIn*RelToOwnSupply only)
* - GlobalRelativePattern: has RelToMarketCap series (netUnrealizedPnlRelToMarketCap, etc)
* - OwnRelativePattern: has RelToOwnMarketCap series (netUnrealizedPnlRelToOwnMarketCap, etc)
* - FullRelativePattern: has BOTH RelToMarketCap AND RelToOwnMarketCap
* Unrealized patterns by capability level
* @typedef {Brk.LossNetNuplProfitPattern} BasicRelativePattern
* @typedef {Brk.LossNetNuplProfitPattern} GlobalRelativePattern
* @typedef {Brk.GrossInvestedInvestorLossNetNuplProfitSentimentPattern2} OwnRelativePattern
* @typedef {Brk.GrossInvestedInvestorLossNetNuplProfitSentimentPattern2} FullRelativePattern
* @typedef {Brk.GrossInvestedInvestorLossNetNuplProfitSentimentPattern2} UnrealizedPattern
*
* Profitability bucket pattern (supply + realized_cap + nupl)
* @typedef {Brk.NuplRealizedSupplyPattern} RealizedSupplyPattern
*
* Realized patterns
* Realized pattern (full: cap + gross + investor + loss + mvrv + net + peak + price + profit + sell + sopr)
* @typedef {Brk.CapGrossInvestorLossMvrvNetPeakPriceProfitSellSoprPattern} RealizedPattern
* @typedef {Brk.CapGrossInvestorLossMvrvNetPeakPriceProfitSellSoprPattern} RealizedPattern2
* @typedef {Brk.CapGrossInvestorLossMvrvNetPeakPriceProfitSellSoprPattern} RealizedPattern3
* @typedef {Brk.CapGrossInvestorLossMvrvNetPeakPriceProfitSellSoprPattern} RealizedPattern4
*
* Transfer volume pattern (block + cumulative + inProfit/inLoss + sum windows)
* @typedef {Brk.AverageBlockCumulativeInSumPattern} TransferVolumePattern
@@ -103,7 +94,7 @@
* @typedef {Brk.SeriesTree_Cohorts_Utxo_Lth_Realized} LthRealizedPattern
*
* Net PnL pattern with change (base + change + cumulative + delta + rel + sum)
* @typedef {Brk.BlockChangeCumulativeDeltaSumToPattern} NetPnlFullPattern
* @typedef {Brk.BlockChangeCumulativeDeltaSumPattern} NetPnlFullPattern
*
* Net PnL basic pattern (base + cumulative + delta + sum)
* @typedef {Brk.BlockCumulativeDeltaSumPattern} NetPnlBasicPattern
@@ -135,13 +126,9 @@
* @typedef {Brk._1m1w1y24hPattern7} SellSideRiskPattern
*/
/**
* Stats pattern: average, min, max, percentiles (height-only indexes, NO base)
* Stats pattern: min, max, median, percentiles
* @typedef {Brk.MaxMedianMinPct10Pct25Pct75Pct90Pattern<number>} StatsPattern
*/
/**
* Base stats pattern: average, min, max, percentiles
* @typedef {Brk.MaxMedianMinPct10Pct25Pct75Pct90Pattern<number>} BaseStatsPattern
*/
/**
* Full stats pattern: cumulative, sum, average, min, max, percentiles + rolling
* @typedef {Brk.AverageBlockCumulativeMaxMedianMinPct10Pct25Pct75Pct90SumPattern} FullStatsPattern
@@ -150,14 +137,6 @@
* Aggregated pattern: cumulative + rolling (with distribution stats) + sum (no base)
* @typedef {Brk.CumulativeRollingSumPattern} AggregatedPattern
*/
/**
* Sum stats pattern: cumulative, sum, average, min, max, percentiles + rolling (same as FullStatsPattern)
* @typedef {Brk.AverageBlockCumulativeMaxMedianMinPct10Pct25Pct75Pct90SumPattern} SumStatsPattern
*/
/**
* Full stats pattern for Bitcoin (non-generic variant) - same as FullStatsPattern
* @typedef {Brk.AverageBlockCumulativeMaxMedianMinPct10Pct25Pct75Pct90SumPattern} BtcFullStatsPattern
*/
/**
* Count pattern: height, cumulative, and rolling sum windows
* @template T
@@ -170,8 +149,8 @@
* @typedef {Omit<Brk.AverageBlockCumulativeMaxMedianMinPct10Pct25Pct75Pct90SumPattern, 'block'>} FullPerBlockPattern
*/
/**
* Any stats pattern union - patterns with sum/cumulative + percentiles
* @typedef {FullStatsPattern | BtcFullStatsPattern} AnyStatsPattern
* Any stats pattern union
* @typedef {FullStatsPattern} AnyStatsPattern
*/
/**
* Distribution stats: min, max, median, pct10/25/75/90
@@ -190,8 +169,7 @@
* @typedef {Brk.SeriesTree_Market_MovingAverage} MarketMovingAverage
* @typedef {Brk.SeriesTree_Market_Dca} MarketDca
* @typedef {Brk._10y2y3y4y5y6y8yPattern} PeriodCagrPattern
* Full stats pattern union (both generic and non-generic variants)
* @typedef {FullStatsPattern | BtcFullStatsPattern} AnyFullStatsPattern
* @typedef {FullStatsPattern} AnyFullStatsPattern
*
* DCA period keys - derived from pattern types
* @typedef {keyof Brk._10y2y3y4y5y6y8yPattern} LongPeriodKey
@@ -204,18 +182,14 @@
* @typedef {UtxoCohortPattern | AddrCohortPattern} CohortPattern
*
* Relative pattern capability types
* @typedef {GlobalRelativePattern | FullRelativePattern | AllRelativePattern} RelativeWithMarketCap
* @typedef {OwnRelativePattern | FullRelativePattern} RelativeWithOwnMarketCap
* @typedef {OwnRelativePattern | FullRelativePattern | AllRelativePattern} RelativeWithOwnPnl
* @typedef {GlobalRelativePattern | FullRelativePattern | AllRelativePattern} RelativeWithNupl
* @typedef {BasicRelativePattern | GlobalRelativePattern | OwnRelativePattern | FullRelativePattern | AllRelativePattern} RelativeWithInvestedCapitalPct
* @typedef {BasicRelativePattern | FullRelativePattern | AllRelativePattern} RelativeWithMarketCap
* @typedef {FullRelativePattern | AllRelativePattern} RelativeWithOwnMarketCap
* @typedef {FullRelativePattern | AllRelativePattern} RelativeWithOwnPnl
* @typedef {BasicRelativePattern | FullRelativePattern | AllRelativePattern} RelativeWithNupl
* @typedef {BasicRelativePattern | FullRelativePattern | AllRelativePattern} RelativeWithInvestedCapitalPct
*
* Realized pattern capability types
* RealizedWithExtras: patterns with realizedCapRelToOwnMarketCap + realizedProfitToLossRatio
* @typedef {RealizedPattern2 | RealizedPattern3} RealizedWithExtras
*
* Any realized pattern (all have sellSideRiskRatio, valueCreated, valueDestroyed, etc.)
* @typedef {RealizedPattern | RealizedPattern2 | RealizedPattern3 | RealizedPattern4} AnyRealizedPattern
* @typedef {RealizedPattern} AnyRealizedPattern
*
* Capability-based pattern groupings (patterns that have specific properties)
* @typedef {AllUtxoPattern | AgeRangePattern | UtxoAmountPattern} PatternWithRealizedPrice