global: snapshot part 10

This commit is contained in:
nym21
2026-03-21 11:25:37 +01:00
parent b807b50a64
commit 143aa90b18
4 changed files with 247 additions and 186 deletions

View File

@@ -31,21 +31,44 @@ import { colors } from "../../utils/colors.js";
// ============================================================================
/**
* @param {{ transferVolume: TransferVolumePattern, coindaysDestroyed: CountPattern<number> }} activity
* Volume folder with optional profitability (in profit + in loss per window)
* @param {{ transferVolume: TransferVolumePattern }} activity
* @param {Color} color
* @param {(name: string) => string} title
* @param {boolean} [withProfitability]
* @returns {PartialOptionsGroup}
*/
function volumeFolder(activity, color, title, withProfitability) {
const tv = activity.transferVolume;
return {
name: "Volume",
tree: [
...satsBtcUsdFullTree({ pattern: tv, title: title("Sent Volume"), color }),
...(withProfitability ? [{
name: "Profitability",
tree: ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`Sent Volume Profitability (${w.title})`),
bottom: [
...satsBtcUsd({ pattern: tv.inProfit.sum[w.key], name: "In Profit", color: colors.profit }),
...satsBtcUsd({ pattern: tv.inLoss.sum[w.key], name: "In Loss", color: colors.loss }),
],
})),
}] : []),
],
};
}
/**
* Full activity items: volume (with profitability), coindays, dormancy
* @param {FullActivityPattern} activity
* @param {Color} color
* @param {(name: string) => string} title
* @returns {PartialOptionsTree}
*/
function volumeAndCoinsTree(activity, color, title) {
function fullVolumeTree(activity, color, title) {
return [
{
name: "Volume",
tree: satsBtcUsdFullTree({
pattern: activity.transferVolume,
title: title("Sent Volume"),
color,
}),
},
volumeFolder(activity, color, title, true),
{
name: "Coindays Destroyed",
tree: chartsFromCount({
@@ -55,47 +78,6 @@ function volumeAndCoinsTree(activity, color, title) {
color,
}),
},
];
}
/**
* Sent in profit/loss breakdown tree (shared by full and mid-level activity)
* @param {TransferVolumePattern} sent
* @param {(name: string) => string} title
* @returns {PartialOptionsTree}
*/
function sentProfitLossTree(sent, title) {
return [
{
name: "Sent In Profit",
tree: satsBtcUsdFullTree({
pattern: sent.inProfit,
title: title("Sent Volume In Profit"),
color: colors.profit,
}),
},
{
name: "Sent In Loss",
tree: satsBtcUsdFullTree({
pattern: sent.inLoss,
title: title("Sent Volume In Loss"),
color: colors.loss,
}),
},
];
}
/**
* Volume and coins tree with dormancy, and sent in profit/loss (All/STH/LTH)
* @param {FullActivityPattern} activity
* @param {Color} color
* @param {(name: string) => string} title
* @returns {PartialOptionsTree}
*/
function fullVolumeTree(activity, color, title) {
return [
...volumeAndCoinsTree(activity, color, title),
...sentProfitLossTree(activity.transferVolume, title),
{
name: "Dormancy",
tree: averagesArray({
@@ -255,8 +237,16 @@ export function createActivitySectionWithActivity({ cohort, title }) {
return {
name: "Activity",
tree: [
...volumeAndCoinsTree(tree.activity, color, title),
...sentProfitLossTree(tree.activity.transferVolume, title),
volumeFolder(tree.activity, color, title, true),
{
name: "Coindays Destroyed",
tree: chartsFromCount({
pattern: tree.activity.coindaysDestroyed,
title: title("Coindays Destroyed"),
unit: Unit.coindays,
color,
}),
},
{
name: "SOPR",
title: title("SOPR (24h)"),
@@ -365,33 +355,79 @@ export function createGroupedActivitySectionWithAdjusted({ list, all, title }) {
tree: [
{
name: "Volume",
tree: ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`Sent Volume (${w.title})`),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.activity.transferVolume.sum[w.key], name, color }),
),
})),
tree: [
...ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`Sent Volume (${w.title})`),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.activity.transferVolume.sum[w.key], name, color }),
),
})),
{
name: "Cumulative",
title: title("Cumulative Sent Volume"),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.activity.transferVolume.cumulative, name, color }),
),
},
{
name: "In Profit",
tree: [
...ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`Sent In Profit (${w.title})`),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.activity.transferVolume.inProfit.sum[w.key], name, color }),
),
})),
{
name: "Cumulative",
title: title("Cumulative Sent In Profit"),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.activity.transferVolume.inProfit.cumulative, name, color }),
),
},
],
},
{
name: "In Loss",
tree: [
...ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`Sent In Loss (${w.title})`),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.activity.transferVolume.inLoss.sum[w.key], name, color }),
),
})),
{
name: "Cumulative",
title: title("Cumulative Sent In Loss"),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.activity.transferVolume.inLoss.cumulative, name, color }),
),
},
],
},
],
},
{
name: "Sent In Profit",
tree: ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`Sent In Profit (${w.title})`),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.activity.transferVolume.inProfit.sum[w.key], name, color }),
),
})),
},
{
name: "Sent In Loss",
tree: ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`Sent In Loss (${w.title})`),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.activity.transferVolume.inLoss.sum[w.key], name, color }),
),
})),
name: "Coindays Destroyed",
tree: [
...ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`Coindays Destroyed (${w.title})`),
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
line({ series: tree.activity.coindaysDestroyed.sum[w.key], name, color, unit: Unit.coindays }),
),
})),
{
name: "Cumulative",
title: title("Cumulative Coindays Destroyed"),
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
line({ series: tree.activity.coindaysDestroyed.cumulative, name, color, unit: Unit.coindays }),
),
},
],
},
{
name: "Dormancy",
@@ -419,22 +455,7 @@ export function createGroupedActivitySectionWithAdjusted({ list, all, title }) {
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: "Coindays Destroyed",
tree: ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`Coindays Destroyed (${w.title})`),
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
line({ series: tree.activity.coindaysDestroyed.sum[w.key], name, color, unit: Unit.coindays }),
line({ series: tree.realized.sellSideRiskRatio[w.key].ratio, name, color, unit: Unit.ratio }),
),
})),
},
@@ -453,33 +474,79 @@ export function createGroupedActivitySection({ list, all, title }) {
tree: [
{
name: "Volume",
tree: ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`Sent Volume (${w.title})`),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.activity.transferVolume.sum[w.key], name, color }),
),
})),
tree: [
...ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`Sent Volume (${w.title})`),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.activity.transferVolume.sum[w.key], name, color }),
),
})),
{
name: "Cumulative",
title: title("Cumulative Sent Volume"),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.activity.transferVolume.cumulative, name, color }),
),
},
{
name: "In Profit",
tree: [
...ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`Sent In Profit (${w.title})`),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.activity.transferVolume.inProfit.sum[w.key], name, color }),
),
})),
{
name: "Cumulative",
title: title("Cumulative Sent In Profit"),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.activity.transferVolume.inProfit.cumulative, name, color }),
),
},
],
},
{
name: "In Loss",
tree: [
...ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`Sent In Loss (${w.title})`),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.activity.transferVolume.inLoss.sum[w.key], name, color }),
),
})),
{
name: "Cumulative",
title: title("Cumulative Sent In Loss"),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.activity.transferVolume.inLoss.cumulative, name, color }),
),
},
],
},
],
},
{
name: "Sent In Profit",
tree: ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`Sent In Profit (${w.title})`),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.activity.transferVolume.inProfit.sum[w.key], name, color }),
),
})),
},
{
name: "Sent In Loss",
tree: ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`Sent In Loss (${w.title})`),
bottom: flatMapCohortsWithAll(list, all, ({ name, color, tree }) =>
satsBtcUsd({ pattern: tree.activity.transferVolume.inLoss.sum[w.key], name, color }),
),
})),
name: "Coindays Destroyed",
tree: [
...ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`Coindays Destroyed (${w.title})`),
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
line({ series: tree.activity.coindaysDestroyed.sum[w.key], name, color, unit: Unit.coindays }),
),
})),
{
name: "Cumulative",
title: title("Cumulative Coindays Destroyed"),
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
line({ series: tree.activity.coindaysDestroyed.cumulative, name, color, unit: Unit.coindays }),
),
},
],
},
{
name: "Dormancy",
@@ -501,22 +568,7 @@ export function createGroupedActivitySection({ list, all, title }) {
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: "Coindays Destroyed",
tree: ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`Coindays Destroyed (${w.title})`),
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
line({ series: tree.activity.coindaysDestroyed.sum[w.key], name, color, unit: Unit.coindays }),
line({ series: tree.realized.sellSideRiskRatio[w.key].ratio, name, color, unit: Unit.ratio }),
),
})),
},
@@ -558,13 +610,22 @@ export function createGroupedActivitySectionWithActivity({ list, all, title }) {
},
{
name: "Coindays Destroyed",
tree: ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`Coindays Destroyed (${w.title})`),
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
line({ series: tree.activity.coindaysDestroyed.sum[w.key], name, color, unit: Unit.coindays }),
),
})),
tree: [
...ROLLING_WINDOWS.map((w) => ({
name: w.name,
title: title(`Coindays Destroyed (${w.title})`),
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
line({ series: tree.activity.coindaysDestroyed.sum[w.key], name, color, unit: Unit.coindays }),
),
})),
{
name: "Cumulative",
title: title("Cumulative Coindays Destroyed"),
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
line({ series: tree.activity.coindaysDestroyed.cumulative, name, color, unit: Unit.coindays }),
),
},
],
},
],
};

