diff --git a/website/scripts/options/distribution/activity.js b/website/scripts/options/distribution/activity.js index 7f6a6c01f..1a410258c 100644 --- a/website/scripts/options/distribution/activity.js +++ b/website/scripts/options/distribution/activity.js @@ -31,21 +31,44 @@ import { colors } from "../../utils/colors.js"; // ============================================================================ /** - * @param {{ transferVolume: TransferVolumePattern, coindaysDestroyed: CountPattern }} 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 }), + ), + }, + ], }, ], }; diff --git a/website/scripts/options/distribution/profitability.js b/website/scripts/options/distribution/profitability.js index ebfb436e7..94e2c70f6 100644 --- a/website/scripts/options/distribution/profitability.js +++ b/website/scripts/options/distribution/profitability.js @@ -912,7 +912,7 @@ function groupedRealizedSubfolderFull(list, all, title) { ], }, { - name: "Gross", + name: "Gross P&L", tree: [ ...ROLLING_WINDOWS.map((w) => ({ name: w.name, diff --git a/website/scripts/options/distribution/valuation.js b/website/scripts/options/distribution/valuation.js index 63b0fea50..3aaaa28ae 100644 --- a/website/scripts/options/distribution/valuation.js +++ b/website/scripts/options/distribution/valuation.js @@ -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), ], }; } diff --git a/website/scripts/options/shared.js b/website/scripts/options/shared.js index 208ce9948..666348744 100644 --- a/website/scripts/options/shared.js +++ b/website/scripts/options/shared.js @@ -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), }; }