investing: more data + charts

This commit is contained in:
nym21
2026-01-26 10:28:26 +01:00
parent 5c824e50b8
commit 371fb2cb17
30 changed files with 1922 additions and 1010 deletions

View File

@@ -30,7 +30,6 @@ export function createChainSection(ctx) {
pools,
inputs,
outputs,
market,
scripts,
supply,
distribution,
@@ -381,17 +380,22 @@ export function createChainSection(ctx) {
],
},
// Input
// UTXO Set (merged Input, Output, UTXO)
{
name: "Input",
name: "UTXO Set",
tree: [
{
name: "Count",
name: "Input Count",
title: "Input Count",
bottom: [...fromSizePattern(inputs.count, Unit.count)],
},
{
name: "Speed",
name: "Output Count",
title: "Output Count",
bottom: [...fromSizePattern(outputs.count.totalCount, Unit.count)],
},
{
name: "Inputs/sec",
title: "Inputs Per Second",
bottom: [
dots({
@@ -401,20 +405,8 @@ export function createChainSection(ctx) {
}),
],
},
],
},
// Output
{
name: "Output",
tree: [
{
name: "Count",
title: "Output Count",
bottom: [...fromSizePattern(outputs.count.totalCount, Unit.count)],
},
{
name: "Speed",
name: "Outputs/sec",
title: "Outputs Per Second",
bottom: [
dots({
@@ -424,14 +416,8 @@ export function createChainSection(ctx) {
}),
],
},
],
},
{
name: "UTXO",
tree: [
{
name: "Count",
name: "UTXO Count",
title: "UTXO Count",
bottom: [
line({
@@ -451,19 +437,49 @@ export function createChainSection(ctx) {
{
name: "Count",
tree: [
{ name: "P2PKH", title: "P2PKH Output Count", bottom: fromDollarsPattern(scripts.count.p2pkh, Unit.count) },
{ name: "P2SH", title: "P2SH Output Count", bottom: fromDollarsPattern(scripts.count.p2sh, Unit.count) },
{ name: "P2WPKH", title: "P2WPKH Output Count", bottom: fromDollarsPattern(scripts.count.p2wpkh, Unit.count) },
{ name: "P2WSH", title: "P2WSH Output Count", bottom: fromDollarsPattern(scripts.count.p2wsh, Unit.count) },
{ name: "P2TR", title: "P2TR Output Count", bottom: fromDollarsPattern(scripts.count.p2tr, Unit.count) },
{ name: "P2PK33", title: "P2PK33 Output Count", bottom: fromDollarsPattern(scripts.count.p2pk33, Unit.count) },
{ name: "P2PK65", title: "P2PK65 Output Count", bottom: fromDollarsPattern(scripts.count.p2pk65, Unit.count) },
{ name: "P2MS", title: "P2MS Output Count", bottom: fromDollarsPattern(scripts.count.p2ms, Unit.count) },
{ name: "P2A", title: "P2A Output Count", bottom: fromDollarsPattern(scripts.count.p2a, Unit.count) },
{ name: "OP_RETURN", title: "OP_RETURN Output Count", bottom: fromDollarsPattern(scripts.count.opreturn, Unit.count) },
{ name: "SegWit", title: "SegWit Output Count", bottom: fromDollarsPattern(scripts.count.segwit, Unit.count) },
{ name: "Empty", title: "Empty Output Count", bottom: fromDollarsPattern(scripts.count.emptyoutput, Unit.count) },
{ name: "Unknown", title: "Unknown Output Count", bottom: fromDollarsPattern(scripts.count.unknownoutput, Unit.count) },
// Legacy scripts
{
name: "Legacy",
tree: [
{ name: "P2PKH", title: "P2PKH Output Count", bottom: fromDollarsPattern(scripts.count.p2pkh, Unit.count) },
{ name: "P2PK33", title: "P2PK33 Output Count", bottom: fromDollarsPattern(scripts.count.p2pk33, Unit.count) },
{ name: "P2PK65", title: "P2PK65 Output Count", bottom: fromDollarsPattern(scripts.count.p2pk65, Unit.count) },
],
},
// Script Hash
{
name: "Script Hash",
tree: [
{ name: "P2SH", title: "P2SH Output Count", bottom: fromDollarsPattern(scripts.count.p2sh, Unit.count) },
{ name: "P2MS", title: "P2MS Output Count", bottom: fromDollarsPattern(scripts.count.p2ms, Unit.count) },
],
},
// SegWit scripts
{
name: "SegWit",
tree: [
{ name: "All SegWit", title: "SegWit Output Count", bottom: fromDollarsPattern(scripts.count.segwit, Unit.count) },
{ name: "P2WPKH", title: "P2WPKH Output Count", bottom: fromDollarsPattern(scripts.count.p2wpkh, Unit.count) },
{ name: "P2WSH", title: "P2WSH Output Count", bottom: fromDollarsPattern(scripts.count.p2wsh, Unit.count) },
],
},
// Taproot scripts
{
name: "Taproot",
tree: [
{ name: "P2TR", title: "P2TR Output Count", bottom: fromDollarsPattern(scripts.count.p2tr, Unit.count) },
{ name: "P2A", title: "P2A Output Count", bottom: fromDollarsPattern(scripts.count.p2a, Unit.count) },
],
},
// Other scripts
{
name: "Other",
tree: [
{ name: "OP_RETURN", title: "OP_RETURN Output Count", bottom: fromDollarsPattern(scripts.count.opreturn, Unit.count) },
{ name: "Empty", title: "Empty Output Count", bottom: fromDollarsPattern(scripts.count.emptyoutput, Unit.count) },
{ name: "Unknown", title: "Unknown Output Count", bottom: fromDollarsPattern(scripts.count.unknownoutput, Unit.count) },
],
},
],
},
{
@@ -489,12 +505,7 @@ export function createChainSection(ctx) {
},
],
},
{
name: "Value",
tree: [
{ name: "OP_RETURN", title: "OP_RETURN Value", bottom: fromCoinbasePattern(scripts.value.opreturn) },
],
},
{ name: "OP_RETURN Value", title: "OP_RETURN Value", bottom: fromCoinbasePattern(scripts.value.opreturn) },
],
},
@@ -925,18 +936,6 @@ export function createChainSection(ctx) {
}),
],
},
{
name: "Puell Multiple",
title: "Puell Multiple",
bottom: [
baseline({
metric: market.indicators.puellMultiple,
name: "Puell Multiple",
unit: Unit.ratio,
base: 1,
}),
],
},
],
},

View File

@@ -24,7 +24,7 @@ function createCointimePriceWithRatioOptions(
) {
return [
{
name: "price",
name: "Price",
title,
top: [line({ metric: price, name: legend, color, unit: Unit.usd })],
},
@@ -78,7 +78,7 @@ export function createCointimeSection(ctx) {
{
price: pricing.cointimePrice,
ratio: pricing.cointimePriceRatio,
name: "cointime",
name: "Cointime",
title: "Cointime Price",
color: colors.yellow,
},
@@ -88,31 +88,31 @@ export function createCointimeSection(ctx) {
const cointimeCapitalizations = [
{
metric: cap.vaultedCap,
name: "vaulted",
name: "Vaulted",
title: "Vaulted Cap",
color: colors.lime,
},
{
metric: cap.activeCap,
name: "active",
name: "Active",
title: "Active Cap",
color: colors.rose,
},
{
metric: cap.cointimeCap,
name: "cointime",
name: "Cointime",
title: "Cointime Cap",
color: colors.yellow,
},
{
metric: cap.investorCap,
name: "investor",
name: "Investor",
title: "Investor Cap",
color: colors.fuchsia,
},
{
metric: cap.thermoCap,
name: "thermo",
name: "Thermo",
title: "Thermo Cap",
color: colors.emerald,
},
@@ -283,7 +283,7 @@ export function createCointimeSection(ctx) {
name: "Reserve Risk",
tree: [
{
name: "reserve risk",
name: "Ratio",
title: "Reserve Risk",
bottom: [
line({
@@ -295,7 +295,7 @@ export function createCointimeSection(ctx) {
],
},
{
name: "hodl bank",
name: "HODL Bank",
title: "HODL Bank",
bottom: [
line({
@@ -307,7 +307,7 @@ export function createCointimeSection(ctx) {
],
},
{
name: "vocdd 365d sma",
name: "VOCDD 365d SMA",
title: "VOCDD 365d SMA",
bottom: [
line({
@@ -326,7 +326,7 @@ export function createCointimeSection(ctx) {
name: "Value",
tree: [
{
name: "created",
name: "Created",
title: "Cointime Value Created",
bottom: [
line({
@@ -345,7 +345,7 @@ export function createCointimeSection(ctx) {
],
},
{
name: "destroyed",
name: "Destroyed",
title: "Cointime Value Destroyed",
bottom: [
line({
@@ -364,7 +364,7 @@ export function createCointimeSection(ctx) {
],
},
{
name: "stored",
name: "Stored",
title: "Cointime Value Stored",
bottom: [
line({
@@ -383,7 +383,7 @@ export function createCointimeSection(ctx) {
],
},
{
name: "vocdd",
name: "VOCDD",
title: "VOCDD (Value of Coin Days Destroyed)",
bottom: [
line({

View File

@@ -18,8 +18,6 @@ import {
createSingleCoinsDestroyedSeries,
createGroupedCoinblocksDestroyedSeries,
createGroupedCoindaysDestroyedSeries,
createGroupedSatblocksDestroyedSeries,
createGroupedSatdaysDestroyedSeries,
createSingleSentSeries,
createGroupedSentSatsSeries,
createGroupedSentBitcoinSeries,
@@ -48,7 +46,7 @@ export function createAddressCohortFolder(ctx, args) {
// Supply section
isSingle
? {
name: "supply",
name: "Supply",
title: title("Supply"),
bottom: createSingleSupplySeries(
ctx,
@@ -60,14 +58,14 @@ export function createAddressCohortFolder(ctx, args) {
// UTXO count
{
name: "utxo count",
name: "UTXO Count",
title: title("UTXO Count"),
bottom: createUtxoCountSeries(list, useGroupName),
},
// Address count (ADDRESS COHORTS ONLY - fully type safe!)
{
name: "address count",
name: "Address Count",
title: title("Address Count"),
bottom: createAddressCountSeries(ctx, list, useGroupName),
},
@@ -94,12 +92,12 @@ export function createAddressCohortFolder(ctx, args) {
title,
)),
{
name: "capitalization",
name: "Capitalization",
title: title("Realized Cap"),
bottom: createRealizedCapWithExtras(ctx, list, args, useGroupName),
},
{
name: "value",
name: "Value",
title: title("Realized Value"),
bottom: list.map(({ color, name, tree }) =>
line({
@@ -143,7 +141,7 @@ function createRealizedPriceOptions(args, title) {
return [
{
name: "price",
name: "Price",
title: title("Realized Price"),
top: [
line({
@@ -179,7 +177,7 @@ function createRealizedCapWithExtras(ctx, list, args, useGroupName) {
? [
baseline({
metric: tree.realized.realizedCap30dDelta,
name: "30d Change",
name: "1m Change",
unit: Unit.usd,
defaultActive: false,
}),
@@ -203,7 +201,7 @@ function createRealizedPnlSection(ctx, args, title) {
return [
{
name: "pnl",
name: "P&L",
title: title("Realized P&L"),
bottom: [
line({
@@ -297,7 +295,7 @@ function createRealizedPnlSection(ctx, args, title) {
}),
baseline({
metric: realized.netRealizedPnlCumulative30dDelta,
name: "Cumulative 30d change",
name: "Cumulative 1m Change",
unit: Unit.usd,
defaultActive: false,
}),
@@ -314,13 +312,13 @@ function createRealizedPnlSection(ctx, args, title) {
}),
baseline({
metric: realized.netRealizedPnlCumulative30dDeltaRelToRealizedCap,
name: "Cumulative 30d change",
name: "Cumulative 1m Change",
unit: Unit.pctRcap,
defaultActive: false,
}),
baseline({
metric: realized.netRealizedPnlCumulative30dDeltaRelToMarketCap,
name: "Cumulative 30d change",
name: "Cumulative 1m Change",
unit: Unit.pctMcap,
}),
priceLine({
@@ -339,7 +337,7 @@ function createRealizedPnlSection(ctx, args, title) {
],
},
{
name: "sopr",
name: "SOPR",
title: title("SOPR"),
bottom: [
baseline({
@@ -398,7 +396,7 @@ function createRealizedPnlSection(ctx, args, title) {
],
},
{
name: "value",
name: "Value",
title: title("Value Created & Destroyed"),
bottom: [
line({
@@ -434,7 +432,7 @@ function createUnrealizedSection(ctx, list, useGroupName, title) {
name: "Unrealized",
tree: [
{
name: "profit",
name: "Profit",
title: title("Unrealized Profit"),
bottom: list.flatMap(({ color, name, tree }) => [
line({
@@ -446,7 +444,7 @@ function createUnrealizedSection(ctx, list, useGroupName, title) {
]),
},
{
name: "loss",
name: "Loss",
title: title("Unrealized Loss"),
bottom: list.flatMap(({ color, name, tree }) => [
line({
@@ -458,7 +456,7 @@ function createUnrealizedSection(ctx, list, useGroupName, title) {
]),
},
{
name: "total pnl",
name: "Total P&L",
title: title("Total Unrealized P&L"),
bottom: list.flatMap(({ color, name, tree }) => [
baseline({
@@ -470,7 +468,7 @@ function createUnrealizedSection(ctx, list, useGroupName, title) {
]),
},
{
name: "negative loss",
name: "Negative Loss",
title: title("Negative Unrealized Loss"),
bottom: list.flatMap(({ color, name, tree }) => [
line({
@@ -485,7 +483,7 @@ function createUnrealizedSection(ctx, list, useGroupName, title) {
name: "Relative",
tree: [
{
name: "nupl",
name: "NUPL",
title: title("NUPL (Rel to Market Cap)"),
bottom: list.flatMap(({ color, name, tree }) => [
baseline({
@@ -498,7 +496,7 @@ function createUnrealizedSection(ctx, list, useGroupName, title) {
]),
},
{
name: "profit",
name: "Profit",
title: title("Unrealized Profit (% of Market Cap)"),
bottom: list.flatMap(({ color, name, tree }) => [
line({
@@ -510,7 +508,7 @@ function createUnrealizedSection(ctx, list, useGroupName, title) {
]),
},
{
name: "loss",
name: "Loss",
title: title("Unrealized Loss (% of Market Cap)"),
bottom: list.flatMap(({ color, name, tree }) => [
line({
@@ -522,7 +520,7 @@ function createUnrealizedSection(ctx, list, useGroupName, title) {
]),
},
{
name: "net pnl",
name: "Net P&L",
title: title("Net Unrealized P&L (% of Market Cap)"),
bottom: list.flatMap(({ color, name, tree }) => [
baseline({
@@ -534,7 +532,7 @@ function createUnrealizedSection(ctx, list, useGroupName, title) {
]),
},
{
name: "negative loss",
name: "Negative Loss",
title: title("Negative Unrealized Loss (% of Market Cap)"),
bottom: list.flatMap(({ color, name, tree }) => [
line({
@@ -548,7 +546,7 @@ function createUnrealizedSection(ctx, list, useGroupName, title) {
],
},
{
name: "nupl",
name: "NUPL",
title: title("Net Unrealized P&L"),
bottom: list.flatMap(({ color, name, tree }) => [
baseline({
@@ -581,7 +579,7 @@ function createCostBasisSection(list, useGroupName, title) {
name: "Cost Basis",
tree: [
{
name: "min",
name: "Min",
title: title("Min Cost Basis"),
top: list.map(({ color, name, tree }) =>
line({
@@ -656,16 +654,6 @@ function createActivitySection(args, title) {
title: title("Coindays Destroyed"),
bottom: createGroupedCoindaysDestroyedSeries(list),
},
{
name: "satblocks destroyed",
title: title("Satblocks Destroyed"),
bottom: createGroupedSatblocksDestroyedSeries(list),
},
{
name: "satdays destroyed",
title: title("Satdays Destroyed"),
bottom: createGroupedSatdaysDestroyedSeries(list),
},
{
name: "Sent",
tree: [

View File

@@ -109,20 +109,20 @@ export function createGroupedSupplyInLossSeries(list, { relativeMetrics } = {})
*/
export function createGroupedSupplySection(list, title, { supplyRelativeMetrics, profitRelativeMetrics, lossRelativeMetrics } = {}) {
return {
name: "supply",
name: "Supply",
tree: [
{
name: "total",
name: "Total",
title: title("Supply"),
bottom: createGroupedSupplyTotalSeries(list, { relativeMetrics: supplyRelativeMetrics }),
},
{
name: "in profit",
name: "In Profit",
title: title("Supply In Profit"),
bottom: createGroupedSupplyInProfitSeries(list, { relativeMetrics: profitRelativeMetrics }),
},
{
name: "in loss",
name: "In Loss",
title: title("Supply In Loss"),
bottom: createGroupedSupplyInLossSeries(list, { relativeMetrics: lossRelativeMetrics }),
},
@@ -404,18 +404,6 @@ export function createSingleCoinsDestroyedSeries(cohort) {
unit: Unit.coindays,
defaultActive: false,
}),
line({
metric: tree.activity.satblocksDestroyed,
name: "Satblocks",
color,
unit: Unit.satblocks,
}),
line({
metric: tree.activity.satdaysDestroyed,
name: "Satdays",
color,
unit: Unit.satdays,
}),
];
}
@@ -451,38 +439,6 @@ export function createGroupedCoindaysDestroyedSeries(list) {
]);
}
/**
* Create satblocks destroyed series for grouped cohorts (comparison)
* @param {readonly CohortObject[]} list
* @returns {AnyFetchedSeriesBlueprint[]}
*/
export function createGroupedSatblocksDestroyedSeries(list) {
return list.flatMap(({ color, name, tree }) => [
line({
metric: tree.activity.satblocksDestroyed,
name,
color,
unit: Unit.satblocks,
}),
]);
}
/**
* Create satdays destroyed series for grouped cohorts (comparison)
* @param {readonly CohortObject[]} list
* @returns {AnyFetchedSeriesBlueprint[]}
*/
export function createGroupedSatdaysDestroyedSeries(list) {
return list.flatMap(({ color, name, tree }) => [
line({
metric: tree.activity.satdaysDestroyed,
name,
color,
unit: Unit.satdays,
}),
]);
}
/**
* Create sent series (sats, btc, usd) for single cohort - all on one chart
* @param {CohortObject} cohort

View File

@@ -136,7 +136,7 @@ export function createCohortFolderWithAdjusted(ctx, args) {
createSingleUtxoCountChart(args, title),
createSingleRealizedSectionWithAdjusted(ctx, args, title),
createSingleUnrealizedSectionWithMarketCap(ctx, args, title),
createCostBasisSection({ cohort: args, title }),
createCostBasisSection(ctx, { cohort: args, title }),
createSingleActivitySectionWithAdjusted(ctx, args, title),
],
};
@@ -249,7 +249,7 @@ export function createCohortFolderBasicWithMarketCap(ctx, args) {
createSingleUtxoCountChart(args, title),
createSingleRealizedSectionBasic(ctx, args, title),
createSingleUnrealizedSectionWithMarketCapOnly(ctx, args, title),
createCostBasisSection({ cohort: args, title }),
createCostBasisSection(ctx, { cohort: args, title }),
createActivitySection({ ctx, cohort: args, title }),
],
};
@@ -285,7 +285,7 @@ export function createCohortFolderBasicWithoutMarketCap(ctx, args) {
createSingleUtxoCountChart(args, title),
createSingleRealizedSectionBasic(ctx, args, title),
createSingleUnrealizedSectionBase(ctx, args, title),
createCostBasisSection({ cohort: args, title }),
createCostBasisSection(ctx, { cohort: args, title }),
createActivitySection({ ctx, cohort: args, title }),
],
};
@@ -324,7 +324,7 @@ export function createCohortFolderAddress(ctx, args) {
createSingleAddrCountChart(ctx, args, title),
createSingleRealizedSectionBasic(ctx, args, title),
createSingleUnrealizedSectionBase(ctx, args, title),
createCostBasisSection({ cohort: args, title }),
createCostBasisSection(ctx, { cohort: args, title }),
createActivitySection({ ctx, cohort: args, title }),
],
};
@@ -342,7 +342,7 @@ export function createCohortFolderAddress(ctx, args) {
*/
function createSingleSupplyChart(ctx, cohort, title, options = {}) {
return {
name: "supply",
name: "Supply",
title: title("Supply"),
bottom: createSingleSupplySeries(ctx, cohort, options),
};
@@ -356,7 +356,7 @@ function createSingleSupplyChart(ctx, cohort, title, options = {}) {
*/
function createSingleUtxoCountChart(cohort, title) {
return {
name: "utxo count",
name: "UTXO Count",
title: title("UTXO Count"),
bottom: createUtxoCountSeries([cohort], false),
};
@@ -370,7 +370,7 @@ function createSingleUtxoCountChart(cohort, title) {
*/
function createGroupedUtxoCountChart(list, title) {
return {
name: "utxo count",
name: "UTXO Count",
title: title("UTXO Count"),
bottom: createUtxoCountSeries(list, true),
};
@@ -385,7 +385,7 @@ function createGroupedUtxoCountChart(list, title) {
*/
function createSingleAddrCountChart(ctx, cohort, title) {
return {
name: "address count",
name: "Address Count",
title: title("Address Count"),
bottom: [
line({
@@ -406,7 +406,7 @@ function createSingleAddrCountChart(ctx, cohort, title) {
*/
function createGroupedAddrCountChart(list, title) {
return {
name: "address count",
name: "Address Count",
title: title("Address Count"),
bottom: list.map(({ color, name, addrCount }) =>
line({ metric: addrCount, name, color, unit: Unit.count }),
@@ -427,7 +427,7 @@ function createSingleRealizedSectionFull(ctx, cohort, title) {
tree: [
...createSingleRealizedPriceChartsWithRatio(ctx, cohort, title),
{
name: "capitalization",
name: "Capitalization",
title: title("Realized Cap"),
bottom: createSingleRealizedCapSeries(ctx, cohort, {
extra: createRealizedCapRatioSeries(ctx, cohort.tree),
@@ -454,7 +454,7 @@ function createSingleRealizedSectionWithAdjusted(ctx, cohort, title) {
tree: [
...createSingleRealizedPriceChartsBasic(ctx, cohort, title),
{
name: "capitalization",
name: "Capitalization",
title: title("Realized Cap"),
bottom: createSingleRealizedCapSeries(ctx, cohort),
},
@@ -489,7 +489,7 @@ function createGroupedRealizedSectionWithAdjusted(ctx, list, title, { ratioMetri
bottom: createRealizedPriceRatioSeries(list),
},
{
name: "capitalization",
name: "Capitalization",
title: title("Realized Cap"),
bottom: createGroupedRealizedCapSeries(list),
},
@@ -512,7 +512,7 @@ function createSingleRealizedSectionWithPercentiles(ctx, cohort, title) {
tree: [
...createSingleRealizedPriceChartsWithRatio(ctx, cohort, title),
{
name: "capitalization",
name: "Capitalization",
title: title("Realized Cap"),
bottom: createSingleRealizedCapSeries(ctx, cohort, {
extra: createRealizedCapRatioSeries(ctx, cohort.tree),
@@ -539,7 +539,7 @@ function createSingleRealizedSectionBasic(ctx, cohort, title) {
tree: [
...createSingleRealizedPriceChartsBasic(ctx, cohort, title),
{
name: "capitalization",
name: "Capitalization",
title: title("Realized Cap"),
bottom: createSingleRealizedCapSeries(ctx, cohort),
},
@@ -574,7 +574,7 @@ function createGroupedRealizedSectionBasic(ctx, list, title, { ratioMetrics } =
bottom: createRealizedPriceRatioSeries(list),
},
{
name: "capitalization",
name: "Capitalization",
title: title("Realized Cap"),
bottom: createGroupedRealizedCapSeries(list),
},
@@ -593,7 +593,7 @@ function createGroupedRealizedSectionBasic(ctx, list, title, { ratioMetrics } =
function createSingleRealizedPriceChart(cohort, title) {
const { tree, color } = cohort;
return {
name: "price",
name: "Price",
title: title("Realized Price"),
top: [
line({
@@ -651,7 +651,7 @@ function createSingleRealizedPriceChartsBasic(ctx, cohort, title) {
return [
createSingleRealizedPriceChart(cohort, title),
{
name: "ratio",
name: "Ratio",
title: title("Realized Price Ratio"),
bottom: [
baseline({
@@ -693,7 +693,7 @@ function createSingleRealizedCapSeries(ctx, cohort, { extra = [] } = {}) {
}),
baseline({
metric: tree.realized.realizedCap30dDelta,
name: "30d change",
name: "1m Change",
unit: Unit.usd,
defaultActive: false,
}),
@@ -789,7 +789,7 @@ function createSingleRealizedPnlSection(ctx, cohort, title, { extra = [] } = {})
return [
{
name: "pnl",
name: "P&L",
title: title("Realized P&L"),
bottom: [
...fromBlockCountWithUnit(
@@ -849,7 +849,7 @@ function createSingleRealizedPnlSection(ctx, cohort, title, { extra = [] } = {})
],
},
{
name: "Net pnl",
name: "Net P&L",
title: title("Net Realized P&L"),
bottom: [
...fromBlockCountWithUnit(
@@ -859,7 +859,7 @@ function createSingleRealizedPnlSection(ctx, cohort, title, { extra = [] } = {})
),
baseline({
metric: tree.realized.netRealizedPnlCumulative30dDelta,
name: "Cumulative 30d change",
name: "Cumulative 1m Change",
unit: Unit.usd,
defaultActive: false,
}),
@@ -877,13 +877,13 @@ function createSingleRealizedPnlSection(ctx, cohort, title, { extra = [] } = {})
baseline({
metric:
tree.realized.netRealizedPnlCumulative30dDeltaRelToRealizedCap,
name: "Cumulative 30d change",
name: "Cumulative 1m Change",
unit: Unit.pctRcap,
defaultActive: false,
}),
baseline({
metric: tree.realized.netRealizedPnlCumulative30dDeltaRelToMarketCap,
name: "Cumulative 30d change",
name: "Cumulative 1m Change",
unit: Unit.pctMcap,
}),
priceLine({ ctx, unit: Unit.pctMcap }),
@@ -907,7 +907,7 @@ function createSingleRealizedPnlSection(ctx, cohort, title, { extra = [] } = {})
function createGroupedRealizedPnlSections(ctx, list, title, { ratioMetrics } = {}) {
return [
{
name: "profit",
name: "Profit",
title: title("Realized Profit"),
bottom: [
...list.flatMap(({ color, name, tree }) => [
@@ -928,7 +928,7 @@ function createGroupedRealizedPnlSections(ctx, list, title, { ratioMetrics } = {
],
},
{
name: "loss",
name: "Loss",
title: title("Realized Loss"),
bottom: [
...list.flatMap(({ color, name, tree }) => [
@@ -949,7 +949,7 @@ function createGroupedRealizedPnlSections(ctx, list, title, { ratioMetrics } = {
],
},
{
name: "Total pnl",
name: "Total P&L",
title: title("Total Realized P&L"),
bottom: [
...list.flatMap((cohort) => [
@@ -964,7 +964,7 @@ function createGroupedRealizedPnlSections(ctx, list, title, { ratioMetrics } = {
],
},
{
name: "Net pnl",
name: "Net P&L",
title: title("Net Realized P&L"),
bottom: [
...list.flatMap(({ color, name, tree }) => [
@@ -986,10 +986,10 @@ function createGroupedRealizedPnlSections(ctx, list, title, { ratioMetrics } = {
],
},
{
name: "cumulative",
name: "Cumulative",
tree: [
{
name: "profit",
name: "Profit",
title: title("Cumulative Realized Profit"),
bottom: list.flatMap(({ color, name, tree }) => [
line({
@@ -1001,7 +1001,7 @@ function createGroupedRealizedPnlSections(ctx, list, title, { ratioMetrics } = {
]),
},
{
name: "loss",
name: "Loss",
title: title("Cumulative Realized Loss"),
bottom: list.flatMap(({ color, name, tree }) => [
line({
@@ -1013,7 +1013,7 @@ function createGroupedRealizedPnlSections(ctx, list, title, { ratioMetrics } = {
]),
},
{
name: "Net pnl",
name: "Net P&L",
title: title("Cumulative Net Realized P&L"),
bottom: [
...list.flatMap(({ color, name, tree }) => [
@@ -1028,8 +1028,8 @@ function createGroupedRealizedPnlSections(ctx, list, title, { ratioMetrics } = {
],
},
{
name: "Net pnl 30d change",
title: title("Net Realized P&L 30d Change"),
name: "Net P&L 1m Change",
title: title("Net Realized P&L 1m Change"),
bottom: [
...list.flatMap(({ color, name, tree }) => [
baseline({
@@ -1244,7 +1244,7 @@ function createGroupedAdjustedSoprChart(list, title) {
*/
function createSingleSoprSectionWithAdjusted(ctx, cohort, title) {
return {
name: "sopr",
name: "SOPR",
tree: [
createSingleBaseSoprChart(ctx, cohort, title),
createSingleAdjustedSoprChart(ctx, cohort, title),
@@ -1260,7 +1260,7 @@ function createSingleSoprSectionWithAdjusted(ctx, cohort, title) {
*/
function createGroupedSoprSectionWithAdjusted(list, title) {
return {
name: "sopr",
name: "SOPR",
tree: [
createGroupedBaseSoprChart(list, title),
createGroupedAdjustedSoprChart(list, title),
@@ -1277,7 +1277,7 @@ function createGroupedSoprSectionWithAdjusted(list, title) {
*/
function createSingleSoprSectionBasic(ctx, cohort, title) {
return {
name: "sopr",
name: "SOPR",
tree: [createSingleBaseSoprChart(ctx, cohort, title)],
};
}
@@ -1290,7 +1290,7 @@ function createSingleSoprSectionBasic(ctx, cohort, title) {
*/
function createGroupedSoprSectionBasic(list, title) {
return {
name: "sopr",
name: "SOPR",
tree: [createGroupedBaseSoprChart(list, title)],
};
}
@@ -1494,7 +1494,7 @@ function createNetUnrealizedPnlBaseMetric(tree) {
*/
function createNuplChart(ctx, rel, title) {
return {
name: "nupl",
name: "NUPL",
title: title("NUPL"),
bottom: [
baseline({
@@ -1516,7 +1516,7 @@ function createNuplChart(ctx, rel, title) {
*/
function createGroupedNuplChart(ctx, list, title) {
return {
name: "nupl",
name: "NUPL",
title: title("NUPL"),
bottom: [
...list.map(({ color, name, tree }) =>
@@ -1552,7 +1552,7 @@ function createUnrealizedSection({ ctx, tree, title, pnl = [], netPnl = [], char
name: "Unrealized",
tree: [
{
name: "pnl",
name: "P&L",
title: title("Unrealized P&L"),
bottom: [
...createUnrealizedPnlBaseMetrics(ctx, tree),
@@ -1561,7 +1561,7 @@ function createUnrealizedSection({ ctx, tree, title, pnl = [], netPnl = [], char
],
},
{
name: "Net pnl",
name: "Net P&L",
title: title("Net Unrealized P&L"),
bottom: [
createNetUnrealizedPnlBaseMetric(tree),
@@ -1590,7 +1590,7 @@ function createGroupedUnrealizedSection({ list, title, netPnlMetrics, charts = [
tree: [
...createGroupedUnrealizedBaseCharts(list, title),
{
name: "Net pnl",
name: "Net P&L",
title: title("Net Unrealized P&L"),
bottom: [
...list.flatMap((cohort) => [
@@ -1725,7 +1725,7 @@ function createSingleUnrealizedSectionBase(ctx, cohort, title) {
function createGroupedUnrealizedBaseCharts(list, title) {
return [
{
name: "profit",
name: "Profit",
title: title("Unrealized Profit"),
bottom: list.flatMap(({ color, name, tree }) => [
line({
@@ -1737,7 +1737,7 @@ function createGroupedUnrealizedBaseCharts(list, title) {
]),
},
{
name: "loss",
name: "Loss",
title: title("Unrealized Loss"),
bottom: list.flatMap(({ color, name, tree }) => [
line({
@@ -1749,7 +1749,7 @@ function createGroupedUnrealizedBaseCharts(list, title) {
]),
},
{
name: "total pnl",
name: "Total P&L",
title: title("Unrealized Total P&L"),
bottom: list.flatMap(({ color, name, tree }) => [
line({
@@ -1918,19 +1918,21 @@ function createGroupedUnrealizedSectionAgeRange(list, title) {
/**
* Generic single cost basis section builder - callers pass optional percentiles
* @param {PartialContext} ctx
* @param {Object} args
* @param {UtxoCohortObject} args.cohort
* @param {(metric: string) => string} args.title
* @param {PartialChartOption[]} [args.charts] - Extra charts (e.g., percentiles)
* @returns {PartialOptionsGroup}
*/
function createCostBasisSection({ cohort, title, charts = [] }) {
function createCostBasisSection(ctx, { cohort, title, charts = [] }) {
const { colors } = ctx;
const { color, tree } = cohort;
return {
name: "Cost Basis",
tree: [
{
name: charts.length > 0 ? "Average" : "cost basis",
name: "Average",
title: title("Cost Basis"),
top: [
line({
@@ -1940,16 +1942,41 @@ function createCostBasisSection({ cohort, title, charts = [] }) {
unit: Unit.usd,
}),
line({
metric: tree.costBasis.min,
name: "Min",
color,
metric: tree.costBasis.max,
name: "Max",
color: colors.green,
unit: Unit.usd,
defaultActive: false,
}),
line({
metric: tree.costBasis.min,
name: "Min",
color: colors.red,
unit: Unit.usd,
defaultActive: false,
}),
],
},
{
name: "Max",
title: title("Max Cost Basis"),
top: [
line({
metric: tree.costBasis.max,
name: "Max",
color,
color: colors.green,
unit: Unit.usd,
}),
],
},
{
name: "Min",
title: title("Min Cost Basis"),
top: [
line({
metric: tree.costBasis.min,
name: "Min",
color: colors.red,
unit: Unit.usd,
}),
],
@@ -1984,13 +2011,6 @@ function createGroupedCostBasisSection({ list, title, charts = [] }) {
}),
),
},
{
name: "Min",
title: title("Min Cost Basis"),
top: list.map(({ color, name, tree }) =>
line({ metric: tree.costBasis.min, name, color, unit: Unit.usd }),
),
},
{
name: "Max",
title: title("Max Cost Basis"),
@@ -1998,6 +2018,13 @@ function createGroupedCostBasisSection({ list, title, charts = [] }) {
line({ metric: tree.costBasis.max, name, color, unit: Unit.usd }),
),
},
{
name: "Min",
title: title("Min Cost Basis"),
top: list.map(({ color, name, tree }) =>
line({ metric: tree.costBasis.min, name, color, unit: Unit.usd }),
),
},
...charts,
],
};
@@ -2016,12 +2043,12 @@ function createGroupedCostBasisSection({ list, title, charts = [] }) {
*/
function createSingleCostBasisSectionWithPercentiles(ctx, cohort, title) {
const { colors } = ctx;
return createCostBasisSection({
return createCostBasisSection(ctx, {
cohort,
title,
charts: [
{
name: "percentiles",
name: "Percentiles",
title: title("Cost Basis Percentiles"),
top: createCostBasisPercentilesSeries(colors, [cohort], false),
},
@@ -2043,7 +2070,7 @@ function createGroupedCostBasisSectionWithPercentiles(ctx, list, title) {
title,
charts: [
{
name: "percentiles",
name: "Percentiles",
title: title("Cost Basis Percentiles"),
top: createCostBasisPercentilesSeries(colors, list, true),
},
@@ -2091,7 +2118,7 @@ function createActivitySection({ ctx, cohort, title, valueMetrics = [] }) {
],
},
{
name: "value",
name: "Value",
title: title("Value Created & Destroyed"),
bottom: [
line({ metric: tree.realized.valueCreated, name: "Created", color: colors.emerald, unit: Unit.usd }),
@@ -2107,8 +2134,6 @@ function createActivitySection({ ctx, cohort, title, valueMetrics = [] }) {
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 }),
line({ metric: tree.activity.satblocksDestroyed, name: "Satblocks", color, unit: Unit.satblocks }),
line({ metric: tree.activity.satdaysDestroyed, name: "Satdays", color, unit: Unit.satdays }),
],
},
],
@@ -2136,17 +2161,17 @@ function createGroupedActivitySection({ list, title, valueTree }) {
]),
},
{
name: "value",
name: "Value",
tree: valueTree ?? [
{
name: "created",
name: "Created",
title: title("Value Created"),
bottom: list.flatMap(({ color, name, tree }) => [
line({ metric: tree.realized.valueCreated, name, color, unit: Unit.usd }),
]),
},
{
name: "destroyed",
name: "Destroyed",
title: title("Value Destroyed"),
bottom: list.flatMap(({ color, name, tree }) => [
line({ metric: tree.realized.valueDestroyed, name, color, unit: Unit.usd }),
@@ -2216,7 +2241,7 @@ function createGroupedActivitySectionWithAdjusted(list, title) {
title,
valueTree: [
{
name: "created",
name: "Created",
tree: [
{
name: "Normal",
@@ -2235,7 +2260,7 @@ function createGroupedActivitySectionWithAdjusted(list, title) {
],
},
{
name: "destroyed",
name: "Destroyed",
tree: [
{
name: "Normal",

View File

@@ -115,7 +115,8 @@ export function initOptions(brk) {
}
// Remove from set if manual price line already exists
if (blueprint.type === "Line") {
// Note: line() doesn't set type, so undefined means Line
if (blueprint.type === "Line" || blueprint.type === undefined) {
const path = Object.values(blueprint.metric.by)[0]?.path ?? "";
if (path.includes("constant_")) {
priceLines.get(unit)?.delete(parseFloat(blueprint.title));

View File

@@ -85,7 +85,7 @@ export function createPriceWithRatioOptions(
return [
{
name: "price",
name: "Price",
title,
top: [line({ metric: priceMetric, name: legend, color, unit: Unit.usd })],
},
@@ -100,6 +100,9 @@ export function createPriceWithRatioOptions(
];
}
/** Common period IDs to show at top level */
const COMMON_PERIODS = ["1w", "1m", "200d", "1y", "200w", "4y"];
/**
* @param {PartialContext} ctx
* @param {MarketMovingAverage} movingAverage
@@ -113,35 +116,54 @@ export function createAveragesSection(ctx, movingAverage) {
* @param {string} label
* @param {ReturnType<typeof buildSmaAverages> | ReturnType<typeof buildEmaAverages>} averages
*/
const createSubSection = (label, averages) => ({
name: label,
tree: [
{
name: "Compare",
title: `Price ${label}s`,
top: averages.map(({ id, color, ratio }) =>
line({
metric: ratio.price,
name: id,
const createSubSection = (label, averages) => {
const commonAverages = averages.filter(({ id }) => COMMON_PERIODS.includes(id));
const moreAverages = averages.filter(({ id }) => !COMMON_PERIODS.includes(id));
return {
name: label,
tree: [
{
name: "Compare",
title: `Price ${label}s`,
top: averages.map(({ id, color, ratio }) =>
line({
metric: ratio.price,
name: id,
color,
unit: Unit.usd,
}),
),
},
// Common periods at top level
...commonAverages.map(({ name, color, ratio }) => ({
name,
tree: createPriceWithRatioOptions(ctx, {
ratio,
title: `${name} ${label}`,
legend: "average",
color,
unit: Unit.usd,
}),
),
},
...averages.map(({ name, color, ratio }) => ({
name,
tree: createPriceWithRatioOptions(ctx, {
ratio,
title: `${name} ${label}`,
legend: "average",
color,
}),
})),
],
});
})),
// Less common periods in "More..." folder
{
name: "More...",
tree: moreAverages.map(({ name, color, ratio }) => ({
name,
tree: createPriceWithRatioOptions(ctx, {
ratio,
title: `${name} ${label}`,
legend: "average",
color,
}),
})),
},
],
};
};
return {
name: "Averages",
name: "Moving Averages",
tree: [
createSubSection("SMA", smaAverages),
createSubSection("EMA", emaAverages),

View File

@@ -1,7 +1,7 @@
/** Bands indicators (MinMax, Mayer Multiple) */
import { Unit } from "../../../utils/units.js";
import { line } from "../../series.js";
import { Unit } from "../../utils/units.js";
import { line } from "../series.js";
/**
* Create Bands section

View File

@@ -4,9 +4,12 @@ import { localhost } from "../../utils/env.js";
import { Unit } from "../../utils/units.js";
import { candlestick, line } from "../series.js";
import { createAveragesSection } from "./averages.js";
import { createPerformanceSection } from "./performance.js";
import { createIndicatorsSection } from "./indicators/index.js";
import { createInvestingSection } from "./investing.js";
import { createReturnsSection } from "./performance.js";
import { createMomentumSection } from "./momentum.js";
import { createVolatilitySection } from "./volatility.js";
import { createBandsSection } from "./bands.js";
import { createValuationSection } from "./onchain.js";
import { createDcaVsLumpSumSection, createDcaByYearSection } from "./investing.js";
/**
* Create Market section
@@ -161,22 +164,29 @@ export function createMarketSection(ctx) {
],
},
// Averages
// Moving Averages
createAveragesSection(ctx, movingAverage),
// Performance
createPerformanceSection(ctx, returns),
// Returns
createReturnsSection(ctx, returns),
// Indicators
createIndicatorsSection(ctx, {
volatility,
range,
movingAverage,
indicators,
}),
// Volatility
createVolatilitySection(ctx, { volatility, range }),
// Investing
createInvestingSection(ctx, { dca, lookback, returns }),
// Momentum
createMomentumSection(ctx, indicators),
// Bands
createBandsSection(ctx, { range, movingAverage }),
// Valuation
createValuationSection(ctx, { indicators, movingAverage }),
// DCA vs Lump Sum
createDcaVsLumpSumSection(ctx, { dca, lookback, returns }),
// DCA by Year
createDcaByYearSection(ctx, { dca }),
],
};
}

View File

@@ -1,27 +0,0 @@
/** Indicators section - Main entry point */
import { createMomentumSection } from "./momentum.js";
import { createVolatilitySection } from "./volatility.js";
import { createBandsSection } from "./bands.js";
import { createOnchainSection } from "./onchain.js";
/**
* Create Indicators section
* @param {PartialContext} ctx
* @param {Object} args
* @param {Market["volatility"]} args.volatility
* @param {Market["range"]} args.range
* @param {Market["movingAverage"]} args.movingAverage
* @param {Market["indicators"]} args.indicators
*/
export function createIndicatorsSection(ctx, { volatility, range, movingAverage, indicators }) {
return {
name: "Indicators",
tree: [
createMomentumSection(ctx, indicators),
createVolatilitySection(ctx, { volatility, range }),
createBandsSection(ctx, { range, movingAverage }),
createOnchainSection(ctx, { indicators, movingAverage }),
],
};
}

View File

@@ -32,20 +32,115 @@ export function buildDcaClasses(colors, dca) {
costBasis: dca.classAveragePrice[`_${year}`],
returns: dca.classReturns[`_${year}`],
stack: dca.classStack[`_${year}`],
daysInProfit: dca.classDaysInProfit[`_${year}`],
daysInLoss: dca.classDaysInLoss[`_${year}`],
maxDrawdown: dca.classMaxDrawdown[`_${year}`],
maxReturn: dca.classMaxReturn[`_${year}`],
}));
}
/**
* Create Investing section
* Create DCA vs Lump Sum section
* @param {PartialContext} ctx
* @param {Object} args
* @param {Market["dca"]} args.dca
* @param {Market["lookback"]} args.lookback
* @param {Market["returns"]} args.returns
*/
export function createInvestingSection(ctx, { dca, lookback, returns }) {
export function createDcaVsLumpSumSection(ctx, { dca, lookback, returns }) {
const { colors } = ctx;
const dcaClasses = buildDcaClasses(colors, dca);
/**
* @param {string} name
* @param {ShortPeriodKey | LongPeriodKey} key
*/
const costBasisChart = (name, key) => ({
name: "Cost Basis",
title: `${name} Cost Basis`,
top: [
line({
metric: dca.periodAveragePrice[key],
name: "DCA",
color: colors.green,
unit: Unit.usd,
}),
line({
metric: lookback[key],
name: "Lump sum",
color: colors.orange,
unit: Unit.usd,
}),
],
});
/** @param {string} name @param {ShortPeriodKey | LongPeriodKey} key */
const daysInProfitChart = (name, key) => ({
name: "Days in Profit",
title: `${name} Days in Profit`,
top: [
line({ metric: dca.periodAveragePrice[key], name: "DCA", color: colors.green, unit: Unit.usd }),
line({ metric: lookback[key], name: "Lump sum", color: colors.orange, unit: Unit.usd }),
],
bottom: [
line({ metric: dca.periodDaysInProfit[key], name: "DCA", color: colors.green, unit: Unit.days }),
line({ metric: dca.periodLumpSumDaysInProfit[key], name: "Lump sum", color: colors.orange, unit: Unit.days }),
],
});
/** @param {string} name @param {ShortPeriodKey | LongPeriodKey} key */
const daysInLossChart = (name, key) => ({
name: "Days in Loss",
title: `${name} Days in Loss`,
top: [
line({ metric: dca.periodAveragePrice[key], name: "DCA", color: colors.green, unit: Unit.usd }),
line({ metric: lookback[key], name: "Lump sum", color: colors.orange, unit: Unit.usd }),
],
bottom: [
line({ metric: dca.periodDaysInLoss[key], name: "DCA", color: colors.red, unit: Unit.days }),
line({ metric: dca.periodLumpSumDaysInLoss[key], name: "Lump sum", color: colors.orange, unit: Unit.days }),
],
});
/** @param {string} name @param {ShortPeriodKey | LongPeriodKey} key */
const maxDrawdownChart = (name, key) => ({
name: "Max Drawdown",
title: `${name} Max Drawdown`,
top: [
line({ metric: dca.periodAveragePrice[key], name: "DCA", color: colors.green, unit: Unit.usd }),
line({ metric: lookback[key], name: "Lump sum", color: colors.orange, unit: Unit.usd }),
],
bottom: [
line({ metric: dca.periodMaxDrawdown[key], name: "DCA", color: colors.green, unit: Unit.percentage }),
line({ metric: dca.periodLumpSumMaxDrawdown[key], name: "Lump sum", color: colors.orange, unit: Unit.percentage }),
],
});
/** @param {string} name @param {ShortPeriodKey | LongPeriodKey} key */
const maxReturnChart = (name, key) => ({
name: "Max Return",
title: `${name} Max Return`,
top: [
line({ metric: dca.periodAveragePrice[key], name: "DCA", color: colors.green, unit: Unit.usd }),
line({ metric: lookback[key], name: "Lump sum", color: colors.orange, unit: Unit.usd }),
],
bottom: [
line({ metric: dca.periodMaxReturn[key], name: "DCA", color: colors.green, unit: Unit.percentage }),
line({ metric: dca.periodLumpSumMaxReturn[key], name: "Lump sum", color: colors.orange, unit: Unit.percentage }),
],
});
/**
* @param {string} name
* @param {ShortPeriodKey | LongPeriodKey} key
*/
const stackChart = (name, key) => ({
name: "Stack",
title: `${name} Stack`,
bottom: [
...satsBtcUsd(dca.periodStack[key], "DCA", colors.green),
...satsBtcUsd(dca.periodLumpSumStack[key], "Lump sum", colors.orange),
],
});
/**
* @param {string} id
@@ -56,31 +151,34 @@ export function createInvestingSection(ctx, { dca, lookback, returns }) {
return {
name,
tree: [
{
name: "Cost basis",
title: `${name} Cost Basis`,
top: [
line({ metric: dca.periodAveragePrice[key], name: "DCA", color: colors.green, unit: Unit.usd }),
line({ metric: lookback[key], name: "Lump sum", color: colors.orange, unit: Unit.usd }),
],
},
costBasisChart(name, key),
{
name: "Returns",
title: `${name} Returns`,
bottom: [
baseline({ metric: dca.periodReturns[key], name: "DCA", unit: Unit.percentage }),
baseline({ metric: returns.priceReturns[key], name: "Lump sum", color: [colors.cyan, colors.orange], unit: Unit.percentage }),
priceLine({ ctx, unit: Unit.percentage }),
baseline({
metric: dca.periodReturns[key],
name: "DCA",
unit: Unit.percentage,
}),
baseline({
metric: dca.periodLumpSumReturns[key],
name: "Lump sum",
color: [colors.cyan, colors.orange],
unit: Unit.percentage,
}),
],
},
{
name: "Stack",
title: `${name} Stack`,
bottom: [
...satsBtcUsd(dca.periodStack[key], "DCA", colors.green),
...satsBtcUsd(dca.periodLumpSumStack[key], "Lump sum", colors.orange),
name: "Profitability",
tree: [
daysInProfitChart(name, key),
daysInLossChart(name, key),
maxDrawdownChart(name, key),
maxReturnChart(name, key),
],
},
stackChart(name, key),
],
};
};
@@ -94,142 +192,226 @@ export function createInvestingSection(ctx, { dca, lookback, returns }) {
return {
name,
tree: [
{
name: "Cost basis",
title: `${name} Cost Basis`,
top: [
line({ metric: dca.periodAveragePrice[key], name: "DCA", color: colors.green, unit: Unit.usd }),
line({ metric: lookback[key], name: "Lump sum", color: colors.orange, unit: Unit.usd }),
],
},
costBasisChart(name, key),
{
name: "Returns",
title: `${name} Returns`,
bottom: [
baseline({ metric: dca.periodReturns[key], name: "DCA", unit: Unit.percentage }),
baseline({ metric: returns.priceReturns[key], name: "Lump sum", color: [colors.cyan, colors.orange], unit: Unit.percentage }),
line({ metric: dca.periodCagr[key], name: "DCA CAGR", color: colors.purple, unit: Unit.percentage, defaultActive: false }),
line({ metric: returns.cagr[key], name: "Lump sum CAGR", color: colors.indigo, unit: Unit.percentage, defaultActive: false }),
baseline({
metric: dca.periodReturns[key],
name: "DCA",
unit: Unit.percentage,
}),
baseline({
metric: dca.periodLumpSumReturns[key],
name: "Lump sum",
color: [colors.cyan, colors.orange],
unit: Unit.percentage,
}),
line({
metric: dca.periodCagr[key],
name: "DCA CAGR",
color: colors.purple,
unit: Unit.percentage,
defaultActive: false,
}),
line({
metric: returns.cagr[key],
name: "Lump sum CAGR",
color: colors.indigo,
unit: Unit.percentage,
defaultActive: false,
}),
priceLine({ ctx, unit: Unit.percentage }),
],
},
{
name: "Stack",
title: `${name} Stack`,
bottom: [
...satsBtcUsd(dca.periodStack[key], "DCA", colors.green),
...satsBtcUsd(dca.periodLumpSumStack[key], "Lump sum", colors.orange),
name: "Profitability",
tree: [
daysInProfitChart(name, key),
daysInLossChart(name, key),
maxDrawdownChart(name, key),
maxReturnChart(name, key),
],
},
stackChart(name, key),
],
};
};
return {
name: "Investing",
name: "DCA vs Lump Sum",
tree: [
// DCA vs Lump sum
{
name: "DCA vs Lump sum",
tree: [
createPeriodTree("1w", "_1w"),
createPeriodTree("1m", "_1m"),
createPeriodTree("3m", "_3m"),
createPeriodTree("6m", "_6m"),
createPeriodTree("1y", "_1y"),
createPeriodTreeWithCagr("2y", "_2y"),
createPeriodTreeWithCagr("3y", "_3y"),
createPeriodTreeWithCagr("4y", "_4y"),
createPeriodTreeWithCagr("5y", "_5y"),
createPeriodTreeWithCagr("6y", "_6y"),
createPeriodTreeWithCagr("8y", "_8y"),
createPeriodTreeWithCagr("10y", "_10y"),
],
},
// DCA classes
{
name: "DCA classes",
tree: [
// Comparison charts (all years overlaid)
{
name: "Compare",
tree: [
{
name: "Cost basis",
title: "DCA Cost Basis",
top: dcaClasses.map(
({ year, color, defaultActive, costBasis }) =>
line({
metric: costBasis,
name: `${year}`,
color,
defaultActive,
unit: Unit.usd,
}),
),
},
{
name: "Returns",
title: "DCA Returns",
bottom: dcaClasses.map(
({ year, color, defaultActive, returns }) =>
baseline({
metric: returns,
name: `${year}`,
color,
defaultActive,
unit: Unit.percentage,
}),
),
},
{
name: "Stack",
title: "DCA Stack",
bottom: dcaClasses.flatMap(
({ year, color, defaultActive, stack }) =>
satsBtcUsd(stack, `${year}`, color, { defaultActive }),
),
},
],
},
// Individual year charts
...dcaClasses.map(({ year, color, costBasis, returns, stack }) => ({
name: `${year}`,
tree: [
{
name: "Cost basis",
title: `${year} Cost Basis`,
top: [
line({
metric: costBasis,
name: "Cost basis",
color,
unit: Unit.usd,
}),
],
},
{
name: "Returns",
title: `${year} Returns`,
bottom: [
baseline({
metric: returns,
name: "Returns",
color,
unit: Unit.percentage,
}),
],
},
{
name: "Stack",
title: `${year} Stack`,
bottom: satsBtcUsd(stack, "Stack", color),
},
],
})),
],
},
createPeriodTree("1w", "_1w"),
createPeriodTree("1m", "_1m"),
createPeriodTree("3m", "_3m"),
createPeriodTree("6m", "_6m"),
createPeriodTree("1y", "_1y"),
createPeriodTreeWithCagr("2y", "_2y"),
createPeriodTreeWithCagr("3y", "_3y"),
createPeriodTreeWithCagr("4y", "_4y"),
createPeriodTreeWithCagr("5y", "_5y"),
createPeriodTreeWithCagr("6y", "_6y"),
createPeriodTreeWithCagr("8y", "_8y"),
createPeriodTreeWithCagr("10y", "_10y"),
],
};
}
/**
* Create DCA by Year section
* @param {PartialContext} ctx
* @param {Object} args
* @param {Market["dca"]} args.dca
*/
export function createDcaByYearSection(ctx, { dca }) {
const { colors } = ctx;
const dcaClasses = buildDcaClasses(colors, dca);
return {
name: "DCA by Year",
tree: [
// Comparison charts (all years overlaid)
{
name: "Compare",
tree: [
{
name: "Cost basis",
title: "DCA Cost Basis",
top: dcaClasses.map(({ year, color, defaultActive, costBasis }) =>
line({
metric: costBasis,
name: `${year}`,
color,
defaultActive,
unit: Unit.usd,
}),
),
},
{
name: "Returns",
title: "DCA Returns",
bottom: dcaClasses.map(({ year, defaultActive, returns }) =>
baseline({
metric: returns,
name: `${year}`,
defaultActive,
unit: Unit.percentage,
}),
),
},
{
name: "Profitability",
title: "DCA Profitability",
bottom: [
...dcaClasses.map(({ year, color, defaultActive, daysInProfit }) =>
line({
metric: daysInProfit,
name: `${year} Days in Profit`,
color,
defaultActive,
unit: Unit.days,
}),
),
...dcaClasses.map(({ year, color, daysInLoss }) =>
line({
metric: daysInLoss,
name: `${year} Days in Loss`,
color,
defaultActive: false,
unit: Unit.days,
}),
),
],
},
{
name: "Stack",
title: "DCA Stack",
bottom: dcaClasses.flatMap(
({ year, color, defaultActive, stack }) =>
satsBtcUsd(stack, `${year}`, color, { defaultActive }),
),
},
],
},
// Individual year charts
...dcaClasses.map(
({
year,
color,
costBasis,
returns,
stack,
daysInProfit,
daysInLoss,
maxDrawdown,
maxReturn,
}) => ({
name: `${year}`,
tree: [
{
name: "Cost Basis",
title: `${year} Cost Basis`,
top: [
line({
metric: costBasis,
name: "Cost Basis",
color,
unit: Unit.usd,
}),
],
},
{
name: "Returns",
title: `${year} Returns`,
bottom: [
baseline({
metric: returns,
name: "Returns",
unit: Unit.percentage,
}),
],
},
{
name: "Profitability",
title: `${year} Profitability`,
bottom: [
line({
metric: daysInProfit,
name: "Days in Profit",
color: colors.green,
unit: Unit.days,
}),
line({
metric: daysInLoss,
name: "Days in Loss",
color: colors.red,
unit: Unit.days,
}),
line({
metric: maxDrawdown,
name: "Max Drawdown",
color: colors.purple,
unit: Unit.percentage,
defaultActive: false,
}),
line({
metric: maxReturn,
name: "Max Return",
color: colors.cyan,
unit: Unit.percentage,
defaultActive: false,
}),
],
},
{
name: "Stack",
title: `${year} Stack`,
bottom: satsBtcUsd(stack, "Stack", color),
},
],
}),
),
],
};
}

View File

@@ -1,8 +1,8 @@
/** Momentum indicators (RSI, StochRSI, Stochastic, MACD) */
import { Unit } from "../../../utils/units.js";
import { priceLine, priceLines } from "../../constants.js";
import { line, histogram } from "../../series.js";
import { Unit } from "../../utils/units.js";
import { priceLine, priceLines } from "../constants.js";
import { line, histogram } from "../series.js";
/**
* Create Momentum section

View File

@@ -1,20 +1,20 @@
/** On-chain indicators (Pi Cycle, Puell, NVT, Gini) */
import { Unit } from "../../../utils/units.js";
import { baseline, line } from "../../series.js";
import { Unit } from "../../utils/units.js";
import { baseline, line } from "../series.js";
/**
* Create On-chain section
* Create Valuation section
* @param {PartialContext} ctx
* @param {Object} args
* @param {Market["indicators"]} args.indicators
* @param {Market["movingAverage"]} args.movingAverage
*/
export function createOnchainSection(ctx, { indicators, movingAverage }) {
export function createValuationSection(ctx, { indicators, movingAverage }) {
const { colors } = ctx;
return {
name: "On-chain",
name: "Valuation",
tree: [
{
name: "Pi Cycle",

View File

@@ -6,55 +6,100 @@ import { baseline } from "../series.js";
import { periodIdToName } from "./utils.js";
/**
* Create Performance section
* Create Returns section
* @param {PartialContext} ctx
* @param {Market["returns"]} returns
*/
export function createPerformanceSection(ctx, returns) {
export function createReturnsSection(ctx, returns) {
const { colors } = ctx;
const shortTermPeriods = /** @type {const} */ ([
["1d", "_1d", undefined],
["1w", "_1w", undefined],
["1m", "_1m", undefined],
]);
const mediumTermPeriods = /** @type {const} */ ([
["3m", "_3m", undefined],
["6m", "_6m", undefined],
["1y", "_1y", undefined],
]);
const longTermPeriods = /** @type {const} */ ([
["2y", "_2y", "_2y"],
["3y", "_3y", "_3y"],
["4y", "_4y", "_4y"],
["5y", "_5y", "_5y"],
["6y", "_6y", "_6y"],
["8y", "_8y", "_8y"],
["10y", "_10y", "_10y"],
]);
/**
* @template {keyof typeof returns.priceReturns} K
* @param {readonly [string, K, K | undefined]} period
*/
const createPeriodChart = ([id, returnKey, cagrKey]) => {
const priceReturns = returns.priceReturns[/** @type {K} */ (returnKey)];
const cagr = cagrKey ? returns.cagr[/** @type {keyof typeof returns.cagr} */ (cagrKey)] : undefined;
const name = periodIdToName(id, true);
return {
name,
title: `${name} Returns`,
bottom: [
baseline({
metric: priceReturns,
name: "Total",
unit: Unit.percentage,
}),
...(cagr
? [
baseline({
metric: cagr,
name: "CAGR",
color: [colors.cyan, colors.orange],
unit: Unit.percentage,
}),
]
: []),
priceLine({ ctx, unit: Unit.percentage }),
],
};
};
return {
name: "Performance",
tree: /** @type {const} */ ([
["1d", "_1d", undefined],
["1w", "_1w", undefined],
["1m", "_1m", undefined],
["3m", "_3m", undefined],
["6m", "_6m", undefined],
["1y", "_1y", undefined],
["2y", "_2y", "_2y"],
["3y", "_3y", "_3y"],
["4y", "_4y", "_4y"],
["5y", "_5y", "_5y"],
["6y", "_6y", "_6y"],
["8y", "_8y", "_8y"],
["10y", "_10y", "_10y"],
]).map(([id, returnKey, cagrKey]) => {
const priceReturns = returns.priceReturns[returnKey];
const cagr = cagrKey ? returns.cagr[cagrKey] : undefined;
const name = periodIdToName(id, true);
return {
name,
title: `${name} Returns`,
name: "Returns",
tree: [
// Compare all periods
{
name: "Compare",
title: "Returns Comparison",
bottom: [
baseline({
metric: priceReturns,
name: "Total",
unit: Unit.percentage,
}),
...(cagr
? [
baseline({
metric: cagr,
name: "CAGR",
color: [colors.cyan, colors.orange],
unit: Unit.percentage,
}),
]
: []),
baseline({ metric: returns.priceReturns._1d, name: "1d", color: colors.red, unit: Unit.percentage }),
baseline({ metric: returns.priceReturns._1w, name: "1w", color: colors.orange, unit: Unit.percentage }),
baseline({ metric: returns.priceReturns._1m, name: "1m", color: colors.yellow, unit: Unit.percentage }),
baseline({ metric: returns.priceReturns._3m, name: "3m", color: colors.lime, unit: Unit.percentage, defaultActive: false }),
baseline({ metric: returns.priceReturns._6m, name: "6m", color: colors.green, unit: Unit.percentage, defaultActive: false }),
baseline({ metric: returns.priceReturns._1y, name: "1y", color: colors.teal, unit: Unit.percentage }),
baseline({ metric: returns.priceReturns._4y, name: "4y", color: colors.blue, unit: Unit.percentage }),
priceLine({ ctx, unit: Unit.percentage }),
],
};
}),
},
// Short-term (1d, 1w, 1m)
{
name: "Short-term",
tree: shortTermPeriods.map(createPeriodChart),
},
// Medium-term (3m, 6m, 1y)
{
name: "Medium-term",
tree: mediumTermPeriods.map(createPeriodChart),
},
// Long-term (2y+)
{
name: "Long-term",
tree: longTermPeriods.map(createPeriodChart),
},
],
};
}

View File

@@ -1,8 +1,8 @@
/** Volatility indicators (Index, True Range, Choppiness, Sharpe, Sortino) */
import { Unit } from "../../../utils/units.js";
import { priceLine, priceLines } from "../../constants.js";
import { line } from "../../series.js";
import { Unit } from "../../utils/units.js";
import { priceLine, priceLines } from "../constants.js";
import { line } from "../series.js";
/**
* Create Volatility section

View File

@@ -95,62 +95,45 @@ export function createPartialOptions({ brk }) {
{
name: "Distribution",
tree: [
// All UTXOs - CohortAll (adjustedSopr + percentiles but no RelToMarketCap)
createCohortFolderAll(ctx, cohortAll),
// Overview - All UTXOs (adjustedSopr + percentiles but no RelToMarketCap)
createCohortFolderAll(ctx, { ...cohortAll, name: "Overview" }),
// Terms (STH/LTH) - Short is Full, Long has nupl
{
name: "Terms",
tree: [
// Compare folder - both have nupl + percentiles
createCohortFolderWithNupl(ctx, {
name: "Compare",
title: "Term",
list: [termShort, termLong],
}),
// Individual cohorts with their specific capabilities
createCohortFolderFull(ctx, termShort),
createCohortFolderWithNupl(ctx, termLong),
],
},
// STH - Short term holder cohort (Full capability)
createCohortFolderFull(ctx, termShort),
// Types - addressable types have addrCount, others don't
{
name: "Types",
tree: [
createCohortFolderAddress(ctx, {
name: "Compare",
title: "Type",
list: typeAddressable,
}),
...typeAddressable.map(mapAddress),
...typeOther.map(mapBasicWithoutMarketCap),
],
},
// LTH - Long term holder cohort (nupl)
createCohortFolderWithNupl(ctx, termLong),
// Age cohorts
// STH vs LTH - Direct comparison
createCohortFolderWithNupl(ctx, {
name: "STH vs LTH",
title: "Term",
list: [termShort, termLong],
}),
// Ages cohorts
{
name: "Age",
name: "Ages",
tree: [
// Up To (< X old)
// Younger Than (< X old)
{
name: "Up To",
name: "Younger Than",
tree: [
createCohortFolderWithAdjusted(ctx, {
name: "Compare",
title: "Age Up To",
title: "Age Younger Than",
list: upToDate,
}),
...upToDate.map(mapWithAdjusted),
],
},
// At Least (≥ X old)
// Older Than (≥ X old)
{
name: "At Least",
name: "Older Than",
tree: [
createCohortFolderBasicWithMarketCap(ctx, {
name: "Compare",
title: "Age At Least",
title: "Age Older Than",
list: fromDate,
}),
...fromDate.map(mapBasicWithMarketCap),
@@ -171,29 +154,29 @@ export function createPartialOptions({ brk }) {
],
},
// Amount cohorts (UTXO size)
// Sizes cohorts (UTXO size)
{
name: "Amount",
name: "Sizes",
tree: [
// Under (< X sats)
// Less Than (< X sats)
{
name: "Under",
name: "Less Than",
tree: [
createCohortFolderBasicWithMarketCap(ctx, {
name: "Compare",
title: "Amount Under",
title: "Size Less Than",
list: utxosUnderAmount,
}),
...utxosUnderAmount.map(mapBasicWithMarketCap),
],
},
// Above (≥ X sats)
// More Than (≥ X sats)
{
name: "Above",
name: "More Than",
tree: [
createCohortFolderBasicWithMarketCap(ctx, {
name: "Compare",
title: "Amount Above",
title: "Size More Than",
list: utxosAboveAmount,
}),
...utxosAboveAmount.map(mapBasicWithMarketCap),
@@ -205,7 +188,7 @@ export function createPartialOptions({ brk }) {
tree: [
createCohortFolderBasicWithoutMarketCap(ctx, {
name: "Compare",
title: "Amount Range",
title: "Size Range",
list: utxosAmountRanges,
}),
...utxosAmountRanges.map(mapBasicWithoutMarketCap),
@@ -214,29 +197,29 @@ export function createPartialOptions({ brk }) {
],
},
// Balance cohorts (Address balance)
// Balances cohorts (Address balance)
{
name: "Balance",
name: "Balances",
tree: [
// Under (< X sats)
// Less Than (< X sats)
{
name: "Under",
name: "Less Than",
tree: [
createAddressCohortFolder(ctx, {
name: "Compare",
title: "Balance Under",
title: "Balance Less Than",
list: addressesUnderAmount,
}),
...addressesUnderAmount.map(mapAddressCohorts),
],
},
// Above (≥ X sats)
// More Than (≥ X sats)
{
name: "Above",
name: "More Than",
tree: [
createAddressCohortFolder(ctx, {
name: "Compare",
title: "Balance Above",
title: "Balance More Than",
list: addressesAboveAmount,
}),
...addressesAboveAmount.map(mapAddressCohorts),
@@ -257,6 +240,20 @@ export function createPartialOptions({ brk }) {
],
},
// Script Types - addressable types have addrCount, others don't
{
name: "Script Types",
tree: [
createCohortFolderAddress(ctx, {
name: "Compare",
title: "Script Type",
list: typeAddressable,
}),
...typeAddressable.map(mapAddress),
...typeOther.map(mapBasicWithoutMarketCap),
],
},
// Epochs - CohortBasicWithoutMarketCap (no RelToMarketCap)
{
name: "Epochs",
@@ -285,8 +282,13 @@ export function createPartialOptions({ brk }) {
],
},
// Cointime section
createCointimeSection(ctx),
// Research section
{
name: "Research",
tree: [
createCointimeSection(ctx),
],
},
],
},

View File

@@ -166,7 +166,7 @@ export function createRatioChart(ctx, { title, price, ratio, color, name }) {
name: name ?? "ratio",
title: title(name ?? "Ratio"),
top: [
line({ metric: price, name: "price", color, unit: Unit.usd }),
line({ metric: price, name: "Price", color, unit: Unit.usd }),
...percentileUsdMap(colors, ratio).map(({ name, prop, color }) =>
line({
metric: prop,
@@ -221,7 +221,7 @@ export function createZScoresFolder(
const sdPats = sdPatterns(ratio);
return {
name: "ZScores",
name: "Z-Scores",
tree: [
{
name: "Compare",

View File

@@ -41,6 +41,8 @@ function walk(node, map, path) {
kn === "outputtype" ||
kn === "heighttopool" ||
kn === "txid" ||
kn.startsWith("satblocks") ||
kn.startsWith("satdays") ||
kn.endsWith("state") ||
kn.endsWith("index") ||
kn.endsWith("indexes") ||