mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-24 06:39:58 -07:00
investing: more data + charts
This commit is contained in:
@@ -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,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
@@ -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 }),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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 }),
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -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),
|
||||
},
|
||||
],
|
||||
}),
|
||||
),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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",
|
||||
@@ -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),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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") ||
|
||||
|
||||
Reference in New Issue
Block a user