View File

@@ -912,7 +912,7 @@ function groupedRealizedSubfolderFull(list, all, title) {
],
},
{
name: "Gross",
name: "Gross P&L",
tree: [
...ROLLING_WINDOWS.map((w) => ({
name: w.name,

View File

@@ -1,19 +1,11 @@
/**
* Capitalization section builders
*
* Structure:
* - Total: Realized Cap (USD)
* - Profitability: Invested Capital (Total + In Profit + In Loss) [full only]
* - MVRV: Market Value to Realized Value ratio
* - % of Own Market Cap [full only]
* - Change: Rolling window absolute changes
* - Growth Rate: Rolling window rate of change
*/
import { Unit } from "../../utils/units.js";
import { colors } from "../../utils/colors.js";
import { ROLLING_WINDOWS, line, baseline, mapWindows, sumsTreeBaseline, rollingPercentRatioTree, percentRatio, percentRatioBaseline } from "../series.js";
import { createRatioChart, mapCohortsWithAll, flatMapCohortsWithAll } from "../shared.js";
import { ratioBottomSeries, mapCohortsWithAll, flatMapCohortsWithAll } from "../shared.js";
// ============================================================================
// Shared building blocks
@@ -41,6 +33,13 @@ function singleDeltaItems(tree, title) {
*/
function groupedDeltaAndMvrv(list, all, title) {
return [
{
name: "MVRV",
title: title("MVRV"),
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
baseline({ series: tree.realized.mvrv, name, color, unit: Unit.ratio, base: 1 }),
),
},
{
name: "Change",
tree: ROLLING_WINDOWS.map((w) => ({
@@ -61,13 +60,6 @@ function groupedDeltaAndMvrv(list, all, title) {
),
})),
},
{
name: "MVRV",
title: title("MVRV"),
bottom: mapCohortsWithAll(list, all, ({ name, color, tree }) =>
baseline({ series: tree.realized.mvrv, name, color, unit: Unit.ratio, base: 1 }),
),
},
];
}
@@ -95,7 +87,7 @@ export function createValuationSectionFull({ cohort, title }) {
line({ series: tree.unrealized.investedCapital.inLoss.usd, name: "In Loss", color: colors.loss, unit: Unit.usd }),
],
},
{ name: "MVRV", title: title("MVRV"), bottom: [baseline({ series: tree.realized.mvrv, name: "MVRV", unit: Unit.ratio, base: 1 })] },
{ name: "MVRV", title: title("MVRV"), bottom: ratioBottomSeries(tree.realized.price) },
{ name: "% of Own Market Cap", title: title("Realized Cap (% of Own Market Cap)"), bottom: percentRatioBaseline({ pattern: tree.realized.cap.toOwnMcap, name: "Rel. to Own Market Cap", color }) },
...singleDeltaItems(tree, title),
],
@@ -172,6 +164,7 @@ export function createGroupedValuationSectionWithOwnMarketCap({ list, all, title
line({ series: tree.unrealized.investedCapital.inLoss.usd, name, color, unit: Unit.usd }),
),
},
...groupedDeltaAndMvrv(list, all, title),
{
name: "% of Own Market Cap",
title: title("Realized Cap (% of Own Market Cap)"),
@@ -179,7 +172,6 @@ export function createGroupedValuationSectionWithOwnMarketCap({ list, all, title
percentRatio({ pattern: tree.realized.cap.toOwnMcap, name, color }),
),
},
...groupedDeltaAndMvrv(list, all, title),
],
};
}

View File

@@ -501,13 +501,41 @@ export function ratioSmas(ratio) {
}
/**
* Create ratio chart from ActivePriceRatioPattern
* Ratio bottom series: baseline + SMAs + percentiles
* @param {AnyRatioPattern} ratio
* @returns {AnyFetchedSeriesBlueprint[]}
*/
export function ratioBottomSeries(ratio) {
return [
baseline({
series: ratio.ratio,
name: "Ratio",
unit: Unit.ratio,
base: 1,
}),
...ratioSmas(ratio).map(({ name, series, color }) =>
line({ series, name, color, unit: Unit.ratio, defaultActive: false }),
),
...percentileMap(ratio).map(({ name, prop, color }) =>
line({
series: prop,
name,
color,
defaultActive: false,
unit: Unit.ratio,
options: { lineStyle: 1 },
}),
),
];
}
/**
* @param {Object} args
* @param {(name: string) => string} args.title
* @param {AnyPricePattern} args.pricePattern - The price pattern to show in top pane
* @param {AnyRatioPattern} args.ratio - The ratio pattern
* @param {AnyPricePattern} args.pricePattern
* @param {AnyRatioPattern} args.ratio
* @param {Color} args.color
* @param {string} [args.name] - Optional name override (default: "ratio")
* @param {string} [args.name]
* @returns {PartialChartOption}
*/
export function createRatioChart({ title, pricePattern, ratio, color, name }) {
@@ -526,27 +554,7 @@ export function createRatioChart({ title, pricePattern, ratio, color, name }) {
}),
),
],
bottom: [
baseline({
series: ratio.ratio,
name: "Ratio",
unit: Unit.ratio,
base: 1,
}),
...ratioSmas(ratio).map(({ name, series, color }) =>
line({ series, name, color, unit: Unit.ratio, defaultActive: false }),
),
...percentileMap(ratio).map(({ name, prop, color }) =>
line({
series: prop,
name,
color,
defaultActive: false,
unit: Unit.ratio,
options: { lineStyle: 1 },
}),
),
],
bottom: ratioBottomSeries(ratio),
};
}